/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * The "bscbus" driver provides access to the LOMlite2 virtual registers,
 * so that its clients (children) need not be concerned with the details
 * of the access mechanism, which in this case is implemented via a
 * packet-based protocol over a Xbus (similar to ebus) parallel link to the
 * H8 host interface registers.
 *
 * On the other hand, this driver doesn't generally know what the virtual
 * registers signify - only the clients need this information.
 */


#include <sys/note.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/file.h>

#if defined(__sparc)
#include <sys/intr.h>
#include <sys/membar.h>
#endif

#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/note.h>
#include <sys/open.h>
#include <sys/poll.h>
#include <sys/spl.h>
#include <sys/stat.h>
#include <sys/strlog.h>
#include <sys/atomic.h>

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>

#include <sys/bscbus.h>

#if	defined(NDI_ACC_HDL_V2)

/*
 * Compiling for Solaris 10+ with access handle enhancements
 */
#define	HANDLE_TYPE		ndi_acc_handle_t
#define	HANDLE_ADDR(hdlp)	(hdlp->ah_addr)
#define	HANDLE_FAULT(hdlp)	(hdlp->ah_fault)
#define	HANDLE_MAPLEN(hdlp)	(hdlp->ah_len)
#define	HANDLE_PRIVATE(hdlp)	(hdlp->ah_bus_private)

#else

/*
 * Compatibility definitions for backport to Solaris 8/9
 */
#define	HANDLE_TYPE		ddi_acc_impl_t
#define	HANDLE_ADDR(hdlp)	(hdlp->ahi_common.ah_addr)
#define	HANDLE_FAULT(hdlp)	(hdlp->ahi_fault)
#define	HANDLE_MAPLEN(hdlp)	(hdlp->ahi_common.ah_len)
#define	HANDLE_PRIVATE(hdlp)	(hdlp->ahi_common.ah_bus_private)

#define	ddi_driver_major(dip)	ddi_name_to_major(ddi_binding_name(dip))

#endif	/* NDI_ACC_HDL_V2 */


/*
 * Local definitions
 */
#define	MYNAME			"bscbus"
#define	NOMAJOR			(~(major_t)0)
#define	DUMMY_VALUE		(~(int8_t)0)

#define	BSCBUS_INST_TO_MINOR(i)	(i)
#define	BSCBUS_MINOR_TO_INST(m)	(m)

#define	BSCBUS_MAX_CHANNELS	(4)

#define	BSCBUS_DUMMY_ADDRESS	((caddr_t)0x0CADD1ED)
#define	ADDR_TO_OFFSET(a, hdlp)	((caddr_t)(a) - HANDLE_ADDR(hdlp))
#define	ADDR_TO_VREG(a)		((caddr_t)(a) - BSCBUS_DUMMY_ADDRESS)
#define	VREG_TO_ADDR(v)		(BSCBUS_DUMMY_ADDRESS + (v))

#ifdef DEBUG
#define	BSCBUS_LOGSTATUS
#endif /* DEBUG */

#ifdef BSCBUS_LOGSTATUS
/*
 * BSC command logging routines.
 * Record the data passing to and from the BSC
 */

typedef enum {
	BSC_CMD_BUSY = 1,		/* bsc reports busy	*/
	BSC_CMD_CLEARING = 2,		/* clearing bsc busy	*/
	BSC_CMD_CLEARED = 3,		/* cleared bsc busy	*/
	BSC_CMD_SENDING = 4,		/* sending next byte	*/
	BSC_CMD_SENT = 5,		/* sending last byte	*/
	BSC_CMD_PENDING = 6,		/* got sent byte ack	*/
	BSC_CMD_REPLY = 7,		/* got reply byte	*/
	BSC_CMD_COMPLETE = 8,		/* command complete	*/
	BSC_CMD_ERROR_SEQ = 9,		/* error status		*/
	BSC_CMD_ERROR_STATUS = 10,	/* error status		*/
	BSC_CMD_ERROR_OFLOW = 11,	/* error status		*/
	BSC_CMD_ERROR_TOUT = 12,	/* error status		*/

	BSC_CMD_PROCESS = 13,		/* async intr		*/
	BSC_CMD_V1INTR = 14,		/* v1 intr		*/
	BSC_CMD_V1INTRUNCL = 15,	/* v1 intr unclaim	*/
	BSC_CMD_DOGPAT = 17		/* watchdog pat		*/
} bsc_cmd_stamp_t;

typedef struct {
	hrtime_t	bcl_now;
	int		bcl_seq;
	bsc_cmd_stamp_t	bcl_cat;
	uint8_t		bcl_chno;
	uint8_t		bcl_cmdstate;
	uint8_t		bcl_status;
	uint8_t		bcl_data;
} bsc_cmd_log_t;

uint32_t	bscbus_cmd_log_size = 1024;

uint32_t	bscbus_cmd_log_flags = 0xffffffff;

#endif /* BSCBUS_LOGSTATUS */

/*
 * The following definitions are taken from the Hardware Manual for
 * the Hitachi H8S/2148 in conjunction with the hardware specification
 * for the Stiletto blade.
 *
 * Each instance of the host interface has 3 registers on the H8:
 * IDRn  - Input Data Register	- write-only for Solaris.
 *				  writes to this can be done via two
 *				  addresses - control and data.
 *				  The H8 can determine which address was
 *				  written by examining the C/D bit in
 *				  the status register.
 * ODRn  - Output Data Register - read-only for Solaris.
 *				  A read has the side effect of acknowledging
 *				  interrupts.
 * STRn  - Status Register	- read-only for Solaris.
 *
 *
 *
 * In terms of host access to this the Input and Output data registers are
 * mapped at the same address.
 */
#define	H8_IDRD	0
#define	H8_IDRC	1
#define	H8_ODR	0
#define	H8_STR	1

#define	H8_STR_OBF		0x01	/* data available in ODR */
#define	H8_STR_IBF		0x02	/* data for H8 in IDR */
#define	H8_STR_IDRC		0x08	/* last write to IDR was to IDRC */
					/* 0=data, 1=command */
#define	H8_STR_BUSY		0x04	/* H8 busy processing command */
#define	H8_STR_TOKENPROTOCOL	0x80	/* token-passing protocol */

/*
 * Packet format ...
 */
#define	BSCBUS_MASK		0xc0	/* Byte-type bits		*/
#define	BSCBUS_PARAM		0x00	/* Parameter byte: 0b0xxxxxxx	*/
#define	BSCBUS_LAST		0x80	/* Last byte of packet		*/
#define	BSCBUS_CMD		0x80	/* Command byte:   0b10###XWV	*/
#define	BSCBUS_STATUS		0xc0	/* Status  byte:   0b11###AEV	*/

#define	BSCBUS_SEQ		0x38	/* Sequence number bits		*/
#define	BSCBUS_SEQ_LSB		0x08	/* Sequence number LSB		*/
#define	BSCBUS_CMD_XADDR	0x04	/* Extended (2-byte) addressing	*/
#define	BSCBUS_CMD_WRITE	0x02	/* Write command		*/
#define	BSCBUS_CMD_WMSB		0x01	/* Set MSB on Write		*/
#define	BSCBUS_CMD_READ		0x01	/* Read command			*/
#define	BSCBUS_CMD_NOP		0x00	/* NOP command			*/

#define	BSCBUS_STATUS_ASYNC	0x04	/* Asynchronous event pending	*/
#define	BSCBUS_STATUS_ERR	0x02	/* Error in command processing	*/
#define	BSCBUS_STATUS_MSB	0x01	/* MSB of Value read		*/

#define	BSCBUS_VREG_LO(x)	((x) & ((1 << 7) - 1))
#define	BSCBUS_VREG_HI(x)	((x) >> 7)

#define	BSCBUS_BUFSIZE		8

#define	BSCBUS_CHANNEL_TO_OFFSET(chno)	((chno) * 2)	/* Register offset */

/*
 * Time periods, in nanoseconds
 *
 * Note that LOMBUS_ONE_SEC and some other time
 * periods are defined in <sys/lombus.h>
 */
#define	BSCBUS_CMD_POLL			(LOMBUS_ONE_SEC)
#define	BSCBUS_CMD_POLLNOINTS		(LOMBUS_ONE_SEC/20)
#define	BSCBUS_HWRESET_POLL		(LOMBUS_ONE_SEC/20)
#define	BSCBUS_HWRESET_TIMEOUT		(LOMBUS_ONE_SEC*2)

#define	BSCBUS_DOG_PAT_POLL_LIMIT	(1000)
#define	BSCBUS_DOG_PAT_POLL		(1)
#define	BSCBUS_PAT_RETRY_LIMIT	5

/*
 * Local datatypes
 */
enum bscbus_cmdstate {
	BSCBUS_CMDSTATE_IDLE,		/* No transaction in progress */
	BSCBUS_CMDSTATE_BUSY,		/* Setting up command */
	BSCBUS_CMDSTATE_CLEARING,	/* Clearing firmware busy status */
	BSCBUS_CMDSTATE_SENDING,	/* Waiting to send data to f/w */
	BSCBUS_CMDSTATE_PENDING,	/* Waiting for ack from f/w */
	BSCBUS_CMDSTATE_WAITING,	/* Waiting for status from f/w */
	BSCBUS_CMDSTATE_READY,		/* Status received/command done */
	BSCBUS_CMDSTATE_ERROR		/* Command failed with error */
};

struct bscbus_channel_state {
	/* Changes to these are protected by the instance ch_mutex mutex */
	struct bscbus_state	*ssp;
	uint8_t			*ch_regs;
	ddi_acc_handle_t	ch_handle;  /* per channel access handle */
	unsigned int		chno;
	unsigned int		map_count; /* Number of mappings to channel */
	boolean_t		map_dog;   /* channel is mapped for watchdog */

	/*
	 * Flag to indicate that we've incurred a hardware fault on
	 * accesses to the H8; once this is set, we fake all further
	 * accesses in order not to provoke additional bus errors.
	 */
	boolean_t		xio_fault;

	/*
	 * Data protected by the dog_mutex: the watchdog-patting
	 * protocol data (since the dog can be patted from a high-level
	 * cyclic), and the interrupt-enabled flag.
	 */
	kmutex_t		dog_mutex[1];
	unsigned int		pat_retry_count;
	unsigned int		pat_fail_count;

	/*
	 * Serial protocol state data, protected by lo_mutex
	 * (which is initialised using <lo_iblk>)
	 */
	kmutex_t		lo_mutex[1];
	ddi_iblock_cookie_t	lo_iblk;
	kcondvar_t		lo_cv[1];
	int			unclaimed_count;

	volatile enum bscbus_cmdstate cmdstate;
	clock_t			deadline;
	clock_t			poll_hz;
	boolean_t		interrupt_failed;
	uint8_t 		cmdbuf[BSCBUS_BUFSIZE];
	uint8_t			*cmdp;	/* Points to last tx'd in cmdbuf */
	uint8_t			reply[BSCBUS_BUFSIZE];
	uint8_t			async;
	uint8_t			index;
	uint8_t			result;
	uint8_t			sequence;
	uint32_t		error;
};

#define	BSCBUS_TX_PENDING(csp)		((csp)->cmdp > (csp)->cmdbuf)

/*
 * This driver's soft-state structure
 */

struct bscbus_state {
	/*
	 * Configuration data, set during attach
	 */
	dev_info_t		*dip;
	major_t			majornum;
	int			instance;

	ddi_acc_handle_t	h8_handle;
	uint8_t			*h8_regs;

	/*
	 * Parameters derived from .conf properties
	 */
	uint32_t		debug;

	/*
	 * Flag to indicate that we are using per channel
	 * mapping of the register sets and interrupts.
	 * reg set 0 is chan 0
	 * reg set 1 is chan 1 ...
	 *
	 * Interrupts are specified in that order but later
	 * channels may not have interrupts.
	 */
	boolean_t		per_channel_regs;

	/*
	 * channel state data, protected by ch_mutex
	 * channel claim/release requests are protected by this mutex.
	 */
	kmutex_t		ch_mutex[1];
	struct bscbus_channel_state	channel[BSCBUS_MAX_CHANNELS];

#ifdef BSCBUS_LOGSTATUS
	/*
	 * Command logging buffer for recording transactions with the
	 * BSC. This is useful for debugging failed transactions and other
	 * such funnies.
	 */
	bsc_cmd_log_t		*cmd_log;
	uint32_t		cmd_log_idx;
	uint32_t		cmd_log_size;
	uint32_t		cmd_log_flags;
#endif /* BSCBUS_LOGSTATUS */
};

/*
 * The auxiliary structure attached to each child
 * (the child's parent-private-data points to this).
 */
struct bscbus_child_info {
	lombus_regspec_t *rsp;
	int nregs;
};

#ifdef BSCBUS_LOGSTATUS
void bscbus_cmd_log(struct bscbus_channel_state *, bsc_cmd_stamp_t,
    uint8_t, uint8_t);
#else /* BSCBUS_LOGSTATUS */
#define	bscbus_cmd_log(state, stamp, status, data)
#endif /* BSCBUS_LOGSTATUS */


/*
 * Local data
 */

static void *bscbus_statep;

static major_t bscbus_major = NOMAJOR;

static ddi_device_acc_attr_t bscbus_dev_acc_attr[1] = {
	DDI_DEVICE_ATTR_V0,
	DDI_STRUCTURE_LE_ACC,
	DDI_STRICTORDER_ACC
};


/*
 *  General utility routines ...
 */

#ifdef DEBUG
static void
bscbus_trace(struct bscbus_channel_state *csp, char code, const char *caller,
	const char *fmt, ...)
{
	char buf[256];
	char *p;
	va_list va;

	if (csp->ssp->debug & (1 << (code-'@'))) {
		p = buf;
		(void) snprintf(p, sizeof (buf) - (p - buf),
		    "%s/%s: ", MYNAME, caller);
		p += strlen(p);

		va_start(va, fmt);
		(void) vsnprintf(p, sizeof (buf) - (p - buf), fmt, va);
		va_end(va);

		buf[sizeof (buf) - 1] = '\0';
		(void) strlog(csp->ssp->majornum, csp->ssp->instance,
		    code, SL_TRACE, buf);
	}
}
#else /* DEBUG */
#define	bscbus_trace
#endif /* DEBUG */

static struct bscbus_state *
bscbus_getstate(dev_info_t *dip, int instance, const char *caller)
{
	struct bscbus_state *ssp = NULL;
	dev_info_t *sdip = NULL;
	major_t dmaj = NOMAJOR;

	if (dip != NULL) {
		/*
		 * Use the instance number from the <dip>; also,
		 * check that it really corresponds to this driver
		 */
		instance = ddi_get_instance(dip);
		dmaj = ddi_driver_major(dip);
		if (bscbus_major == NOMAJOR && dmaj != NOMAJOR)
			bscbus_major = dmaj;
		else if (dmaj != bscbus_major) {
			cmn_err(CE_WARN,
			    "%s: major number mismatch (%d vs. %d) in %s(),"
			    "probably due to child misconfiguration",
			    MYNAME, bscbus_major, dmaj, caller);
			instance = -1;
		}
	}

	if (instance >= 0)
		ssp = ddi_get_soft_state(bscbus_statep, instance);
	if (ssp != NULL) {
		sdip = ssp->dip;
		if (dip == NULL && sdip == NULL)
			ssp = NULL;
		else if (dip != NULL && sdip != NULL && sdip != dip) {
			cmn_err(CE_WARN,
			    "%s: devinfo mismatch (%p vs. %p) in %s(), "
			    "probably due to child misconfiguration",
			    MYNAME, (void *)dip, (void *)sdip, caller);
			ssp = NULL;
		}
	}

	return (ssp);
}

/*
 * Lowest-level I/O register read/write
 */

static void
bscbus_put_reg(struct bscbus_channel_state *csp, uint_t reg, uint8_t val)
{
	if (csp->ch_handle != NULL && !csp->xio_fault) {
		ddi_put8(csp->ch_handle,
		    csp->ch_regs + reg, val);
	}
}

static uint8_t
bscbus_get_reg(struct bscbus_channel_state *csp, uint_t reg)
{
	uint8_t val;

	if (csp->ch_handle != NULL && !csp->xio_fault)
		val = ddi_get8(csp->ch_handle,
		    csp->ch_regs + reg);
	else
		val = DUMMY_VALUE;

	return (val);
}

static void
bscbus_check_fault_status(struct bscbus_channel_state *csp)
{
	csp->xio_fault =
	    ddi_check_acc_handle(csp->ch_handle) != DDI_SUCCESS;
}

static boolean_t
bscbus_faulty(struct bscbus_channel_state *csp)
{
	if (!csp->xio_fault)
		bscbus_check_fault_status(csp);
	return (csp->xio_fault);
}

/*
 * Write data into h8 registers
 */
static void
bscbus_pat_dog(struct bscbus_channel_state *csp, uint8_t val)
{
	uint8_t status;
	uint32_t doglimit = BSCBUS_DOG_PAT_POLL_LIMIT;

	bscbus_trace(csp, 'W', "bscbus_pat_dog:", "");

	bscbus_cmd_log(csp, BSC_CMD_DOGPAT, 0, val);
	status = bscbus_get_reg(csp, H8_STR);
	while (status & H8_STR_IBF) {
		if (csp->pat_retry_count > BSCBUS_PAT_RETRY_LIMIT) {
			/*
			 * Previous attempts to contact BSC have failed.
			 * Do not bother waiting for it to eat previous
			 * data.
			 * Pat anyway just in case the BSC is really alive
			 * and the IBF bit is lying.
			 */
			bscbus_put_reg(csp, H8_IDRC, val);
			bscbus_trace(csp, 'W', "bscbus_pat_dog:",
			    "retry count exceeded");
			return;
		}
		if (--doglimit == 0) {
			/* The BSC is not responding - give up */
			csp->pat_fail_count++;
			csp->pat_retry_count++;
			/* Pat anyway just in case the BSC is really alive */
			bscbus_put_reg(csp, H8_IDRC, val);
			bscbus_trace(csp, 'W', "bscbus_pat_dog:",
			    "poll limit exceeded");
			return;
		}
		drv_usecwait(BSCBUS_DOG_PAT_POLL);
		status = bscbus_get_reg(csp, H8_STR);
	}
	bscbus_put_reg(csp, H8_IDRC, val);
	csp->pat_retry_count = 0;
}

/*
 * State diagrams for how bscbus_process works.
 *	BSCBUS_CMDSTATE_IDLE		No transaction in progress
 *	BSCBUS_CMDSTATE_BUSY		Setting up command
 *	BSCBUS_CMDSTATE_CLEARING	Clearing firmware busy status
 *	BSCBUS_CMDSTATE_SENDING		Waiting to send data to f/w
 *	BSCBUS_CMDSTATE_PENDING		Waiting for ack from f/w
 *	BSCBUS_CMDSTATE_WAITING		Waiting for status from f/w
 *	BSCBUS_CMDSTATE_READY		Status received/command done
 *	BSCBUS_CMDSTATE_ERROR		Command failed with error
 *
 *	+----------+
 *	|	   |
 *	| IDLE/BUSY|
 *	|   (0/1)  |  abnormal
 *	+----------+  state
 *	    |	  \   detected
 *	    |	   \------>------+  +----<---+
 *	bsc |			 |  |	     |
 *	is  |			 V  V	     |
 *     ready|		     +----------+    |
 *	    |		     |		|    ^
 *	    |		     | CLEARING |    |
 *	    |		     |	 (2)	|    |
 *	    |		     +----------+    |
 *	    |		 cleared /  | \	     | more to clear
 *	    |			/   |  \-->--+
 *	    |  +-------<-------/    V
 *	    |  |		    |
 *	    V  V		    |timeout
 *	+----------+ timeout	    |
 *	|	   |------>---------+--------+
 *	| SENDING  |			     |
 *	|   (3)	   |------<-------+	     |
 *	+----------+		  |	     V
 *	sent|	 \ send		  ^ack	     |
 *	last|	  \ next	  |received  |
 *	    |	   \	     +----------+    |
 *	    |	    \	     |		|    |
 *	    |	     \------>| PENDING	|-->-+
 *	    |		     |	 (4)	|    |
 *	    |		     +----------+    |timeout
 *	    |	 +---<----+		     |
 *	    |	 |	  |		     |
 *	    V	 V	  |		     |
 *	+----------+	  |		     |
 *	|	   |	  |		     |
 *	| WAITING  |	  ^		     |
 *	|   (5)	   |	  |		     |
 *	+----------+	  |		     |
 *	    |  | |more	  |		     |
 *	    |  V |required|		     |
 *	done|  | +--->----+		     |
 *	    |  +--->--------------+  +---<---+
 *	    |	error/timeout	  |  |
 *	    V			  V  V
 *	+----------+	      +----------+
 *	|	   |	      |		 |
 *	| READY	   |	      |	 ERROR	 |
 *	|   (7)	   |	      |	  (6)	 |
 *	+----------+	      +----------+
 *	    |			  |
 *	    V			  V
 *	    |			  |
 *	    +------>---+---<------+
 *		       |
 *		       |
 *		     Back to
 *		      Idle
 */

static void
bscbus_process_sending(struct bscbus_channel_state *csp, uint8_t status)
{
	/*
	 * When we get here we actually expect H8_STR_IBF to
	 * be clear but we check just in case of problems.
	 */
	ASSERT(BSCBUS_TX_PENDING(csp));
	if (!(status & H8_STR_IBF)) {
		bscbus_put_reg(csp, H8_IDRD, *--csp->cmdp);
		bscbus_trace(csp, 'P', "bscbus_process_sending",
		    "state %d; val $%x",
		    csp->cmdstate, *csp->cmdp);
		if (!BSCBUS_TX_PENDING(csp)) {
			bscbus_cmd_log(csp, BSC_CMD_SENT,
			    status, *csp->cmdp);
			/* No more pending - move to waiting state */
			bscbus_trace(csp, 'P', "bscbus_process_sending",
			    "moving to waiting");
			csp->cmdstate = BSCBUS_CMDSTATE_WAITING;
			/* Extend deadline because time has moved on */
			csp->deadline = ddi_get_lbolt() +
			    drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
		} else {
			/* Wait for ack of this byte */
			bscbus_cmd_log(csp, BSC_CMD_SENDING,
			    status, *csp->cmdp);
			csp->cmdstate = BSCBUS_CMDSTATE_PENDING;
			bscbus_trace(csp, 'P', "bscbus_process_sending",
			    "moving to pending");
		}
	}
}

static void
bscbus_process_clearing(struct bscbus_channel_state *csp,
    uint8_t status, uint8_t data)
{
	/*
	 * We only enter this state if H8_STR_BUSY was set when
	 * we started the transaction. We just ignore all received
	 * data until we see OBF set AND BUSY cleared.
	 * It is not good enough to see BUSY clear on its own
	 */
	if ((status & H8_STR_OBF) && !(status & H8_STR_BUSY)) {
		bscbus_cmd_log(csp, BSC_CMD_CLEARED, status, data);
		csp->cmdstate = BSCBUS_CMDSTATE_SENDING;
		/* Throw away any data received up until now */
		bscbus_trace(csp, 'P', "bscbus_process_clearing",
		    "busy cleared");
		/*
		 * Send the next byte immediately.
		 * At this stage we should clear the OBF flag because that
		 * data has been used. IBF is still valid so do not clear that.
		 */
		status &= ~(H8_STR_OBF);
		bscbus_process_sending(csp, status);
	} else {
		if (status & H8_STR_OBF) {
			bscbus_cmd_log(csp, BSC_CMD_CLEARING, status, data);
		}
	}
}

static void
bscbus_process_pending(struct bscbus_channel_state *csp, uint8_t status)
{
	/* We are waiting for an acknowledgement of a byte */
	if (status & H8_STR_OBF) {
		bscbus_cmd_log(csp, BSC_CMD_PENDING,
		    status, *csp->cmdp);
		bscbus_trace(csp, 'P', "bscbus_process_pending",
		    "moving to sending");
		csp->cmdstate = BSCBUS_CMDSTATE_SENDING;
		/*
		 * Send the next byte immediately.
		 * At this stage we should clear the OBF flag because that
		 * data has been used. IBF is still valid so do not clear that.
		 */
		status &= ~(H8_STR_OBF);
		bscbus_process_sending(csp, status);
	}
}

static boolean_t
bscbus_process_waiting(struct bscbus_channel_state *csp,
    uint8_t status, uint8_t data)
{
	uint8_t rcvd = 0;
	boolean_t ready = B_FALSE;
	uint8_t tmp;

	if (status & H8_STR_OBF) {
		csp->reply[rcvd = csp->index] = data;
		if (++rcvd < BSCBUS_BUFSIZE)
			csp->index = rcvd;

		bscbus_trace(csp, 'D', "bscbus_process_waiting",
		    "rcvd %d: $%02x $%02x $%02x $%02x $%02x $%02x $%02x $%02x",
		    rcvd,
		    csp->reply[0], csp->reply[1],
		    csp->reply[2], csp->reply[3],
		    csp->reply[4], csp->reply[5],
		    csp->reply[6], csp->reply[7]);
	}

	if (rcvd == 0) {
		/*
		 * No bytes received this time through (though there
		 * might be a partial packet sitting in the buffer).
		 */
		/* EMPTY */
		;
	} else if (rcvd >= BSCBUS_BUFSIZE) {
		/*
		 * Buffer overflow; discard the data & treat as an error
		 * (even if the last byte read did claim to terminate a
		 * packet, it can't be a valid one 'cos it's too long!)
		 */
		bscbus_cmd_log(csp, BSC_CMD_ERROR_OFLOW, status, data);
		csp->index = 0;
		csp->cmdstate = BSCBUS_CMDSTATE_ERROR;
		csp->error = LOMBUS_ERR_OFLOW;
		ready = B_TRUE;
	} else if ((data & BSCBUS_LAST) == 0) {
		/*
		 * Packet not yet complete; leave the partial packet in
		 * the buffer for later ...
		 */
		bscbus_cmd_log(csp, BSC_CMD_REPLY, status, data);
	} else if ((data & BSCBUS_MASK) != BSCBUS_STATUS) {
		/* Invalid "status" byte - maybe an echo of the command? */
		bscbus_cmd_log(csp, BSC_CMD_ERROR_STATUS, status, data);

		csp->cmdstate = BSCBUS_CMDSTATE_ERROR;
		csp->error = LOMBUS_ERR_BADSTATUS;
		ready = B_TRUE;
	} else if ((data & BSCBUS_SEQ) != csp->sequence) {
		/* Wrong sequence number!  Flag this as an error */
		bscbus_cmd_log(csp, BSC_CMD_ERROR_SEQ, status, data);

		csp->cmdstate = BSCBUS_CMDSTATE_ERROR;
		csp->error = LOMBUS_ERR_SEQUENCE;
		ready = B_TRUE;
	} else {
		/*
		 * Finally, we know that's it's a valid reply to our
		 * last command.  Update the ASYNC status, derive the
		 * reply parameter (if any), and check the ERROR bit
		 * to find out what the parameter means.
		 *
		 * Note that not all the values read/assigned here
		 * are meaningful, but it doesn't matter; the waiting
		 * thread will know which one(s) it should check.
		 */
		bscbus_cmd_log(csp, BSC_CMD_COMPLETE, status, data);
		csp->async = (data & BSCBUS_STATUS_ASYNC) ? 1 : 0;

		tmp = ((data & BSCBUS_STATUS_MSB) ? 0x80 : 0) | csp->reply[0];
		if (data & BSCBUS_STATUS_ERR) {
			csp->cmdstate = BSCBUS_CMDSTATE_ERROR;
			csp->error = tmp;
		} else {
			csp->cmdstate = BSCBUS_CMDSTATE_READY;
			csp->result = tmp;
		}
		ready = B_TRUE;
	}
	return (ready);
}

/*
 * Packet receive handler
 *
 * This routine should be called from the low-level softint,
 * or bscbus_cmd() (for polled operation), with the
 * low-level mutex already held.
 */
static void
bscbus_process(struct bscbus_channel_state *csp,
    uint8_t status, uint8_t data)
{
	boolean_t ready = B_FALSE;

	ASSERT(mutex_owned(csp->lo_mutex));

	if ((status & H8_STR_OBF) || (status & H8_STR_IBF)) {
		bscbus_trace(csp, 'D', "bscbus_process",
		    "state %d; error $%x",
		    csp->cmdstate, csp->error);
	}

	switch (csp->cmdstate) {
	case BSCBUS_CMDSTATE_CLEARING:
		bscbus_process_clearing(csp, status, data);
		break;
	case BSCBUS_CMDSTATE_SENDING:
		bscbus_process_sending(csp, status);
		break;
	case BSCBUS_CMDSTATE_PENDING:
		bscbus_process_pending(csp, status);
		break;
	case BSCBUS_CMDSTATE_WAITING:
		ready = bscbus_process_waiting(csp, status, data);
		break;
	default:
		/* Nothing to do */
		break;
	}

	/*
	 * Check for timeouts - but only if the command has not yet
	 * completed (ready is true when command completes in this
	 * call to bscbus_process OR cmdstate is READY or ERROR if
	 * this is a spurious call to bscbus_process i.e. a spurious
	 * interrupt)
	 */
	if (!ready &&
	    ((ddi_get_lbolt() - csp->deadline) > 0) &&
	    csp->cmdstate != BSCBUS_CMDSTATE_READY &&
	    csp->cmdstate != BSCBUS_CMDSTATE_ERROR) {
		bscbus_trace(csp, 'P', "bscbus_process",
		    "timeout previous state %d; error $%x",
		    csp->cmdstate, csp->error);
		bscbus_cmd_log(csp, BSC_CMD_ERROR_TOUT, status, data);
		if (csp->cmdstate == BSCBUS_CMDSTATE_CLEARING) {
			/* Move onto sending because busy might be stuck */
			csp->cmdstate = BSCBUS_CMDSTATE_SENDING;
			/* Extend timeout relative to original start time */
			csp->deadline += drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
		} else if (csp->cmdstate != BSCBUS_CMDSTATE_IDLE) {
			csp->cmdstate = BSCBUS_CMDSTATE_ERROR;
			csp->error = LOMBUS_ERR_TIMEOUT;
		}
		ready = B_TRUE;
	}

	if ((status & H8_STR_OBF) || (status & H8_STR_IBF) || ready) {
		bscbus_trace(csp, 'D', "bscbus_process",
		    "last $%02x; state %d; error $%x; ready %d",
		    data, csp->cmdstate, csp->error, ready);
	}
	if (ready)
		cv_broadcast(csp->lo_cv);
}

static uint_t
bscbus_hwintr(caddr_t arg)
{
	struct bscbus_channel_state *csp = (void *)arg;

	uint8_t status;
	uint8_t data = 0xb0 /* Dummy value */;

	mutex_enter(csp->lo_mutex);
	/*
	 * Read the registers to ensure that the interrupt is cleared.
	 * Status must be read first because reading data changes the
	 * status.
	 * We always read the data because that clears the interrupt down.
	 * This is horrible hardware semantics but we have to do it!
	 */
	status = bscbus_get_reg(csp, H8_STR);
	data = bscbus_get_reg(csp, H8_ODR);
	if (!(status & H8_STR_OBF)) {
		bscbus_cmd_log(csp, BSC_CMD_V1INTRUNCL, status, data);
		csp->unclaimed_count++;
	} else {
		bscbus_cmd_log(csp, BSC_CMD_V1INTR, status, data);
	}
	if (status & H8_STR_TOKENPROTOCOL) {
		bscbus_process(csp, status, data);
		if (csp->interrupt_failed) {
			bscbus_trace(csp, 'I', "bscbus_hwintr:",
			    "interrupt fault cleared channel %d", csp->chno);
			csp->interrupt_failed = B_FALSE;
			csp->poll_hz = drv_usectohz(BSCBUS_CMD_POLL / 1000);
		}
	}

	mutex_exit(csp->lo_mutex);
	return (DDI_INTR_CLAIMED);
}

void
bscbus_poll(struct bscbus_channel_state *csp)
{
	/*
	 * This routine is only called if we timeout in userland
	 * waiting for an interrupt. This generally means that we have
	 * lost interrupt capabilities or that something has gone
	 * wrong.  In this case we are allowed to access the hardware
	 * and read the data register if necessary.
	 * If interrupts return then recovery actions should mend us!
	 */
	uint8_t status;
	uint8_t data = 0xfa; /* Dummy value */

	ASSERT(mutex_owned(csp->lo_mutex));

	/* Should look for data to receive */
	status = bscbus_get_reg(csp, H8_STR);
	if (status & H8_STR_OBF) {
		/* There is data available */
		data = bscbus_get_reg(csp, H8_ODR);
		bscbus_cmd_log(csp, BSC_CMD_PROCESS, status, data);
	}
	bscbus_process(csp, status, data);
}

/*
 * Serial protocol
 *
 * This routine builds a command and sets it in progress.
 */
static uint8_t
bscbus_cmd(HANDLE_TYPE *hdlp, ptrdiff_t vreg, uint_t val, uint_t cmd)
{
	struct bscbus_channel_state *csp;
	clock_t start;
	uint8_t status;

	/*
	 * First of all, wait for the interface to be available.
	 *
	 * NOTE: we blow through all the mutex/cv/state checking and
	 * preempt any command in progress if the system is panicking!
	 */
	csp = HANDLE_PRIVATE(hdlp);
	mutex_enter(csp->lo_mutex);
	while (csp->cmdstate != BSCBUS_CMDSTATE_IDLE && !ddi_in_panic())
		cv_wait(csp->lo_cv, csp->lo_mutex);

	csp->cmdstate = BSCBUS_CMDSTATE_BUSY;
	csp->sequence = (csp->sequence + BSCBUS_SEQ_LSB) & BSCBUS_SEQ;

	/*
	 * We have exclusive ownership, so assemble the command (backwards):
	 *
	 * [byte 0]	Command:	modified by XADDR and/or WMSB bits
	 * [Optional] Parameter: 	Value to write (low 7 bits)
	 * [Optional] Parameter: 	Register number (high 7 bits)
	 * [Optional] Parameter: 	Register number (low 7 bits)
	 */
	csp->cmdp = &csp->cmdbuf[0];
	*csp->cmdp++ = BSCBUS_CMD | csp->sequence | cmd;
	switch (cmd) {
	case BSCBUS_CMD_WRITE:
		*csp->cmdp++ = val & 0x7f;
		if (val >= 0x80)
			csp->cmdbuf[0] |= BSCBUS_CMD_WMSB;
		/*FALLTHRU*/
	case BSCBUS_CMD_READ:
		if (BSCBUS_VREG_HI(vreg) != 0) {
			*csp->cmdp++ = BSCBUS_VREG_HI(vreg);
			csp->cmdbuf[0] |= BSCBUS_CMD_XADDR;
		}
		*csp->cmdp++ = BSCBUS_VREG_LO(vreg);
		/*FALLTHRU*/
	case BSCBUS_CMD_NOP:
		break;
	}

	/*
	 * Check and update the H8 h/w fault status before accessing
	 * the chip registers.  If there's a (new or previous) fault,
	 * we'll run through the protocol but won't really touch the
	 * hardware and all commands will timeout.  If a previously
	 * discovered fault has now gone away (!), then we can (try to)
	 * proceed with the new command (probably a probe).
	 */
	bscbus_check_fault_status(csp);

	/*
	 * Prepare for the command (to be processed by the interrupt
	 * handler and/or polling loop below), and wait for a response
	 * or timeout.
	 */
	start = ddi_get_lbolt();
	csp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
	csp->error = 0;
	csp->index = 0;
	csp->result = DUMMY_VALUE;

	status = bscbus_get_reg(csp, H8_STR);
	if (status & H8_STR_BUSY) {
		bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0xfd);
		/*
		 * Must ensure that the busy state has cleared before
		 * sending the command
		 */
		csp->cmdstate = BSCBUS_CMDSTATE_CLEARING;
		bscbus_trace(csp, 'P', "bscbus_cmd",
		    "h8 reporting status (%x) busy - clearing", status);
	} else {
		/* It is clear to send the command immediately */
		csp->cmdstate = BSCBUS_CMDSTATE_SENDING;
		bscbus_trace(csp, 'P', "bscbus_cmd",
		    "sending first byte of command, status %x", status);
		bscbus_poll(csp);
	}

	csp->poll_hz = drv_usectohz(
	    (csp->interrupt_failed ?
	    BSCBUS_CMD_POLLNOINTS : BSCBUS_CMD_POLL) / 1000);

	while ((csp->cmdstate != BSCBUS_CMDSTATE_READY) &&
	    (csp->cmdstate != BSCBUS_CMDSTATE_ERROR)) {
		ASSERT(csp->cmdstate != BSCBUS_CMDSTATE_IDLE);

		if ((cv_reltimedwait(csp->lo_cv, csp->lo_mutex,
		    csp->poll_hz, TR_CLOCK_TICK) == -1) &&
		    csp->cmdstate != BSCBUS_CMDSTATE_READY &&
		    csp->cmdstate != BSCBUS_CMDSTATE_ERROR) {
			if (!csp->interrupt_failed) {
				bscbus_trace(csp, 'I', "bscbus_cmd:",
				    "interrupt_failed channel %d", csp->chno);
				csp->interrupt_failed = B_TRUE;
				csp->poll_hz = drv_usectohz(
				    BSCBUS_CMD_POLLNOINTS / 1000);
			}
			bscbus_poll(csp);
		}
	}

	/*
	 * The return value may not be meaningful but retrieve it anyway
	 */
	val = csp->result;
	if (bscbus_faulty(csp)) {
		val = DUMMY_VALUE;
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW;
	} else if (csp->cmdstate != BSCBUS_CMDSTATE_READY) {
		/*
		 * Some problem here ... transfer the error code from
		 * the per-instance state to the per-handle fault flag.
		 * The error code shouldn't be zero!
		 */
		if (csp->error != 0)
			HANDLE_FAULT(hdlp) = csp->error;
		else
			HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE;
	}

	/*
	 * All done now!
	 */
	csp->index = 0;
	csp->cmdstate = BSCBUS_CMDSTATE_IDLE;
	cv_broadcast(csp->lo_cv);
	mutex_exit(csp->lo_mutex);

	return (val);
}

/*
 * Space 0 - LOM virtual register access
 * Only 8-bit accesses are supported.
 */
static uint8_t
bscbus_vreg_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
	ptrdiff_t offset;

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping originally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return a dummy value
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return (DUMMY_VALUE);
	}

	/*
	 * Derive the virtual register number and run the command
	 */
	return (bscbus_cmd(hdlp, ADDR_TO_VREG(addr), 0, BSCBUS_CMD_READ));
}

static void
bscbus_vreg_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
	ptrdiff_t offset;

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping originally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return;
	}

	/*
	 * Derive the virtual register number and run the command
	 */
	(void) bscbus_cmd(hdlp, ADDR_TO_VREG(addr), val, BSCBUS_CMD_WRITE);
}

static void
bscbus_vreg_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
	uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		*host_addr++ = bscbus_vreg_get8(hdlp, dev_addr);
}

static void
bscbus_vreg_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
	uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		bscbus_vreg_put8(hdlp, dev_addr, *host_addr++);
}


/*
 * Space 1 - LOM watchdog pat register access
 * Only 8-bit accesses are supported.
 *
 * Reads have no effect and return 0.
 *
 * Multi-byte reads (using ddi_rep_get8(9F)) are a fairly inefficient
 * way of zeroing the destination area ;-) and still won't pat the dog.
 *
 * Multi-byte writes (using ddi_rep_put8(9F)) will almost certainly
 * only count as a single pat, no matter how many bytes the caller
 * says to write, as the inter-pat time is VERY long compared with
 * the time it will take to read the memory source area.
 */

static uint8_t
bscbus_pat_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
	ptrdiff_t offset;

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping originally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return a dummy value
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return (DUMMY_VALUE);
	}

	return (0);
}

static void
bscbus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
	struct bscbus_channel_state *csp;
	ptrdiff_t offset;

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping originally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return;
	}

	csp = HANDLE_PRIVATE(hdlp);
	mutex_enter(csp->dog_mutex);
	bscbus_pat_dog(csp, val);
	mutex_exit(csp->dog_mutex);
}

static void
bscbus_pat_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
	uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		*host_addr++ = bscbus_pat_get8(hdlp, dev_addr);
}

static void
bscbus_pat_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
	uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		bscbus_pat_put8(hdlp, dev_addr, *host_addr++);
}


/*
 * Space 2 - LOM async event flag register access
 * Only 16-bit accesses are supported.
 */
static uint16_t
bscbus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
	struct bscbus_channel_state *csp;
	ptrdiff_t offset;

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping orignally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return a dummy value
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return (DUMMY_VALUE);
	}

	/*
	 * Return the value of the asynchronous-event-pending flag
	 * as passed back by the LOM at the end of the last command.
	 */
	csp = HANDLE_PRIVATE(hdlp);
	return (csp->async);
}

static void
bscbus_event_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
	ptrdiff_t offset;

	_NOTE(ARGUNUSED(val))

	/*
	 * Check the offset that the caller has added to the base address
	 * against the length of the mapping originally requested.
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
		/*
		 * Invalid access - flag a fault and return
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
		return;
	}

	/*
	 * The user can't overwrite the asynchronous-event-pending flag!
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_RO;
}

static void
bscbus_event_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
	uint16_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		*host_addr++ = bscbus_event_get16(hdlp, dev_addr);
}

static void
bscbus_event_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
	uint16_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		bscbus_event_put16(hdlp, dev_addr, *host_addr++);
}


/*
 * All spaces - access handle fault information
 * Only 32-bit accesses are supported.
 */
static uint32_t
bscbus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr)
{
	struct bscbus_channel_state *csp;
	ptrdiff_t offset;

	/*
	 * Derive the offset that the caller has added to the base
	 * address originally returned, and use it to determine
	 * which meta-register is to be accessed ...
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	switch (offset) {
	case LOMBUS_FAULT_REG:
		/*
		 * This meta-register provides a code for the most
		 * recent virtual register access fault, if any.
		 */
		return (HANDLE_FAULT(hdlp));

	case LOMBUS_PROBE_REG:
		/*
		 * Reading this meta-register clears any existing fault
		 * (at the virtual, not the hardware access layer), then
		 * runs a NOP command and returns the fault code from that.
		 */
		HANDLE_FAULT(hdlp) = 0;
		(void) bscbus_cmd(hdlp, 0, 0, BSCBUS_CMD_NOP);
		return (HANDLE_FAULT(hdlp));

	case LOMBUS_ASYNC_REG:
		/*
		 * Obsolescent - but still supported for backwards
		 * compatibility.  This is an alias for the newer
		 * LOMBUS_EVENT_REG, but doesn't require a separate
		 * "reg" entry and ddi_regs_map_setup() call.
		 *
		 * It returns the value of the asynchronous-event-pending
		 * flag as passed back by the BSC at the end of the last
		 * completed command.
		 */
		csp = HANDLE_PRIVATE(hdlp);
		return (csp->async);

	default:
		/*
		 * Invalid access - flag a fault and return a dummy value
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
		return (DUMMY_VALUE);
	}
}

static void
bscbus_meta_put32(HANDLE_TYPE *hdlp, uint32_t *addr, uint32_t val)
{
	ptrdiff_t offset;

	/*
	 * Derive the offset that the caller has added to the base
	 * address originally returned, and use it to determine
	 * which meta-register is to be accessed ...
	 */
	offset = ADDR_TO_OFFSET(addr, hdlp);
	switch (offset) {
	case LOMBUS_FAULT_REG:
		/*
		 * This meta-register contains a code for the most
		 * recent virtual register access fault, if any.
		 * It can be cleared simply by writing 0 to it.
		 */
		HANDLE_FAULT(hdlp) = val;
		return;

	case LOMBUS_PROBE_REG:
		/*
		 * Writing this meta-register clears any existing fault
		 * (at the virtual, not the hardware acess layer), then
		 * runs a NOP command.  The caller can check the fault
		 * code later if required.
		 */
		HANDLE_FAULT(hdlp) = 0;
		(void) bscbus_cmd(hdlp, 0, 0, BSCBUS_CMD_NOP);
		return;

	default:
		/*
		 * Invalid access - flag a fault
		 */
		HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
		return;
	}
}

static void
bscbus_meta_rep_get32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
	uint32_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		*host_addr++ = bscbus_meta_get32(hdlp, dev_addr);
}

static void
bscbus_meta_rep_put32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
	uint32_t *dev_addr, size_t repcount, uint_t flags)
{
	size_t inc;

	inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
	for (; repcount--; dev_addr += inc)
		bscbus_meta_put32(hdlp, dev_addr, *host_addr++);
}


/*
 * Finally, some dummy functions for all unsupported access
 * space/size/mode combinations ...
 */
static uint8_t
bscbus_no_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
	_NOTE(ARGUNUSED(addr))

	/*
	 * Invalid access - flag a fault and return a dummy value
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
	return (DUMMY_VALUE);
}

static void
bscbus_no_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
	_NOTE(ARGUNUSED(addr, val))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
		uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
	uint8_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static uint16_t
bscbus_no_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
	_NOTE(ARGUNUSED(addr))

	/*
	 * Invalid access - flag a fault and return a dummy value
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
	return (DUMMY_VALUE);
}

static void
bscbus_no_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
	_NOTE(ARGUNUSED(addr, val))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
		uint16_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
	uint16_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static uint64_t
bscbus_no_get64(HANDLE_TYPE *hdlp, uint64_t *addr)
{
	_NOTE(ARGUNUSED(addr))

	/*
	 * Invalid access - flag a fault and return a dummy value
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
	return (DUMMY_VALUE);
}

static void
bscbus_no_put64(HANDLE_TYPE *hdlp, uint64_t *addr, uint64_t val)
{
	_NOTE(ARGUNUSED(addr, val))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_get64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
	uint64_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
bscbus_no_rep_put64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
	uint64_t *dev_addr, size_t repcount, uint_t flags)
{
	_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

	/*
	 * Invalid access - flag a fault
	 */
	HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static int
bscbus_acc_fault_check(HANDLE_TYPE *hdlp)
{
	return (HANDLE_FAULT(hdlp) != 0);
}

/*
 * Hardware setup - ensure that there are no pending transactions and
 * hence no pending interrupts. We do this be ensuring that the BSC is
 * not reporting a busy condition and that it does not have any data
 * pending in its output buffer.
 * This is important because if we have pending interrupts at attach
 * time Solaris will hang due to bugs in ddi_get_iblock_cookie.
 */
static void
bscbus_hw_reset(struct bscbus_channel_state *csp)
{
	int64_t timeout;
	uint8_t status;

	if (csp->map_count == 0) {
		/* No-one using this instance - no need to reset hardware */
		return;
	}

	bscbus_trace(csp, 'R', "bscbus_hw_reset",
	    "resetting channel %d", csp->chno);

	status = bscbus_get_reg(csp, H8_STR);
	if (status & H8_STR_BUSY) {
		/*
		 * Give the h8 time to complete a reply.
		 * In practice we should never worry about this
		 * because whenever we get here it will have been
		 * long enough for the h8 to complete a reply
		 */
		bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0);
		bscbus_trace(csp, 'R', "bscbus_hw_reset",
		    "h8 reporting status (%x) busy - waiting", status);
		if (ddi_in_panic()) {
			drv_usecwait(BSCBUS_HWRESET_POLL/1000);
		} else {
			delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000));
		}
	}
	/* Reply should be completed by now. Try to clear busy status */
	status = bscbus_get_reg(csp, H8_STR);
	if (status & (H8_STR_BUSY | H8_STR_OBF)) {
		bscbus_trace(csp, 'R', "bscbus_hw_reset",
		    "clearing busy status for channel %d", csp->chno);

		for (timeout = BSCBUS_HWRESET_TIMEOUT;
		    (timeout > 0);
		    timeout -= BSCBUS_HWRESET_POLL) {
			if (status & H8_STR_OBF) {
				(void) bscbus_get_reg(csp, H8_ODR);
				if (!(status & H8_STR_BUSY)) {
					/* We are done */
					break;
				}
			}
			if (ddi_in_panic()) {
				drv_usecwait(BSCBUS_HWRESET_POLL/1000);
			} else {
				delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000));
			}
			status = bscbus_get_reg(csp, H8_STR);
		}
		if (timeout <= 0) {
			cmn_err(CE_WARN, "bscbus_hw_reset: timed out "
			    "clearing busy status");
		}
	}
	/*
	 * We read ODR just in case there is a pending interrupt with
	 * no data. This is potentially dangerous because we could get
	 * out of sync due to race conditions BUT at this point the
	 * channel should be idle so it is safe.
	 */
	(void) bscbus_get_reg(csp, H8_ODR);
}

/*
 * Higher-level setup & teardown
 */

static void
bscbus_offline(struct bscbus_state *ssp)
{
	if (ssp->h8_handle != NULL)
		ddi_regs_map_free(&ssp->h8_handle);
	ssp->h8_handle = NULL;
	ssp->h8_regs = NULL;
}

static int
bscbus_online(struct bscbus_state *ssp)
{
	ddi_acc_handle_t h;
	caddr_t p;
	int nregs;
	int err;

	ssp->h8_handle = NULL;
	ssp->h8_regs = (void *)NULL;
	ssp->per_channel_regs = B_FALSE;

	if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS)
		nregs = 0;

	switch (nregs) {
	case 1:
		/*
		 *  regset 0 represents the H8 interface registers
		 */
		err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0,
		    bscbus_dev_acc_attr, &h);
		if (err != DDI_SUCCESS)
			return (EIO);

		ssp->h8_handle = h;
		ssp->h8_regs = (void *)p;
		break;

	case 0:
		/*
		 *  If no registers are defined, succeed vacuously;
		 *  commands will be accepted, but we fake the accesses.
		 */
		break;

	default:
		/*
		 * Remember that we are using the new register scheme.
		 * reg set 0 is chan 0
		 * reg set 1 is chan 1 ...
		 * Interrupts are specified in that order but later
		 * channels may not have interrupts.
		 * We map the regs later on a per channel basis.
		 */
		ssp->per_channel_regs = B_TRUE;
		break;
	}
	return (0);
}

static int
bscbus_claim_channel(struct bscbus_channel_state *csp, boolean_t map_dog)
{
	int err;

	mutex_enter(csp->ssp->ch_mutex);
	csp->map_count++;
	bscbus_trace(csp, 'C', "bscbus_claim_channel",
	    "claim channel for channel %d, count %d",
	    csp->chno, csp->map_count);

	if (csp->map_count == 1) {
		/* No-one is using this channel - initialise it */
		bscbus_trace(csp, 'C', "bscbus_claim_channel",
		    "initialise channel %d, count %d",
		    csp->chno, csp->map_count);

		mutex_init(csp->dog_mutex, NULL, MUTEX_DRIVER,
		    (void *)(uintptr_t)__ipltospl(SPL7 - 1));
		csp->map_dog = map_dog;
		csp->interrupt_failed = B_FALSE;
		csp->cmdstate = BSCBUS_CMDSTATE_IDLE;
		csp->pat_retry_count = 0;
		csp->pat_fail_count = 0;

		/* Map appropriate register set for this channel */
		if (csp->ssp->per_channel_regs == B_TRUE) {
			ddi_acc_handle_t h;
			caddr_t p;

			err = ddi_regs_map_setup(csp->ssp->dip, csp->chno,
			    &p, 0, 0, bscbus_dev_acc_attr, &h);

			if (err != DDI_SUCCESS) {
				goto failed1;
			}

			csp->ch_handle = h;
			csp->ch_regs = (void *)p;

			bscbus_trace(csp, 'C', "bscbus_claim_channel",
			    "mapped chno=%d ch_handle=%d ch_regs=%p",
			    csp->chno, h, p);
		} else {
			/*
			 * if using the old reg property scheme use the
			 * common mapping.
			 */
			csp->ch_handle = csp->ssp->h8_handle;
			csp->ch_regs =
			    csp->ssp->h8_regs +
			    BSCBUS_CHANNEL_TO_OFFSET(csp->chno);
		}

		/* Ensure no interrupts pending prior to getting iblk cookie */
		bscbus_hw_reset(csp);

		if (csp->map_dog == 1) {
			/*
			 * we don't want lo_mutex to be initialised
			 * with an iblock cookie if we are the wdog,
			 * because we don't use interrupts.
			 */
			mutex_init(csp->lo_mutex, NULL,
			    MUTEX_DRIVER, NULL);
			cv_init(csp->lo_cv, NULL,
			    CV_DRIVER, NULL);
			csp->unclaimed_count = 0;
		} else {
			int ninterrupts;

			/*
			 * check that there is an interrupt for this
			 * this channel. If we fail to setup interrupts we
			 * must unmap the registers and fail.
			 */
			err = ddi_dev_nintrs(csp->ssp->dip, &ninterrupts);

			if (err != DDI_SUCCESS) {
				ninterrupts = 0;
			}

			if (ninterrupts <= csp->chno) {
				cmn_err(CE_WARN,
				    "no interrupt available for "
				    "bscbus channel %d", csp->chno);
				goto failed2;
			}

			if (ddi_intr_hilevel(csp->ssp->dip, csp->chno) != 0) {
				cmn_err(CE_WARN,
				    "bscbus interrupts are high "
				    "level - channel not usable.");
				goto failed2;
			} else {
				err = ddi_get_iblock_cookie(csp->ssp->dip,
				    csp->chno, &csp->lo_iblk);
				if (err != DDI_SUCCESS) {
					goto failed2;
				}

				mutex_init(csp->lo_mutex, NULL,
				    MUTEX_DRIVER, csp->lo_iblk);
				cv_init(csp->lo_cv, NULL,
				    CV_DRIVER, NULL);
				csp->unclaimed_count = 0;

				err = ddi_add_intr(csp->ssp->dip, csp->chno,
				    &csp->lo_iblk, NULL,
				    bscbus_hwintr, (caddr_t)csp);
				if (err != DDI_SUCCESS) {
					cv_destroy(csp->lo_cv);
					mutex_destroy(csp->lo_mutex);
					goto failed2;
				}
			}
		}
		/*
		 * The channel is now live and may
		 * receive interrupts
		 */
	} else if (csp->map_dog != map_dog) {
		bscbus_trace(csp, 'C', "bscbus_claim_channel",
		    "request conflicts with previous mapping. old %x, new %x.",
		    csp->map_dog, map_dog);
		goto failed1;
	}
	mutex_exit(csp->ssp->ch_mutex);
	return (1);

failed2:
	/* unmap regs for failed channel */
	if (csp->ssp->per_channel_regs == B_TRUE) {
		ddi_regs_map_free(&csp->ch_handle);
	}
	csp->ch_handle = NULL;
	csp->ch_regs = (void *)NULL;
failed1:
	csp->map_count--;
	mutex_exit(csp->ssp->ch_mutex);
	return (0);
}

static void
bscbus_release_channel(struct bscbus_channel_state *csp)
{
	mutex_enter(csp->ssp->ch_mutex);
	if (csp->map_count == 1) {
		/* No-one is now using this channel - shutdown channel */
		bscbus_trace(csp, 'C', "bscbus_release_channel",
		    "shutdown channel %d, count %d",
		    csp->chno, csp->map_count);

		if (csp->map_dog == 0) {
			ASSERT(!ddi_intr_hilevel(csp->ssp->dip, csp->chno));
			ddi_remove_intr(csp->ssp->dip, csp->chno, csp->lo_iblk);
		}
		cv_destroy(csp->lo_cv);
		mutex_destroy(csp->lo_mutex);
		mutex_destroy(csp->dog_mutex);
		bscbus_hw_reset(csp);

		/* unmap registers if using the new register scheme */
		if (csp->ssp->per_channel_regs == B_TRUE) {
			ddi_regs_map_free(&csp->ch_handle);
		}
		csp->ch_handle = NULL;
		csp->ch_regs = (void *)NULL;
	}
	csp->map_count--;
	bscbus_trace(csp, 'C', "bscbus_release_channel",
	    "release channel %d, count %d",
	    csp->chno, csp->map_count);
	mutex_exit(csp->ssp->ch_mutex);
}


/*
 *  Nexus routines
 */

#if	defined(NDI_ACC_HDL_V2)

static const ndi_acc_fns_t bscbus_vreg_acc_fns = {
	NDI_ACC_FNS_CURRENT,
	NDI_ACC_FNS_V1,

	bscbus_vreg_get8,
	bscbus_vreg_put8,
	bscbus_vreg_rep_get8,
	bscbus_vreg_rep_put8,

	bscbus_no_get16,
	bscbus_no_put16,
	bscbus_no_rep_get16,
	bscbus_no_rep_put16,

	bscbus_meta_get32,
	bscbus_meta_put32,
	bscbus_meta_rep_get32,
	bscbus_meta_rep_put32,

	bscbus_no_get64,
	bscbus_no_put64,
	bscbus_no_rep_get64,
	bscbus_no_rep_put64,

	bscbus_acc_fault_check
};

static const ndi_acc_fns_t bscbus_pat_acc_fns = {
	NDI_ACC_FNS_CURRENT,
	NDI_ACC_FNS_V1,

	bscbus_pat_get8,
	bscbus_pat_put8,
	bscbus_pat_rep_get8,
	bscbus_pat_rep_put8,

	bscbus_no_get16,
	bscbus_no_put16,
	bscbus_no_rep_get16,
	bscbus_no_rep_put16,

	bscbus_meta_get32,
	bscbus_meta_put32,
	bscbus_meta_rep_get32,
	bscbus_meta_rep_put32,

	bscbus_no_get64,
	bscbus_no_put64,
	bscbus_no_rep_get64,
	bscbus_no_rep_put64,

	bscbus_acc_fault_check
};

static const ndi_acc_fns_t bscbus_event_acc_fns = {
	NDI_ACC_FNS_CURRENT,
	NDI_ACC_FNS_V1,

	bscbus_no_get8,
	bscbus_no_put8,
	bscbus_no_rep_get8,
	bscbus_no_rep_put8,

	bscbus_event_get16,
	bscbus_event_put16,
	bscbus_event_rep_get16,
	bscbus_event_rep_put16,

	bscbus_meta_get32,
	bscbus_meta_put32,
	bscbus_meta_rep_get32,
	bscbus_meta_rep_put32,

	bscbus_no_get64,
	bscbus_no_put64,
	bscbus_no_rep_get64,
	bscbus_no_rep_put64,

	bscbus_acc_fault_check
};

static int
bscbus_map_handle(struct bscbus_channel_state *csp, ddi_map_op_t op,
	int space, caddr_t vaddr, off_t len,
	ndi_acc_handle_t *hdlp, caddr_t *addrp)
{
	switch (op) {
	default:
		return (DDI_ME_UNIMPLEMENTED);

	case DDI_MO_MAP_LOCKED:
		if (bscbus_claim_channel(csp,
		    (space == LOMBUS_PAT_SPACE)) == 0) {
			return (DDI_ME_GENERIC);
		}

		switch (space) {
		default:
			return (DDI_ME_REGSPEC_RANGE);

		case LOMBUS_VREG_SPACE:
			ndi_set_acc_fns(hdlp, &bscbus_vreg_acc_fns);
			break;

		case LOMBUS_PAT_SPACE:
			ndi_set_acc_fns(hdlp, &bscbus_pat_acc_fns);
			break;

		case LOMBUS_EVENT_SPACE:
			ndi_set_acc_fns(hdlp, &bscbus_event_acc_fns);
			break;
		}
		hdlp->ah_addr = *addrp = vaddr;
		hdlp->ah_len = len;
		hdlp->ah_bus_private = csp;
		return (DDI_SUCCESS);

	case DDI_MO_UNMAP:
		*addrp = NULL;
		hdlp->ah_bus_private = NULL;
		bscbus_release_channel(csp);
		return (DDI_SUCCESS);
	}
}

#else

static int
bscbus_map_handle(struct bscbus_channel_state *csp, ddi_map_op_t op,
	int space, caddr_t vaddr, off_t len,
	ddi_acc_hdl_t *hdlp, caddr_t *addrp)
{
	ddi_acc_impl_t *aip = hdlp->ah_platform_private;

	switch (op) {
	default:
		return (DDI_ME_UNIMPLEMENTED);

	case DDI_MO_MAP_LOCKED:
		if (bscbus_claim_channel(csp,
		    (space == LOMBUS_PAT_SPACE)) == 0) {
			return (DDI_ME_GENERIC);
		}

		switch (space) {
		default:
			return (DDI_ME_REGSPEC_RANGE);

		case LOMBUS_VREG_SPACE:
			aip->ahi_get8 = bscbus_vreg_get8;
			aip->ahi_put8 = bscbus_vreg_put8;
			aip->ahi_rep_get8 = bscbus_vreg_rep_get8;
			aip->ahi_rep_put8 = bscbus_vreg_rep_put8;

			aip->ahi_get16 = bscbus_no_get16;
			aip->ahi_put16 = bscbus_no_put16;
			aip->ahi_rep_get16 = bscbus_no_rep_get16;
			aip->ahi_rep_put16 = bscbus_no_rep_put16;

			aip->ahi_get32 = bscbus_meta_get32;
			aip->ahi_put32 = bscbus_meta_put32;
			aip->ahi_rep_get32 = bscbus_meta_rep_get32;
			aip->ahi_rep_put32 = bscbus_meta_rep_put32;

			aip->ahi_get64 = bscbus_no_get64;
			aip->ahi_put64 = bscbus_no_put64;
			aip->ahi_rep_get64 = bscbus_no_rep_get64;
			aip->ahi_rep_put64 = bscbus_no_rep_put64;

			aip->ahi_fault_check = bscbus_acc_fault_check;
			break;

		case LOMBUS_PAT_SPACE:
			aip->ahi_get8 = bscbus_pat_get8;
			aip->ahi_put8 = bscbus_pat_put8;
			aip->ahi_rep_get8 = bscbus_pat_rep_get8;
			aip->ahi_rep_put8 = bscbus_pat_rep_put8;

			aip->ahi_get16 = bscbus_no_get16;
			aip->ahi_put16 = bscbus_no_put16;
			aip->ahi_rep_get16 = bscbus_no_rep_get16;
			aip->ahi_rep_put16 = bscbus_no_rep_put16;

			aip->ahi_get32 = bscbus_meta_get32;
			aip->ahi_put32 = bscbus_meta_put32;
			aip->ahi_rep_get32 = bscbus_meta_rep_get32;
			aip->ahi_rep_put32 = bscbus_meta_rep_put32;

			aip->ahi_get64 = bscbus_no_get64;
			aip->ahi_put64 = bscbus_no_put64;
			aip->ahi_rep_get64 = bscbus_no_rep_get64;
			aip->ahi_rep_put64 = bscbus_no_rep_put64;

			aip->ahi_fault_check = bscbus_acc_fault_check;
			break;

		case LOMBUS_EVENT_SPACE:
			aip->ahi_get8 = bscbus_no_get8;
			aip->ahi_put8 = bscbus_no_put8;
			aip->ahi_rep_get8 = bscbus_no_rep_get8;
			aip->ahi_rep_put8 = bscbus_no_rep_put8;

			aip->ahi_get16 = bscbus_event_get16;
			aip->ahi_put16 = bscbus_event_put16;
			aip->ahi_rep_get16 = bscbus_event_rep_get16;
			aip->ahi_rep_put16 = bscbus_event_rep_put16;

			aip->ahi_get32 = bscbus_meta_get32;
			aip->ahi_put32 = bscbus_meta_put32;
			aip->ahi_rep_get32 = bscbus_meta_rep_get32;
			aip->ahi_rep_put32 = bscbus_meta_rep_put32;

			aip->ahi_get64 = bscbus_no_get64;
			aip->ahi_put64 = bscbus_no_put64;
			aip->ahi_rep_get64 = bscbus_no_rep_get64;
			aip->ahi_rep_put64 = bscbus_no_rep_put64;

			aip->ahi_fault_check = bscbus_acc_fault_check;
			break;
		}
		hdlp->ah_addr = *addrp = vaddr;
		hdlp->ah_len = len;
		hdlp->ah_bus_private = csp;
		return (DDI_SUCCESS);

	case DDI_MO_UNMAP:
		*addrp = NULL;
		hdlp->ah_bus_private = NULL;
		bscbus_release_channel(csp);
		return (DDI_SUCCESS);
	}
}

#endif	/* NDI_ACC_HDL_V2 */

static int
bscbus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
	off_t off, off_t len, caddr_t *addrp)
{
	struct bscbus_child_info *lcip;
	struct bscbus_state *ssp;
	lombus_regspec_t *rsp;

	if ((ssp = bscbus_getstate(dip, -1, "bscbus_map")) == NULL)
		return (DDI_FAILURE);	/* this "can't happen" */

	/*
	 * Validate mapping request ...
	 */

	if (mp->map_flags != DDI_MF_KERNEL_MAPPING)
		return (DDI_ME_UNSUPPORTED);
	if (mp->map_handlep == NULL)
		return (DDI_ME_UNSUPPORTED);
	if (mp->map_type != DDI_MT_RNUMBER)
		return (DDI_ME_UNIMPLEMENTED);
	if ((lcip = ddi_get_parent_data(rdip)) == NULL)
		return (DDI_ME_INVAL);
	if ((rsp = lcip->rsp) == NULL)
		return (DDI_ME_INVAL);
	if (mp->map_obj.rnumber >= lcip->nregs)
		return (DDI_ME_RNUMBER_RANGE);
	rsp += mp->map_obj.rnumber;
	if (off < 0 || off >= rsp->lombus_size)
		return (DDI_ME_INVAL);
	if (len == 0)
		len = rsp->lombus_size-off;
	if (len < 0)
		return (DDI_ME_INVAL);
	if (off+len < 0 || off+len > rsp->lombus_size)
		return (DDI_ME_INVAL);

	return (bscbus_map_handle(
	    &ssp->channel[LOMBUS_SPACE_TO_CHANNEL(rsp->lombus_space)],
	    mp->map_op, LOMBUS_SPACE_TO_REGSET(rsp->lombus_space),
	    VREG_TO_ADDR(rsp->lombus_base+off), len, mp->map_handlep, addrp));
}


static int
bscbus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
	void *arg, void *result)
{
	struct bscbus_child_info *lcip;
	lombus_regspec_t *rsp;
	dev_info_t *cdip;
	char addr[32];
	uint_t nregs;
	uint_t rnum;
	int *regs;
	int limit;
	int err;
	int i;

	if (bscbus_getstate(dip, -1, "bscbus_ctlops") == NULL)
		return (DDI_FAILURE);	/* this "can't happen" */

	switch (op) {
	default:
		break;

	case DDI_CTLOPS_INITCHILD:
		/*
		 * First, look up and validate the "reg" property.
		 *
		 * It must be a non-empty integer array containing a set
		 * of triples.  Once we've verified that, we can treat it
		 * as an array of type lombus_regspec_t[], which defines
		 * the meaning of the elements of each triple:
		 * +  the first element of each triple must be a valid space
		 * +  the second and third elements (base, size) of each
		 *	triple must define a valid subrange of that space
		 * If it passes all the tests, we save it away for future
		 * reference in the child's parent-private-data field.
		 */
		cdip = arg;
		err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS, "reg", &regs, &nregs);
		if (err != DDI_PROP_SUCCESS)
			return (DDI_FAILURE);

		err = (nregs <= 0 || (nregs % LOMBUS_REGSPEC_SIZE) != 0);
		nregs /= LOMBUS_REGSPEC_SIZE;
		rsp = (lombus_regspec_t *)regs;
		for (i = 0; i < nregs && !err; ++i) {
			switch (LOMBUS_SPACE_TO_REGSET(rsp[i].lombus_space)) {
			default:
				limit = 0;
				err = 1;
				cmn_err(CE_WARN,
				    "child(%p): unknown reg space %d",
				    (void *)cdip, rsp[i].lombus_space);
				break;

			case LOMBUS_VREG_SPACE:
				limit = LOMBUS_MAX_REG+1;
				break;

			case LOMBUS_PAT_SPACE:
				limit = LOMBUS_PAT_REG+1;
				break;

			case LOMBUS_EVENT_SPACE:
				limit = LOMBUS_EVENT_REG+1;
				break;
			}

			err |= (rsp[i].lombus_base < 0);
			err |= (rsp[i].lombus_base >= limit);

			if (rsp[i].lombus_size == 0)
				rsp[i].lombus_size = limit-rsp[i].lombus_base;

			err |= (rsp[i].lombus_size < 0);
			err |= (rsp[i].lombus_base+rsp[i].lombus_size < 0);
			err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);

			err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);

		}

		if (err) {
			ddi_prop_free(regs);
			return (DDI_FAILURE);
		}

		lcip = kmem_zalloc(sizeof (*lcip), KM_SLEEP);
		lcip->nregs = nregs;
		lcip->rsp = rsp;
		ddi_set_parent_data(cdip, lcip);

		(void) snprintf(addr, sizeof (addr),
		    "%x,%x", rsp[0].lombus_space, rsp[0].lombus_base);
		ddi_set_name_addr(cdip, addr);

		return (DDI_SUCCESS);

	case DDI_CTLOPS_UNINITCHILD:
		cdip = arg;
		ddi_set_name_addr(cdip, NULL);
		lcip = ddi_get_parent_data(cdip);
		ddi_set_parent_data(cdip, NULL);
		ddi_prop_free(lcip->rsp);
		kmem_free(lcip, sizeof (*lcip));
		return (DDI_SUCCESS);

	case DDI_CTLOPS_REPORTDEV:
		if (rdip == NULL)
			return (DDI_FAILURE);

		cmn_err(CE_CONT, "?BSC device: %s@%s, %s#%d\n",
		    ddi_node_name(rdip), ddi_get_name_addr(rdip),
		    ddi_driver_name(dip), ddi_get_instance(dip));

		return (DDI_SUCCESS);

	case DDI_CTLOPS_REGSIZE:
		if ((lcip = ddi_get_parent_data(rdip)) == NULL)
			return (DDI_FAILURE);
		if ((rnum = *(uint_t *)arg) >= lcip->nregs)
			return (DDI_FAILURE);
		*(off_t *)result = lcip->rsp[rnum].lombus_size;
		return (DDI_SUCCESS);

	case DDI_CTLOPS_NREGS:
		if ((lcip = ddi_get_parent_data(rdip)) == NULL)
			return (DDI_FAILURE);
		*(int *)result = lcip->nregs;
		return (DDI_SUCCESS);
	}

	return (ddi_ctlops(dip, rdip, op, arg, result));
}


/*
 * This nexus does not support passing interrupts to leaf drivers, so
 * all the intrspec-related operations just fail as cleanly as possible.
 */

/*ARGSUSED*/
static int
bscbus_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
#if defined(__sparc)
	return (i_ddi_intr_ops(dip, rdip, op, hdlp, result));
#else
	_NOTE(ARGUNUSED(dip, rdip, op, hdlp, result))
	return (DDI_FAILURE);
#endif
}

/*
 *  Clean up on detach or failure of attach
 */
static int
bscbus_unattach(struct bscbus_state *ssp, int instance)
{
	int chno;

	if (ssp != NULL) {
		for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
			ASSERT(ssp->channel[chno].map_count == 0);
		}
		bscbus_offline(ssp);
		ddi_set_driver_private(ssp->dip, NULL);
		mutex_destroy(ssp->ch_mutex);
	}
#ifdef BSCBUS_LOGSTATUS
	if (ssp->cmd_log_size != 0) {
		kmem_free(ssp->cmd_log,
		    ssp->cmd_log_size * sizeof (bsc_cmd_log_t));
	}
#endif /* BSCBUS_LOGSTATUS */


	ddi_soft_state_free(bscbus_statep, instance);
	return (DDI_FAILURE);
}

/*
 *  Autoconfiguration routines
 */

static int
bscbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	struct bscbus_state *ssp = NULL;
	int chno;
	int instance;
	int err;

	switch (cmd) {
	default:
		return (DDI_FAILURE);

	case DDI_ATTACH:
		break;
	}

	/*
	 *  Allocate the soft-state structure
	 */
	instance = ddi_get_instance(dip);
	if (ddi_soft_state_zalloc(bscbus_statep, instance) != DDI_SUCCESS)
		return (DDI_FAILURE);
	if ((ssp = bscbus_getstate(dip, instance, "bscbus_attach")) == NULL)
		return (bscbus_unattach(ssp, instance));
	ddi_set_driver_private(dip, ssp);

	/*
	 *  Initialise devinfo-related fields
	 */
	ssp->dip = dip;
	ssp->majornum = ddi_driver_major(dip);
	ssp->instance = instance;

	/*
	 *  Set various options from .conf properties
	 */
	ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "debug", 0);

	mutex_init(ssp->ch_mutex, NULL, MUTEX_DRIVER, NULL);

#ifdef BSCBUS_LOGSTATUS
	ssp->cmd_log_size = bscbus_cmd_log_size;
	if (ssp->cmd_log_size != 0) {
		ssp->cmd_log_idx = 0;
		ssp->cmd_log = kmem_zalloc(ssp->cmd_log_size *
		    sizeof (bsc_cmd_log_t), KM_SLEEP);
	}
#endif /* BSCBUS_LOGSTATUS */

	/*
	 *  Online the hardware ...
	 */
	err = bscbus_online(ssp);
	if (err != 0)
		return (bscbus_unattach(ssp, instance));

	for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
		struct bscbus_channel_state *csp = &ssp->channel[chno];

		/*
		 * Initialise state
		 * The hardware/interrupts are setup at map time to
		 * avoid claiming hardware that OBP is using
		 */
		csp->ssp = ssp;
		csp->chno = chno;
		csp->map_count = 0;
		csp->map_dog = B_FALSE;
	}

	/*
	 *  All done, report success
	 */
	ddi_report_dev(dip);
	return (DDI_SUCCESS);
}

static int
bscbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	struct bscbus_state *ssp;
	int instance;

	switch (cmd) {
	default:
		return (DDI_FAILURE);

	case DDI_DETACH:
		break;
	}

	instance = ddi_get_instance(dip);
	if ((ssp = bscbus_getstate(dip, instance, "bscbus_detach")) == NULL)
		return (DDI_FAILURE);	/* this "can't happen" */

	(void) bscbus_unattach(ssp, instance);
	return (DDI_SUCCESS);
}

static int
bscbus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
	struct bscbus_state *ssp;
	int chno;

	_NOTE(ARGUNUSED(cmd))

	if ((ssp = bscbus_getstate(dip, -1, "bscbus_reset")) == NULL)
		return (DDI_FAILURE);

	for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
		bscbus_hw_reset(&ssp->channel[chno]);
	}
	return (DDI_SUCCESS);
}


/*
 * System interface structures
 */

static struct cb_ops bscbus_cb_ops =
{
	nodev,			/* b/c open	*/
	nodev,			/* b/c close	*/
	nodev,			/* b   strategy	*/
	nodev,			/* b   print	*/
	nodev,			/* b   dump 	*/
	nodev,			/* c   read	*/
	nodev,			/* c   write	*/
	nodev,			/* c   ioctl	*/
	nodev,			/* c   devmap	*/
	nodev,			/* c   mmap	*/
	nodev,			/* c   segmap	*/
	nochpoll,		/* c   poll	*/
	ddi_prop_op,		/* b/c prop_op	*/
	NULL,			/* c   streamtab */
	D_MP | D_NEW		/* b/c flags	*/
};

static struct bus_ops bscbus_bus_ops =
{
	BUSO_REV,			/* revision		*/
	bscbus_map,			/* bus_map		*/
	0,				/* get_intrspec		*/
	0,				/* add_intrspec		*/
	0,				/* remove_intrspec	*/
	i_ddi_map_fault,		/* map_fault		*/
	ddi_no_dma_map,			/* dma_map		*/
	ddi_no_dma_allochdl,		/* allocate DMA handle	*/
	ddi_no_dma_freehdl,		/* free DMA handle	*/
	ddi_no_dma_bindhdl,		/* bind DMA handle	*/
	ddi_no_dma_unbindhdl,		/* unbind DMA handle	*/
	ddi_no_dma_flush,		/* flush DMA		*/
	ddi_no_dma_win,			/* move DMA window	*/
	ddi_no_dma_mctl,		/* generic DMA control	*/
	bscbus_ctlops,			/* generic control	*/
	ddi_bus_prop_op,		/* prop_op		*/
	ndi_busop_get_eventcookie,	/* get_eventcookie	*/
	ndi_busop_add_eventcall,	/* add_eventcall	*/
	ndi_busop_remove_eventcall,	/* remove_eventcall	*/
	ndi_post_event,			/* post_event		*/
	0,				/* interrupt control	*/
	0,				/* bus_config		*/
	0,				/* bus_unconfig		*/
	0,				/* bus_fm_init		*/
	0,				/* bus_fm_fini		*/
	0,				/* bus_fm_access_enter	*/
	0,				/* bus_fm_access_exit	*/
	0,				/* bus_power		*/
	bscbus_intr_op			/* bus_intr_op		*/
};

static struct dev_ops bscbus_dev_ops =
{
	DEVO_REV,
	0,				/* refcount		*/
	ddi_no_info,			/* getinfo		*/
	nulldev,			/* identify		*/
	nulldev,			/* probe		*/
	bscbus_attach,			/* attach		*/
	bscbus_detach,			/* detach		*/
	bscbus_reset,			/* reset		*/
	&bscbus_cb_ops,			/* driver operations	*/
	&bscbus_bus_ops,		/* bus operations	*/
	NULL,				/* power		*/
	ddi_quiesce_not_needed,			/* quiesce		*/
};

static struct modldrv modldrv =
{
	&mod_driverops,
	"bscbus driver",
	&bscbus_dev_ops
};

static struct modlinkage modlinkage =
{
	MODREV_1,
	{
		&modldrv,
		NULL
	}
};


/*
 *  Dynamic loader interface code
 */

int
_init(void)
{
	int err;

	err = ddi_soft_state_init(&bscbus_statep,
	    sizeof (struct bscbus_state), 0);
	if (err == DDI_SUCCESS)
		if ((err = mod_install(&modlinkage)) != DDI_SUCCESS) {
			ddi_soft_state_fini(&bscbus_statep);
		}

	return (err);
}

int
_info(struct modinfo *mip)
{
	return (mod_info(&modlinkage, mip));
}

int
_fini(void)
{
	int err;

	if ((err = mod_remove(&modlinkage)) == DDI_SUCCESS) {
		ddi_soft_state_fini(&bscbus_statep);
		bscbus_major = NOMAJOR;
	}

	return (err);
}

#ifdef BSCBUS_LOGSTATUS
void bscbus_cmd_log(struct bscbus_channel_state *csp, bsc_cmd_stamp_t cat,
    uint8_t status, uint8_t data)
{
	int idx;
	bsc_cmd_log_t *logp;
	struct bscbus_state *ssp;

	if ((csp) == NULL)
		return;
	if ((ssp = (csp)->ssp) == NULL)
		return;
	if (ssp->cmd_log_size == 0)
		return;
	if ((bscbus_cmd_log_flags & (1 << cat)) == 0)
		return;
	idx = atomic_inc_32_nv(&ssp->cmd_log_idx);
	logp = &ssp->cmd_log[idx % ssp->cmd_log_size];
	logp->bcl_seq = idx;
	logp->bcl_cat = cat;
	logp->bcl_now = gethrtime();
	logp->bcl_chno = csp->chno;
	logp->bcl_cmdstate = csp->cmdstate;
	logp->bcl_status = status;
	logp->bcl_data = data;
}
#endif /* BSCBUS_LOGSTATUS */