OpenSolaris_b135/uts/sun4u/io/sbbc.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * Starcat PCI SBBC Nexus Driver.
 *
 * This source code's compiled binary runs on both a Starcat System
 * Controller (SSC) and a Starcat Domain.  One of the SBBC hardware
 * registers is read during attach(9e) in order to determine which
 * environment the driver is executing on.
 *
 * On both the SSC and the Domain, this driver provides nexus driver
 * services to its Device Tree children.  Note that the children in
 * each environment are not necessarily the same.
 *
 * This driver allows one concurrent open(2) of its associated device
 * (/dev/sbbc0).  The client uses the file descriptor to issue
 * ioctl(2)'s in order to read and write from the 2MB (PCI) space
 * reserved for "SBBC Internal Registers".  Among other things,
 * these registers consist of command/control/status registers for
 * devices such as Console Bus, I2C, EPLD, IOSRAM, and JTAG.  The 2MB
 * space is very sparse; EINVAL is returned if a reserved or unaligned
 * address is specified in the ioctl(2).
 *
 * Note that the 2MB region reserved for SBBC Internal Registers is
 * a subset of the 128MB of PCI address space addressable by the SBBC
 * ASIC.  Address space outside of the 2MB (such as the 64MB reserved
 * for the Console Bus) is not accessible via this driver.
 *
 * Also, note that the SBBC Internal Registers are only read and
 * written by the SSC; no process on the Domain accesses these
 * registers.  As a result, the registers are unmapped (when running
 * on the Domain) near the end of attach(9e) processing.  This conserves
 * kernel virtual address space resources (as one instance of the driver
 * is created for each Domain-side IO assembly).  (To be complete, only
 * one instance of the driver is created on the SSC).
 */

#include <sys/types.h>

#include <sys/conf.h>		/* req. by dev_ops flags MTSAFE etc. */
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/pci.h>
#include <sys/pci/pci_nexus.h>
#include <sys/autoconf.h>
#include <sys/cmn_err.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/modctl.h>
#include <sys/stat.h>


#include <sys/sbbcreg.h>	/* hw description */
#include <sys/sbbcvar.h>	/* driver description */
#include <sys/sbbcio.h>		/* ioctl description */

#define	getprop(dip, name, addr, intp)		\
		ddi_getlongprop(DDI_DEV_T_ANY, (dip), DDI_PROP_DONTPASS, \
				(name), (caddr_t)(addr), (intp))

/* driver entry point fn definitions */
static int sbbc_open(dev_t *, int, int, cred_t *);
static int sbbc_close(dev_t, int, int, cred_t *);
static int sbbc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

/* configuration entry point fn definitions */
static int sbbc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int sbbc_attach(dev_info_t *, ddi_attach_cmd_t);
static int sbbc_detach(dev_info_t *, ddi_detach_cmd_t);

/* local utility routines */
/*
 * NOTE - sbbc_offset_valid contains detailed address information taken from
 * the Serengeti Architecture Programmer's Reference Manual.  If any
 * changes are made to the SBBC registers, this routine may need to be
 * updated.
 */
static int sbbc_offset_valid(uint32_t offset);

/*
 * function prototypes for bus ops routines:
 */
static int sbbc_busmap(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
	off_t offset, off_t len, caddr_t *addrp);
static int sbbc_ctlops(dev_info_t *dip, dev_info_t *rdip,
	ddi_ctl_enum_t op, void *arg, void *result);

static int sbbc_intr_ops(dev_info_t *dip, dev_info_t *rdip,
	ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip,
	ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip,
	ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_update_intr_state(dev_info_t *dip, dev_info_t *rdip,
	ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);

static int sbbc_apply_range(struct sbbcsoft *sbbc_p, dev_info_t *rdip,
    sbbc_child_regspec_t *child_rp, pci_regspec_t *rp);

static int sbbc_init(struct sbbcsoft *);

static uint_t sbbc_intr_wrapper(caddr_t arg);

static int sbbc_get_ranges(struct sbbcsoft *);
static int sbbc_config4pci(struct sbbcsoft *);
static int sbbc_initchild(dev_info_t *, dev_info_t *, dev_info_t *);
static int sbbc_uninitchild(dev_info_t *, dev_info_t *);
static void sbbc_remove_reg_maps(struct sbbcsoft *);

/* debugging functions */
#ifdef DEBUG
uint32_t sbbc_dbg_flags = 0x0;
static void sbbc_dbg(uint32_t flag, dev_info_t *dip, char *fmt,
	uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5);
static void sbbc_dump_devid(dev_info_t *, struct sbbcsoft *, int instance);
#endif

/*
 * For tracing, allocate space for the trace buffer
 */
#if defined(SBBC_TRACE)
struct sbbctrace sbbctrace_buffer[NSBBCTRACE+1];
struct sbbctrace *sbbctrace_ptr;
int sbbctrace_count;
#endif

/*
 * Local declarations and variables
 */

static void *sbbcsoft_statep;

/* Determines whether driver is executing on System Controller or Domain */
int sbbc_scmode = FALSE;

/*
 * ops stuff.
 */
static struct bus_ops sbbc_bus_ops = {
	BUSO_REV,
	sbbc_busmap,
	0,
	0,
	0,
	NULL, 			/* (*bus_map_fault)() */
	ddi_no_dma_map,
	ddi_no_dma_allochdl,
	ddi_no_dma_freehdl, 	/* (*bus_dma_freehdl)() */
	ddi_no_dma_bindhdl, 	/* (*bus_dma_bindhdl)() */
	ddi_no_dma_unbindhdl, 	/* (*bus_dma_unbindhdl)() */
	ddi_no_dma_flush, 	/* (*bus_dma_flush)() */
	ddi_no_dma_win, 	/* (*bus_dma_win)() */
	ddi_no_dma_mctl, 	/* (*bus_dma_ctl)() */
	sbbc_ctlops,
	ddi_bus_prop_op,
	0,			/* (*bus_get_eventcookie)();	*/
	0,			/* (*bus_add_eventcall)();	*/
	0,			/* (*bus_remove_eventcall)();	*/
	0,			/* (*bus_post_event)();		*/
	0,			/* (*bus_intr_ctl)();	*/
	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)();	*/
	sbbc_intr_ops		/* (*bus_intr_op)();	*/
};

/*
 * cb_ops
 */
static struct cb_ops sbbc_cb_ops = {
	sbbc_open,		/* cb_open */
	sbbc_close,		/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read */
	nodev,			/* cb_write */
	sbbc_ioctl,		/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	NULL,			/* cb_stream */
	(int)(D_NEW | D_MP)	/* cb_flag */
};

/*
 * Declare ops vectors for auto configuration.
 */
struct dev_ops  sbbc_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	sbbc_getinfo,		/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	sbbc_attach,		/* devo_attach */
	sbbc_detach,		/* devo_detach */
	nodev,			/* devo_reset */
	&sbbc_cb_ops,		/* devo_cb_ops */
	&sbbc_bus_ops,		/* devo_bus_ops */
	nulldev,			/* devo_power */
	ddi_quiesce_not_supported,	/* devo_quiesce */
};

/*
 * Loadable module support.
 */
extern struct mod_ops mod_driverops;

static struct modldrv sbbcmodldrv = {
	&mod_driverops,		/* type of module - driver */
	"PCI Sbbc Nexus Driver",
	&sbbc_ops,
};

static struct modlinkage sbbcmodlinkage = {
	MODREV_1,
	&sbbcmodldrv,
	NULL
};

int
_init(void)
{
	int    error;

	if ((error = ddi_soft_state_init(&sbbcsoft_statep,
	    sizeof (struct sbbcsoft), 1)) != 0)
		return (error);
	if ((error = mod_install(&sbbcmodlinkage)) != 0)
		ddi_soft_state_fini(&sbbcsoft_statep);

	return (error);
}

int
_fini(void)
{
	int    error;

	if ((error = mod_remove(&sbbcmodlinkage)) == 0)
		ddi_soft_state_fini(&sbbcsoft_statep);

	return (error);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&sbbcmodlinkage, modinfop));
}

static int
sbbc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int	instance;
	char	name[32];
	struct	sbbcsoft *sbbcsoftp;
	struct ddi_device_acc_attr attr;
	uint32_t sbbc_id_reg;

	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
	attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;

	/* initialize tracing */
	SBBCTRACEINIT();

	SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attaching\n");

	instance = ddi_get_instance(dip);
	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_RESUME:
		if (!(sbbcsoftp =
		    ddi_get_soft_state(sbbcsoft_statep, instance))) {
			cmn_err(CE_WARN, "sbbc_attach:resume: unable "
			    "to acquire sbbcsoftp for instance %d",
			    instance);
			return (DDI_FAILURE);
		}
		mutex_enter(&sbbcsoftp->umutex);
		if (!sbbcsoftp->suspended) {
			mutex_exit(&sbbcsoftp->umutex);
			return (DDI_FAILURE);
		}
		sbbcsoftp->suspended = 0;
		mutex_exit(&sbbcsoftp->umutex);
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	if (ddi_soft_state_zalloc(sbbcsoft_statep, instance) != 0) {
		cmn_err(CE_WARN, "sbbc_attach: Unable to allocate statep "
		    "for instance %d", instance);
		return (DDI_FAILURE);
	}

	sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance);

	if (sbbcsoftp == NULL) {
		cmn_err(CE_WARN, "sbbc_attach: Unable to acquire "
		    "sbbcsoftp for instance %d", instance);
		ddi_soft_state_free(sbbcsoft_statep, instance);
		return (DDI_FAILURE);
	}

	sbbcsoftp->instance = instance;
	sbbcsoftp->dip = dip;
	sbbcsoftp->oflag = FALSE;

	/*
	 * Read our ranges property from OBP to map children space.
	 * And setup the internal structure for a later use when
	 * a child gets initialized.
	 */
	if (sbbc_get_ranges(sbbcsoftp)) {
		cmn_err(CE_WARN, "sbbc_attach: Unable to read sbbc "
		    "ranges from OBP %d", instance);
		ddi_soft_state_free(sbbcsoft_statep, instance);
		return (DDI_FAILURE);
	}

	if (sbbc_config4pci(sbbcsoftp)) {
		cmn_err(CE_WARN, "sbbc_attach: Unable to configure "
		    "sbbc on PCI %d", instance);
		kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);
		ddi_soft_state_free(sbbcsoft_statep, instance);
		return (DDI_FAILURE);
	}

	mutex_init(&sbbcsoftp->umutex, NULL, MUTEX_DRIVER, (void *)NULL);
	mutex_init(&sbbcsoftp->sbbc_intr_mutex, NULL,
	    MUTEX_DRIVER, (void *)NULL);

	/* Map SBBC's Internal Registers */
	if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sbbcsoftp->pci_sbbc_map,
	    offsetof(struct pci_sbbc, sbbc_internal_regs),
	    sizeof (struct sbbc_regs_map), &attr,
	    &sbbcsoftp->pci_sbbc_map_handle) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "(%d):sbbc_attach failed to map sbbc_reg",
		    instance);
		goto failed;
	}

	SBBC_DBG1(SBBC_DBG_ATTACH, dip, "Mapped sbbc at %lx\n",
	    sbbcsoftp->pci_sbbc_map);
#ifdef DEBUG
	sbbc_dump_devid(dip, sbbcsoftp, instance);
#endif
	/*
	 * Read a hardware register to determine if we are executing on
	 * a Starcat System Controller or a Starcat Domain.
	 */
	sbbc_id_reg = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
	    &sbbcsoftp->pci_sbbc_map->device_conf);

	if (sbbc_id_reg & SBBC_SC_MODE) {
		sbbc_scmode = TRUE;
		SBBC_DBG1(SBBC_DBG_ATTACH, dip, "SBBC(%d) nexus running "
		    "in System Controller Mode.\n", instance);

		/* initialize SBBC ASIC */
		if (!sbbc_init(sbbcsoftp)) {
			goto failed;
		}
	} else {
		sbbc_scmode = FALSE;
		SBBC_DBG1(SBBC_DBG_ATTACH, dip, "SBBC(%d) nexus "
		    "running in Domain Mode.\n", instance);

		/* initialize SBBC ASIC before we unmap registers */
		if (!sbbc_init(sbbcsoftp)) {
			goto failed;
		}

		/*
		 * Access to SBBC registers is no longer needed.  Unmap
		 * the registers to conserve kernel virtual address space.
		 */
		SBBC_DBG1(SBBC_DBG_ATTACH, dip, "SBBC(%d): unmap "
		    "SBBC registers\n", instance);
		sbbc_remove_reg_maps(sbbcsoftp);
		sbbcsoftp->pci_sbbc_map = NULL;
	}

	(void) sprintf(name, "sbbc%d", instance);

	if (ddi_create_minor_node(dip, name, S_IFCHR, instance, NULL,
	    NULL) == DDI_FAILURE) {
		ddi_remove_minor_node(dip, NULL);
		goto failed;
	}

	ddi_report_dev(dip);

	SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attached successfully\n");

	return (DDI_SUCCESS);

failed:
	mutex_destroy(&sbbcsoftp->sbbc_intr_mutex);
	mutex_destroy(&sbbcsoftp->umutex);

	sbbc_remove_reg_maps(sbbcsoftp);
	kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);
	ddi_soft_state_free(sbbcsoft_statep, instance);

	SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attach failed\n");

	return (DDI_FAILURE);
}

static int
sbbc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		instance;
	struct sbbcsoft *sbbcsoftp;

	SBBCTRACE(sbbc_detach, 'DETA', dip);

	instance = ddi_get_instance(dip);

	switch (cmd) {
	case DDI_DETACH:
		break;

	case DDI_SUSPEND:
		if (!(sbbcsoftp =
		    ddi_get_soft_state(sbbcsoft_statep, instance))) {
			cmn_err(CE_WARN,
			    "sbbc_detach: unable to get softstate %p",
			    (void *)sbbcsoftp);
			return (DDI_FAILURE);
		}
		mutex_enter(&sbbcsoftp->umutex);
		if (sbbcsoftp->suspended) {
			mutex_exit(&sbbcsoftp->umutex);
			return (DDI_FAILURE);
		}
		sbbcsoftp->suspended = 1;
		mutex_exit(&sbbcsoftp->umutex);
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance))) {
		cmn_err(CE_WARN, "sbbc_detach: unable to get softstate %p",
		    (void *)sbbcsoftp);
		return (DDI_FAILURE);
	}

	ddi_remove_minor_node(dip, NULL);

	mutex_destroy(&sbbcsoftp->sbbc_intr_mutex);
	mutex_destroy(&sbbcsoftp->umutex);

	sbbc_remove_reg_maps(sbbcsoftp);
	kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);

	ddi_soft_state_free(sbbcsoft_statep, instance);

	return (DDI_SUCCESS);

}


/*
 * Translate child's address into parents.
 */
static int
sbbc_busmap(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
	    off_t off, off_t len, caddr_t *addrp)
{
	struct sbbcsoft *sbbcsoftp;
	sbbc_child_regspec_t *child_rp, *child_regs;
	pci_regspec_t pci_reg;
	ddi_map_req_t p_map_request;
	int rnumber, i, n;
	int rval = DDI_SUCCESS;
	int instance;

	SBBC_DBG4(SBBC_DBG_BUSMAP, dip,
	    "mapping child %s, type %llx, off %llx, len %llx\n",
	    ddi_driver_name(rdip), mp->map_type, off, len);

	SBBCTRACE(sbbc_busmap, 'BMAP', mp);

	/*
	 * Handle the mapping according to its type.
	 */
	instance = ddi_get_instance(dip);
	if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
		return (DDI_FAILURE);

	switch (mp->map_type) {
	case DDI_MT_REGSPEC:

		/*
		 * We assume the register specification is in sbbc format.
		 * We must convert it into a PCI format regspec and pass
		 * the request to our parent.
		 */
		child_rp = (sbbc_child_regspec_t *)mp->map_obj.rp;
		break;

	case DDI_MT_RNUMBER:

		/*
		 * map_type 0
		 * Get the "reg" property from the device node and convert
		 * it to our parent's format.
		 */
		rnumber = mp->map_obj.rnumber;

		/* get the requester's reg property */
		if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
		    "reg", (caddr_t)&child_regs, &i) != DDI_SUCCESS) {
			cmn_err(CE_WARN,
			    "SBBC: couldn't get %s ranges property %d",
			    ddi_get_name(sbbcsoftp->dip), instance);
			return (DDI_ME_RNUMBER_RANGE);
		}
		n = i / sizeof (sbbc_child_regspec_t);

		if (rnumber < 0 || rnumber >= n) {
			kmem_free(child_regs, i);
			return (DDI_ME_RNUMBER_RANGE);
		}
		child_rp = &child_regs[rnumber];
		break;

	default:
		return (DDI_ME_INVAL);

	}

	/* Adjust our reg property with offset and length */
	child_rp->addr_low += off;

	if (len)
		child_rp->size = len;

	/*
	 * Combine this reg prop. into our parents PCI address using the ranges
	 * property.
	 */
	rval = sbbc_apply_range(sbbcsoftp, rdip, child_rp, &pci_reg);

	if (mp->map_type == DDI_MT_RNUMBER)
		kmem_free(child_regs, i);

	if (rval != DDI_SUCCESS)
		return (rval);

	p_map_request = *mp;
	p_map_request.map_type = DDI_MT_REGSPEC;
	p_map_request.map_obj.rp = (struct regspec *)&pci_reg;

	/* Send it to PCI nexus to map into the PCI space */
	rval = ddi_map(dip, &p_map_request, 0, 0, addrp);

	return (rval);

}


/* new intr_ops structure */
static int
sbbc_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	int	ret = DDI_SUCCESS;

	switch (intr_op) {
	case DDI_INTROP_GETCAP:
		*(int *)result = DDI_INTR_FLAG_LEVEL;
		break;
	case DDI_INTROP_ALLOC:
		*(int *)result = hdlp->ih_scratch1;
		break;
	case DDI_INTROP_FREE:
		break;
	case DDI_INTROP_GETPRI:
		if (hdlp->ih_pri == 0) {
			hdlp->ih_pri = 0x1;

			cmn_err(CE_WARN, "%s%d assigning default interrupt "
			    "level %d for device %s%d", ddi_driver_name(dip),
			    ddi_get_instance(dip), hdlp->ih_pri,
			    ddi_driver_name(rdip), ddi_get_instance(rdip));
		}

		*(int *)result = hdlp->ih_pri;

		break;
	case DDI_INTROP_ADDISR:
		ret = sbbc_add_intr_impl(dip, rdip, intr_op, hdlp, result);
		break;
	case DDI_INTROP_REMISR:
		ret = sbbc_remove_intr_impl(dip, rdip, intr_op, hdlp, result);
		break;
	case DDI_INTROP_ENABLE:
		ret = sbbc_update_intr_state(dip, rdip, intr_op, hdlp, &result);
		break;
	case DDI_INTROP_DISABLE:
		ret = sbbc_update_intr_state(dip, rdip, intr_op, hdlp, &result);
		break;
	case DDI_INTROP_NINTRS:
	case DDI_INTROP_NAVAIL:
		*(int *)result = i_ddi_get_intx_nintrs(rdip);
		break;
	case DDI_INTROP_SUPPORTED_TYPES:
		/* PCI nexus driver supports only fixed interrupts */
		*(int *)result = i_ddi_get_intx_nintrs(rdip) ?
		    DDI_INTR_TYPE_FIXED : 0;
		break;
	default:
		ret = DDI_ENOTSUP;
		break;
	}

	return (ret);
}


static int
sbbc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	sbbcsoft_t *sbbcsoftp;
	sbbc_child_intr_t *childintr;
	int instance, i, rval = DDI_SUCCESS;

	SBBC_DBG2(SBBC_DBG_INTR, dip,
	    "add: rdip 0x%llx hdlp 0x%llx\n", rdip, hdlp);

	/* insert the sbbc isr wrapper instead */
	instance = ddi_get_instance(dip);
	if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
		return (DDI_FAILURE);

	childintr = kmem_zalloc(sizeof (struct sbbc_child_intr), KM_SLEEP);

	childintr->name = ddi_get_name(rdip);
	childintr->inum = hdlp->ih_inum;
	childintr->intr_handler = hdlp->ih_cb_func;
	childintr->arg1 = hdlp->ih_cb_arg1;
	childintr->arg2 = hdlp->ih_cb_arg2;
	childintr->status = SBBC_INTR_STATE_DISABLE;

	for (i = 0; i < MAX_SBBC_DEVICES; i++) {
		if (sbbcsoftp->child_intr[i] == 0) {
			sbbcsoftp->child_intr[i] = childintr;
			break;
		}
	}

	DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp,
	    (ddi_intr_handler_t *)sbbc_intr_wrapper,
	    (caddr_t)sbbcsoftp, NULL);

	if ((rval = i_ddi_intr_ops(dip, rdip, intr_op,
	    hdlp, result)) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "sbbc%d: failed to add intr for %s",
		    instance, ddi_get_name(rdip));
		kmem_free(childintr, sizeof (struct sbbc_child_intr));
		sbbcsoftp->child_intr[i] = NULL;
	}

	/*
	 * Restore original interrupt handler
	 * and arguments in interrupt handle.
	 */
	DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, childintr->intr_handler,
	    childintr->arg1, childintr->arg2);

	return (rval);
}

static int
sbbc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	sbbcsoft_t *sbbcsoftp;
	sbbc_child_intr_t *childintr;
	int instance, i, rval = DDI_SUCCESS;

	SBBC_DBG2(SBBC_DBG_INTR, dip,
	    "remove: rdip 0x%llx hdlp 0x%llx\n", rdip, hdlp);

	instance = ddi_get_instance(dip);
	if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
		return (DDI_FAILURE);

	/* remove the sbbc isr wrapper instead */
	for (i = 0; i < MAX_SBBC_DEVICES; i++) {
		if (sbbcsoftp->child_intr[i]) {
			childintr = sbbcsoftp->child_intr[i];
			if (childintr->status == SBBC_INTR_STATE_DISABLE &&
			    childintr->name == ddi_get_name(rdip)) {
				/* put back child's inum */
				hdlp->ih_inum = childintr->inum;
				break;
			}
		}
	}

	if (i >= MAX_SBBC_DEVICES) {
		cmn_err(CE_WARN, "sbbc%d:obound failed to remove intr for %s",
		    instance, ddi_get_name(rdip));
		return (DDI_FAILURE);
	}

	if ((rval = i_ddi_intr_ops(dip, rdip, intr_op,
	    hdlp, result)) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "sbbc%d: failed to remove intr for %s",
		    instance, ddi_get_name(rdip));
		return (rval);
	}

	kmem_free(childintr, sizeof (struct sbbc_child_intr));
	sbbcsoftp->child_intr[i] = NULL;

	return (rval);
}


static int
sbbc_update_intr_state(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	sbbcsoft_t		*sbbcsoftp;
	sbbc_child_intr_t	*childintr;
	int			instance, i;
	int			ret = DDI_SUCCESS;

	SBBC_DBG2(SBBC_DBG_INTR, dip, "sbbc_update_intr_state: "
	    "rdip 0x%llx hdlp 0x%llx state 0x%x\n", rdip, hdlp);

	instance = ddi_get_instance(dip);
	if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
		return (DDI_FAILURE);

	for (i = 0; i < MAX_SBBC_DEVICES; i++) {
		if (sbbcsoftp->child_intr[i]) {
			childintr = sbbcsoftp->child_intr[i];
			if (childintr->name == ddi_get_name(rdip))
				break;
		}
	}

	if (i >= MAX_SBBC_DEVICES) {
		cmn_err(CE_WARN, "sbbc%d: failed to update intr state for %s",
		    instance, ddi_get_name(rdip));
		return (DDI_FAILURE);
	}

	if ((ret = i_ddi_intr_ops(dip, rdip, intr_op,
	    hdlp, result)) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "sbbc%d: failed to update intr state for %s",
		    instance, ddi_get_name(rdip));
		return (ret);
	}

	/* Update the interrupt state */
	childintr->status = (intr_op == DDI_INTROP_ENABLE) ?
	    SBBC_INTR_STATE_ENABLE : SBBC_INTR_STATE_DISABLE;

	return (ret);
}


/*
 * This entry point is called before a child's probe or attach is called.
 * The arg pointer points to child's dev_info_t structure.
 */
static int
sbbc_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
	    void *arg, void *result)
{
	sbbc_child_regspec_t *child_rp;
	int i, n;

	SBBC_DBG3(SBBC_DBG_CTLOPS, dip,
	    "Initializing %s, arg %x, op %x\n",
	    ddi_driver_name(rdip), arg, op);

	SBBCTRACE(sbbc_ctlops, 'CTLO', arg);

	switch (op) {
	case DDI_CTLOPS_INITCHILD: {
		return (sbbc_initchild(dip, rdip, (dev_info_t *)arg));
	}

	case DDI_CTLOPS_UNINITCHILD: {
		return (sbbc_uninitchild(rdip, (dev_info_t *)arg));
	}

	case DDI_CTLOPS_REPORTDEV:

		cmn_err(CE_CONT, "?%s%d at %s%d: offset %s\n",
		    ddi_driver_name(rdip), ddi_get_instance(rdip),
		    ddi_driver_name(dip), ddi_get_instance(dip),
		    ddi_get_name_addr(rdip));
		return (DDI_SUCCESS);

	case DDI_CTLOPS_REGSIZE:

		if (getprop(rdip, "reg", &child_rp, &i) != DDI_SUCCESS) {
			return (DDI_FAILURE);
		}
		n = i / sizeof (sbbc_child_regspec_t);
		if (*(int *)arg < 0 || *(int *)arg >= n) {
			kmem_free(child_rp, i);
			return (DDI_FAILURE);
		}
		*((off_t *)result) = child_rp[*(int *)arg].size;
		kmem_free(child_rp, i);
		return (DDI_SUCCESS);

	case DDI_CTLOPS_NREGS:

		if (getprop(rdip, "reg", &child_rp, &i) != DDI_SUCCESS) {
			return (DDI_FAILURE);
		}
		*((uint_t *)result) = i / sizeof (sbbc_child_regspec_t);
		kmem_free(child_rp, i);
		return (DDI_SUCCESS);
	}

	/*
	 * Now pass the request up to our parent.
	 */
	SBBC_DBG0(SBBC_DBG_CTLOPS, dip, "Calling ddi_ctlops\n");

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


/*
 * The following routine uses ranges property, that was read earlier, and
 * takes child's reg property, and computes the complete address and size
 * for the PCI parent to map.
 */
static int
sbbc_apply_range(struct sbbcsoft *sbbc_p, dev_info_t *rdip,
    sbbc_child_regspec_t *child_rp, pci_regspec_t *rp)
{
	int b;
	int rval = DDI_SUCCESS;
	struct sbbc_pci_rangespec *rangep = sbbc_p->rangep;
	int nrange = sbbc_p->range_cnt;

	SBBC_DBG4(SBBC_DBG_MAPRANGES, rdip,
	    "Applying ranges for %s, rangep %llx, child_rp %llx, range %x\n",
	    ddi_driver_name(rdip), sbbc_p->rangep, child_rp, nrange);

	SBBCTRACE(sbbc_apply_range, 'APPL', sbbc_p);

	for (b = 0; b < nrange; ++b, ++rangep) {

		/* Make sure the correct range is being mapped */
		if (child_rp->addr_hi == rangep->sbbc_phys_hi)
			/* See if we fit in this range */
			if ((child_rp->addr_low >=
			    rangep->sbbc_phys_low) &&
			    ((child_rp->addr_low + child_rp->size - 1)
			    <= (rangep->sbbc_phys_low +
			    rangep->rng_size - 1))) {
				uint_t addr_offset = child_rp->addr_low -
				    rangep->sbbc_phys_low;
				/*
				 * Use the range entry to translate
				 * the SBBC physical address into the
				 * parents PCI space.
				 */
				rp->pci_phys_hi =
				    rangep->pci_phys_hi;
				rp->pci_phys_mid = rangep->pci_phys_mid;
				rp->pci_phys_low =
				    rangep->pci_phys_low + addr_offset;
				rp->pci_size_hi = 0;
				rp->pci_size_low =
				    min(child_rp->size, (rangep->rng_size -
				    addr_offset));

				break;
			}
	}

	if (b == nrange)  {
		cmn_err(CE_WARN, "out_of_range %s", ddi_get_name(rdip));
		return (DDI_ME_REGSPEC_RANGE);
	}

	return (rval);
}


/*
 * The following routine reads sbbc's ranges property from OBP and sets up
 * its soft structure with it.
 */
static int
sbbc_get_ranges(struct sbbcsoft *sbbcsoftp)
{
	struct sbbc_pci_rangespec *rangep;
	int range_len, nrange;

	if (ddi_getlongprop(DDI_DEV_T_ANY, sbbcsoftp->dip, DDI_PROP_DONTPASS,
	    "ranges", (caddr_t)&rangep, &range_len) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "SBBC: couldn't get %s ranges property %d",
		    ddi_get_name(sbbcsoftp->dip), sbbcsoftp->instance);
		return (DDI_ME_REGSPEC_RANGE);
	}

	nrange = range_len / sizeof (struct sbbc_pci_rangespec);

	if (!nrange) {
		kmem_free(rangep, range_len);
		return (DDI_FAILURE);
	}

	/* setup the soft structure with ranges info. */
	sbbcsoftp->rangep = rangep;
	sbbcsoftp->range_cnt = nrange;
	sbbcsoftp->range_len = range_len;

	return (DDI_SUCCESS);
}


/*
 * Configure the SBBC for PCI
 */
static int
sbbc_config4pci(struct sbbcsoft *sbbcsoftp)
{
	ddi_acc_handle_t conf_handle;
	uint16_t comm, vendid, devid, stat;
	uint8_t revid;

#ifdef DEBUG
	if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
		cmn_err(CE_CONT,
		    "sbbc_config4pci: sbbcsoftp %p\n", (void *)sbbcsoftp);
	}
#endif
	if (pci_config_setup(sbbcsoftp->dip, &conf_handle) != DDI_SUCCESS)
		return (1);

	vendid = pci_config_get16(conf_handle, PCI_CONF_VENID);
	devid = pci_config_get16(conf_handle, PCI_CONF_DEVID);
	comm = pci_config_get16(conf_handle, PCI_CONF_COMM);
	stat = pci_config_get16(conf_handle, PCI_CONF_STAT);
	revid = pci_config_get8(conf_handle, PCI_CONF_REVID);

#ifdef DEBUG
	if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
		cmn_err(CE_CONT,
		    "SBBC vendid %x, devid %x, comm %x, stat %x, revid %x\n",
		    vendid, devid, comm, stat, revid);
	}
#endif
	comm = (PCI_COMM_ME | PCI_COMM_MAE | PCI_COMM_SERR_ENABLE |
	    PCI_COMM_PARITY_DETECT);

	pci_config_put16(conf_handle, PCI_CONF_COMM, comm);

	comm = pci_config_get16(conf_handle, PCI_CONF_COMM);

#ifdef DEBUG
	if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
		cmn_err(CE_CONT, "comm %x\n", comm);
	}
#endif
	pci_config_teardown(&conf_handle);

	return (0);
}


/* ARGSUSED0 */
int
sbbc_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	dev_t	dev = (dev_t)arg;
	struct sbbcsoft *sbbcsoftp;
	int	instance, ret;

	instance = getminor(dev);

	SBBCTRACE(sbbc_getinfo, 'GINF', instance);

	switch (infocmd) {
		case DDI_INFO_DEVT2DEVINFO:
			sbbcsoftp = (struct sbbcsoft *)
			    ddi_get_soft_state(sbbcsoft_statep, instance);
			if (sbbcsoftp == NULL) {
				*result = (void *) NULL;
				ret = DDI_FAILURE;
			} else {
				*result = sbbcsoftp->dip;
				ret = DDI_SUCCESS;
			}
			break;
		case DDI_INFO_DEVT2INSTANCE:
			*result = (void *)(uintptr_t)instance;
			ret = DDI_SUCCESS;
			break;
		default:
			ret = DDI_FAILURE;
			break;
	}

	return (ret);
}

/*ARGSUSED1*/
static int
sbbc_open(dev_t *dev, int flag, int otype, cred_t *credp)
{
	struct sbbcsoft *sbbcsoftp;
	int		instance;

	/* check privilege of caller process */
	if (drv_priv(credp)) {
		return (EPERM);
	}

	instance = getminor(*dev);
	if (instance < 0)
		return (ENXIO);
	sbbcsoftp = (struct sbbcsoft *)ddi_get_soft_state(sbbcsoft_statep,
	    instance);
	SBBCTRACE(sbbc_open, 'OPEN', sbbcsoftp);

	if (sbbcsoftp == NULL)
		return (ENXIO);

	mutex_enter(&sbbcsoftp->umutex);

	/* check for exclusive access */
	if ((sbbcsoftp->oflag == TRUE)) {
		mutex_exit(&sbbcsoftp->umutex);
		return (EBUSY);
	}
	sbbcsoftp->oflag = TRUE;

	mutex_exit(&sbbcsoftp->umutex);

	return (0);
}

/*ARGSUSED1*/
static int
sbbc_close(dev_t dev, int flag, int otype, cred_t *credp)
{
	struct sbbcsoft *sbbcsoftp;
	int		instance;

	instance = getminor(dev);
	if (instance < 0)
		return (ENXIO);
	sbbcsoftp = (struct sbbcsoft *)ddi_get_soft_state(sbbcsoft_statep,
	    instance);
	/* wait till all output activity has ceased */

	mutex_enter(&sbbcsoftp->umutex);

	SBBCTRACE(sbbc_close, 'CLOS', sbbcsoftp);

	sbbcsoftp->oflag = FALSE;

	mutex_exit(&sbbcsoftp->umutex);

	return (0);
}

/*ARGSUSED2*/
static int
sbbc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
		int *rvalp)
{
	struct sbbcsoft *sbbcsoftp;

	SBBCTRACE(sbbc_ioctl, 'IOCT', arg);

	sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, getminor(dev));

	if (sbbcsoftp == NULL) {
		return (ENXIO);
	}

	switch (cmd) {
	case SBBC_SBBCREG_WR:
		{
		struct ssc_sbbc_regio sbbcregs;
		uint64_t offset;

		if (sbbc_scmode == FALSE) {
			/* then we're executing on Domain; Writes not allowed */
			return (EINVAL);
		}

		if (arg == NULL) {
			return (ENXIO);
		}

		if (ddi_copyin((caddr_t)arg, (caddr_t)&sbbcregs,
				    sizeof (struct ssc_sbbc_regio), mode)) {
			cmn_err(CE_WARN, "sbbc_ioctl: copyin failed arg %p",
			    (void *)arg);
			return (EFAULT);
		}

		/*
		 * Bug #4287186: SBBC driver on cp1500 doesn't check length for
		 *		reads or writes
		 * Note that I've also added a check to make sure the offset is
		 * valid, since misaligned (i.e. not on 16-byte boundary)
		 * accesses or accesses to "Reserved" register offsets are
		 * treated as unmapped by the SBBC.
		 */
		if ((sbbcregs.len != 4) ||
		    !sbbc_offset_valid(sbbcregs.offset)) {
			return (EINVAL);
		}

		offset = (uint64_t)sbbcsoftp->pci_sbbc_map;
		offset += sbbcregs.offset;
		ddi_put32(sbbcsoftp->pci_sbbc_map_handle, (uint32_t *)offset,
		    sbbcregs.value);
		}
		break;
	case SBBC_SBBCREG_RD:
		{
		struct ssc_sbbc_regio sbbcregs;
		uint64_t offset;

		if (sbbc_scmode == FALSE) {
			/* then we're executing on Domain; Reads not allowed */
			return (EINVAL);
		}

		if (arg == NULL) {
			return (ENXIO);
		}

		if (ddi_copyin((caddr_t)arg, (caddr_t)&sbbcregs,
				    sizeof (struct ssc_sbbc_regio), mode)) {
			cmn_err(CE_WARN, "sbbc_ioctl: copyin failed arg %p",
			    (void *)arg);
			return (EFAULT);
		}

		/*
		 * Bug #4287186: SBBC driver on cp1500 doesn't check length for
		 *		reads or writes
		 * Note that I've also added a check to make sure the offset is
		 * valid, since misaligned (i.e. not on 16-byte boundary)
		 * accesses or accesses to "Reserved" register offsets are
		 * treated as unmapped by the SBBC.
		 */
		if ((sbbcregs.len != 4) ||
		    !sbbc_offset_valid(sbbcregs.offset)) {
			return (EINVAL);
		}

		offset = (uint64_t)sbbcsoftp->pci_sbbc_map;
		offset += sbbcregs.offset;

		sbbcregs.value = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
		    (uint32_t *)offset);

		if (ddi_copyout((caddr_t)&sbbcregs.value,
		    &((struct ssc_sbbc_regio *)arg)->value,
		    sbbcregs.len, mode)) {
			cmn_err(CE_WARN, "sbbc_ioctl:copyout failed arg %p",
			    (void *)arg);
			return (EFAULT);
		}
		}
		break;
	default:
		cmn_err(CE_WARN, "sbbc_ioctl:Illegal command 0x%08x", cmd);
		return (ENOTTY);
	}

	return (DDI_SUCCESS);
}

static void
sbbc_remove_reg_maps(struct sbbcsoft *sbbcsoftp)
{
	SBBCTRACE(sbbc_remove_reg_maps, 'RMAP', sbbcsoftp);
	if (sbbcsoftp->pci_sbbc_map_handle)
		ddi_regs_map_free(&sbbcsoftp->pci_sbbc_map_handle);
}


static int
sbbc_init(struct sbbcsoft *sbbcsoftp)
{
	/* Mask all the interrupts until we are ready. */
	ddi_put32(sbbcsoftp->pci_sbbc_map_handle,
	    &sbbcsoftp->pci_sbbc_map->sys_intr_enable,
	    0x00000000);

	return (1);
}

/*
 * The following routine is a generic routine to initialize any child of
 * sbbc nexus driver information into parent private data structure.
 */
/* ARGSUSED0 */
static int
sbbc_initchild(dev_info_t *dip, dev_info_t *rdip, dev_info_t *child)
{
	sbbc_child_regspec_t *child_rp;
	int reglen, slot;
	char name[10];

	SBBC_DBG1(SBBC_DBG_INITCHILD, dip, "Initializing %s\n",
	    ddi_driver_name(rdip));

	/*
	 * Initialize a child
	 * Set the address portion of the node name based on the
	 * address/offset.
	 */
	if (ddi_getlongprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
	    "reg", (caddr_t)&child_rp, &reglen) != DDI_SUCCESS) {
		if (strcmp(ddi_node_name(child), "hotplug-controller") == 0) {
			slot = 1;
			(void) sprintf(name, "%x", slot);
			ddi_set_name_addr(child, name);
			return (DDI_SUCCESS);
		}
		return (DDI_FAILURE);
	}

	SBBC_DBG3(SBBC_DBG_INITCHILD, dip, "hi 0x%x, low 0x%x, size 0x%x\n",
	    child_rp->addr_hi, child_rp->addr_low, child_rp->size);

	(void) sprintf(name, "%x,%x", child_rp->addr_hi, child_rp->addr_low);

	/*
	 * set child's addresses from the reg property into parent private
	 * data structure.
	 */
	ddi_set_name_addr(child, name);
	kmem_free(child_rp, reglen);

	ddi_set_parent_data(child, NULL);

	return (DDI_SUCCESS);
}


/* ARGSUSED0 */
static int
sbbc_uninitchild(dev_info_t *rdip, dev_info_t *child)
{

	SBBC_DBG1(SBBC_DBG_UNINITCHILD, rdip, "Uninitializing %s\n",
	    ddi_driver_name(rdip));

	ddi_set_name_addr(child, NULL);
	ddi_remove_minor_node(child, NULL);
	impl_rem_dev_props(child);

	return (DDI_SUCCESS);

}


/*
 * The following routine is an interrupt service routine that is used
 * as a wrapper to all the children requiring interrupt services.
 */
static uint_t
sbbc_intr_wrapper(caddr_t arg)
{

	struct sbbcsoft *sbbcsoftp = (struct sbbcsoft *)arg;
	int i, rval;

	SBBC_DBG1(SBBC_DBG_INTR, sbbcsoftp->dip, "Isr arg 0x%llx\n", arg);

	mutex_enter(&sbbcsoftp->sbbc_intr_mutex);

	for (i = 0; i < MAX_SBBC_DEVICES; i++) {
		/*
		 * Check the interrupt status reg. to determine the cause.
		 */
		/*
		 * Check the error status reg. to determine the cause.
		 */
		if (sbbcsoftp->child_intr[i] &&
		    sbbcsoftp->child_intr[i]->status ==
		    SBBC_INTR_STATE_ENABLE) {
			/*
			 * Dispatch the children interrupt service routines and
			 * look for someone to claim.
			 */
			rval = sbbcsoftp->child_intr[i]->intr_handler(
			    sbbcsoftp->child_intr[i]->arg1,
			    sbbcsoftp->child_intr[i]->arg2);

			if (rval == DDI_INTR_CLAIMED) {
				mutex_exit(&sbbcsoftp->sbbc_intr_mutex);
				return (rval);
			}
		}
	}

	mutex_exit(&sbbcsoftp->sbbc_intr_mutex);

	/* for now do not claim since we know its not enabled */
	return (DDI_INTR_UNCLAIMED);
}


/*
 * This function checks an SBBC register offset to make sure that it is properly
 * aligned (i.e. on a 16-byte boundary) and that it corresponds to an accessible
 * register.  Since the SBBC treates accesses to unaligned or reserved addresses
 * as unmapped, failing to check for these would leave a loophole that could be
 * used to crash the system.
 */
static int
sbbc_offset_valid(uint32_t offset) {
	/*
	 * Check for proper alignment first.
	 */
	if ((offset % 16) != 0) {
		return (0);
	}

	/*
	 * Now start checking for the various reserved ranges.
	 * While sticking a bunch of constants in the code (rather than
	 * #define'd values) is usually best avoided, it would probably
	 * do more harm than good here.  These values were taken from the
	 * Serengeti Architecture Programmer's Reference Manual dated
	 * August 10, 1999, pages 2-99 through 2-103.  While there are
	 * various "clever" ways this check could be performed that would
	 * be slightly more efficient, arranging the code in this fashion
	 * should maximize maintainability.
	 */
	if (((offset >= 0x001a0) && (offset <= 0x001ff)) ||
	    ((offset >= 0x002a0) && (offset <= 0x002ff)) ||
	    ((offset >= 0x00350) && (offset <= 0x003ff)) ||
	    ((offset >= 0x00500) && (offset <= 0x00fff)) ||
	    ((offset >= 0x01160) && (offset <= 0x011ff)) ||
	    ((offset >= 0x01210) && (offset <= 0x017ff)) ||
	    ((offset >= 0x01810) && (offset <= 0x01fff)) ||
	    ((offset >= 0x02030) && (offset <= 0x022ff)) ||
	    ((offset >= 0x02340) && (offset <= 0x03fff)) ||
	    ((offset >= 0x04030) && (offset <= 0x05fff)) ||
	    ((offset >= 0x060a0) && (offset <= 0x060ff)) ||
	    (offset == 0x06120) ||
	    ((offset >= 0x06190) && (offset <= 0x061ff)) ||
	    ((offset >= 0x06230) && (offset <= 0x062f0)) ||
	    (offset > 0x06320)) {
		return (0);
	}

	return (1);
}

#ifdef DEBUG
void
sbbc_dbg(uint32_t flag, dev_info_t *dip, char *fmt,
	uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5)
{
	char *s = NULL;

	if (sbbc_dbg_flags && ((sbbc_dbg_flags & flag) == flag)) {
		switch (flag) {
		case SBBC_DBG_ATTACH:
			s = "attach";
			break;
		case SBBC_DBG_DETACH:
			s = "detach";
			break;
		case SBBC_DBG_CTLOPS:
			s = "ctlops";
			break;
		case SBBC_DBG_INITCHILD:
			s = "initchild";
			break;
		case SBBC_DBG_UNINITCHILD:
			s = "uninitchild";
			break;
		case SBBC_DBG_BUSMAP:
			s = "busmap";
			break;
		case SBBC_DBG_INTR:
			s = "intr";
			break;
		case SBBC_DBG_INTROPS:
			s = "intr_ops";
			break;
		case SBBC_DBG_PCICONF:
			s = "pciconfig";
			break;
		case SBBC_DBG_MAPRANGES:
			s = "mapranges";
			break;
		case SBBC_DBG_PROPERTIES:
			s = "properties";
			break;
		case SBBC_DBG_OPEN:
			s = "open";
			break;
		case SBBC_DBG_CLOSE:
			s = "close";
			break;
		case SBBC_DBG_IOCTL:
			s = "ioctl";
			break;
		default:
			s = "Unknown debug flag";
			break;
		}

		cmn_err(CE_CONT, "%s_%s(%d): ", ddi_driver_name(dip), s,
		    ddi_get_instance(dip));
		cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5);
	}
}

/*
 * Dump the SBBC chip's Device ID Register
 */
static void sbbc_dump_devid(dev_info_t *dip, struct sbbcsoft *sbbcsoftp,
	int instance)
{
	uint32_t sbbc_id_reg;
	uint16_t sbbc_id_reg_partid;
	uint16_t sbbc_id_reg_manfid;

	sbbc_id_reg = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
	    (uint32_t *)&sbbcsoftp->pci_sbbc_map->devid);

	sbbc_id_reg_partid = ((sbbc_id_reg << 4) >> 16);
	sbbc_id_reg_manfid = ((sbbc_id_reg << 20) >> 21);

	SBBC_DBG4(SBBC_DBG_ATTACH, dip,
	    "FOUND SBBC(%d) Version %x, Partid %x, Manfid %x\n",
	    instance, (sbbc_id_reg >> 28), sbbc_id_reg_partid,
	    sbbc_id_reg_manfid);
}

#endif /* DEBUG */