view usr/src/uts/sun4u/io/sysiosbus.c @ 13630:76b4d0b12a17

998 obsolete DMA driver interfaces should be removed Reviewed by: Igor Khozhukhov <igor.khozhukhov@nexenta.com> Reviewed by: Albert Lee <trisk@nexenta.com> Reviewed by: Robert Mustacchi <rm@joyent.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Garrett D'Amore <garrett@damore.org>
date Thu, 01 Mar 2012 15:25:51 -0800
parents 2621e50fdf4a
children 1e93e74c836a
line wrap: on
line source

/*
 * 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.
 */

/*
 * Copyright 2012 Garrett D'Amore <garrett@damore.org>.  All rights reserved.
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_implfuncs.h>
#include <sys/obpdefs.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/spl.h>
#include <sys/iommu.h>
#include <sys/sysiosbus.h>
#include <sys/sysioerr.h>
#include <sys/iocache.h>
#include <sys/async.h>
#include <sys/machsystm.h>
#include <sys/intreg.h>
#include <sys/ddi_subrdefs.h>
#ifdef _STARFIRE
#include <sys/starfire.h>
#endif /* _STARFIRE */
#include <sys/sdt.h>

/* Useful debugging Stuff */
#include <sys/nexusdebug.h>
/* Bitfield debugging definitions for this file */
#define	SBUS_ATTACH_DEBUG	0x1
#define	SBUS_SBUSMEM_DEBUG	0x2
#define	SBUS_INTERRUPT_DEBUG	0x4
#define	SBUS_REGISTERS_DEBUG	0x8

/*
 * Interrupt registers table.
 * This table is necessary due to inconsistencies in the sysio register
 * layout.  If this gets fixed in the chip, we can get rid of this stupid
 * table.
 */
static struct sbus_slot_entry ino_1 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L1_CLEAR, NULL};
static struct sbus_slot_entry ino_2 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L2_CLEAR, NULL};
static struct sbus_slot_entry ino_3 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L3_CLEAR, NULL};
static struct sbus_slot_entry ino_4 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L4_CLEAR, NULL};
static struct sbus_slot_entry ino_5 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L5_CLEAR, NULL};
static struct sbus_slot_entry ino_6 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L6_CLEAR, NULL};
static struct sbus_slot_entry ino_7 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
					SBUS_SLOT0_L7_CLEAR, NULL};
static struct sbus_slot_entry ino_9 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L1_CLEAR, NULL};
static struct sbus_slot_entry ino_10 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L2_CLEAR, NULL};
static struct sbus_slot_entry ino_11 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L3_CLEAR, NULL};
static struct sbus_slot_entry ino_12 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L4_CLEAR, NULL};
static struct sbus_slot_entry ino_13 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L5_CLEAR, NULL};
static struct sbus_slot_entry ino_14 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L6_CLEAR, NULL};
static struct sbus_slot_entry ino_15 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
					SBUS_SLOT1_L7_CLEAR, NULL};
static struct sbus_slot_entry ino_17 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L1_CLEAR, NULL};
static struct sbus_slot_entry ino_18 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L2_CLEAR, NULL};
static struct sbus_slot_entry ino_19 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L3_CLEAR, NULL};
static struct sbus_slot_entry ino_20 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L4_CLEAR, NULL};
static struct sbus_slot_entry ino_21 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L5_CLEAR, NULL};
static struct sbus_slot_entry ino_22 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L6_CLEAR, NULL};
static struct sbus_slot_entry ino_23 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
					SBUS_SLOT2_L7_CLEAR, NULL};
static struct sbus_slot_entry ino_25 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L1_CLEAR, NULL};
static struct sbus_slot_entry ino_26 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L2_CLEAR, NULL};
static struct sbus_slot_entry ino_27 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L3_CLEAR, NULL};
static struct sbus_slot_entry ino_28 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L4_CLEAR, NULL};
static struct sbus_slot_entry ino_29 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L5_CLEAR, NULL};
static struct sbus_slot_entry ino_30 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L6_CLEAR, NULL};
static struct sbus_slot_entry ino_31 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
					SBUS_SLOT3_L7_CLEAR, NULL};
static struct sbus_slot_entry ino_32 = {SBUS_SLOT5_CONFIG, ESP_MAPREG,
					ESP_CLEAR, ESP_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_33 = {SBUS_SLOT5_CONFIG, ETHER_MAPREG,
					ETHER_CLEAR, ETHER_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_34 = {SBUS_SLOT5_CONFIG, PP_MAPREG,
					PP_CLEAR, PP_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_36 = {SBUS_SLOT4_CONFIG, AUDIO_MAPREG,
					AUDIO_CLEAR, AUDIO_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_40 = {SBUS_SLOT6_CONFIG, KBDMOUSE_MAPREG,
					KBDMOUSE_CLEAR,
					KBDMOUSE_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_41 = {SBUS_SLOT6_CONFIG, FLOPPY_MAPREG,
					FLOPPY_CLEAR, FLOPPY_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_42 = {SBUS_SLOT6_CONFIG, THERMAL_MAPREG,
					THERMAL_CLEAR,
					THERMAL_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_48 = {SBUS_SLOT6_CONFIG, TIMER0_MAPREG,
					TIMER0_CLEAR, TIMER0_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_49 = {SBUS_SLOT6_CONFIG, TIMER1_MAPREG,
					TIMER1_CLEAR, TIMER1_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_52 = {SBUS_SLOT6_CONFIG, UE_ECC_MAPREG,
					UE_ECC_CLEAR, UE_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_53 = {SBUS_SLOT6_CONFIG, CE_ECC_MAPREG,
					CE_ECC_CLEAR, CE_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_54 = {SBUS_SLOT6_CONFIG, SBUS_ERR_MAPREG,
					SBUS_ERR_CLEAR, SERR_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_55 = {SBUS_SLOT6_CONFIG, PM_WAKEUP_MAPREG,
					PM_WAKEUP_CLEAR, PM_INTR_STATE_SHIFT};
static struct sbus_slot_entry ino_ffb = {NULL, FFB_MAPPING_REG, NULL, NULL};
static struct sbus_slot_entry ino_exp = {NULL, EXP_MAPPING_REG, NULL, NULL};

/* Construct the interrupt number array */
struct sbus_slot_entry *ino_table[] = {
	NULL, &ino_1, &ino_2, &ino_3, &ino_4, &ino_5, &ino_6, &ino_7,
	NULL, &ino_9, &ino_10, &ino_11, &ino_12, &ino_13, &ino_14, &ino_15,
	NULL, &ino_17, &ino_18, &ino_19, &ino_20, &ino_21, &ino_22, &ino_23,
	NULL, &ino_25, &ino_26, &ino_27, &ino_28, &ino_29, &ino_30, &ino_31,
	&ino_32, &ino_33, &ino_34, NULL, &ino_36, NULL, NULL, NULL,
	&ino_40, &ino_41, &ino_42, NULL, NULL, NULL, NULL, NULL, &ino_48,
	&ino_49, NULL, NULL, &ino_52, &ino_53, &ino_54, &ino_55, &ino_ffb,
	&ino_exp
};

/*
 * This table represents the Fusion interrupt priorities.  They range
 * from 1 - 15, so we'll pattern the priorities after the 4M.  We map Fusion
 * interrupt number to system priority.  The mondo number is used as an
 * index into this table.
 */
int interrupt_priorities[] = {
	-1, 2, 3, 5, 7, 9, 11, 13,	/* Slot 0 sbus level 1 - 7 */
	-1, 2, 3, 5, 7, 9, 11, 13,	/* Slot 1 sbus level 1 - 7 */
	-1, 2, 3, 5, 7, 9, 11, 13,	/* Slot 2 sbus level 1 - 7 */
	-1, 2, 3, 5, 7, 9, 11, 13,	/* Slot 3 sbus level 1 - 7 */
	4,				/* Onboard SCSI */
	6,				/* Onboard Ethernet */
	3,				/* Onboard Parallel port */
	-1,				/* Not in use */
	9,				/* Onboard Audio */
	-1, -1, -1,			/* Not in use */
	12,				/* Onboard keyboard/serial ports */
	11,				/* Onboard Floppy */
	9,				/* Thermal interrupt */
	-1, -1, -1,			/* Not is use */
	10,				/* Timer 0 (tick timer) */
	14,				/* Timer 1 (not used) */
	15,				/* Sysio UE ECC error */
	10,				/* Sysio CE ECC error */
	10,				/* Sysio Sbus error */
	10,				/* PM Wakeup */
};

/* Interrupt counter flag.  To enable/disable spurious interrupt counter. */
static int intr_cntr_on;

/*
 * Function prototypes.
 */
static int
sbus_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *);

static int
sbus_add_intr_impl(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp);

static void
sbus_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp);

static int
sbus_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
sbus_xlate_intrs(dev_info_t *dip, dev_info_t *rdip, uint32_t *intr,
    uint32_t *pil, int32_t ign);

static int
sbus_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);

static int
sbus_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);

static int
sbus_do_detach(dev_info_t *devi);

static	void
sbus_add_picN_kstats(dev_info_t *dip);

static	void
sbus_add_kstats(struct sbus_soft_state *);

static	int
sbus_counters_kstat_update(kstat_t *, int);

extern int
sysio_err_uninit(struct sbus_soft_state *softsp);

extern int
iommu_uninit(struct sbus_soft_state *softsp);

extern int
stream_buf_uninit(struct sbus_soft_state *softsp);

static int
find_sbus_slot(dev_info_t *dip, dev_info_t *rdip);

static void make_sbus_ppd(dev_info_t *child);

static int
sbusmem_initchild(dev_info_t *dip, dev_info_t *child);

static int
sbus_initchild(dev_info_t *dip, dev_info_t *child);

static int
sbus_uninitchild(dev_info_t *dip);

static int
sbus_ctlops_poke(struct sbus_soft_state *softsp, peekpoke_ctlops_t *in_args);

static int
sbus_ctlops_peek(struct sbus_soft_state *softsp, peekpoke_ctlops_t *in_args,
    void *result);

static int
sbus_init(struct sbus_soft_state *softsp, caddr_t address);

static int
sbus_resume_init(struct sbus_soft_state *softsp, int resume);

static void
sbus_cpr_handle_intr_map_reg(uint64_t *cpr_softsp, volatile uint64_t *baddr,
    int flag);

static void sbus_intrdist(void *);
static uint_t sbus_intr_reset(void *);

static int
sbus_update_intr_state(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp, uint_t new_intr_state);

#ifdef	_STARFIRE
void
pc_ittrans_init(int, caddr_t *);

void
pc_ittrans_uninit(caddr_t);

int
pc_translate_tgtid(caddr_t, int, volatile uint64_t *);

void
pc_ittrans_cleanup(caddr_t, volatile uint64_t *);
#endif	/* _STARFIRE */

/*
 * Configuration data structures
 */
static struct bus_ops sbus_bus_ops = {
	BUSO_REV,
	i_ddi_bus_map,
	0,
	0,
	0,
	i_ddi_map_fault,
	0,
	iommu_dma_allochdl,
	iommu_dma_freehdl,
	iommu_dma_bindhdl,
	iommu_dma_unbindhdl,
	iommu_dma_flush,
	iommu_dma_win,
	iommu_dma_mctl,
	sbus_ctlops,
	ddi_bus_prop_op,
	0,			/* (*bus_get_eventcookie)();	*/
	0,			/* (*bus_add_eventcall)();	*/
	0,			/* (*bus_remove_eventcall)();	*/
	0,			/* (*bus_post_event)();		*/
	0,			/* (*bus_intr_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)();		*/
	sbus_intr_ops		/* (*bus_intr_op)();		*/
};

static struct cb_ops sbus_cb_ops = {
	nodev,			/* open */
	nodev,			/* close */
	nodev,			/* strategy */
	nodev,			/* print */
	nodev,			/* dump */
	nodev,			/* read */
	nodev,			/* write */
	nodev,			/* ioctl */
	nodev,			/* devmap */
	nodev,			/* mmap */
	nodev,			/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* prop_op */
	NULL,
	D_NEW | D_MP | D_HOTPLUG,
	CB_REV,				/* rev */
	nodev,				/* int (*cb_aread)() */
	nodev				/* int (*cb_awrite)() */
};

static struct dev_ops sbus_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	ddi_no_info,		/* info */
	nulldev,		/* identify */
	nulldev,		/* probe */
	sbus_attach,		/* attach */
	sbus_detach,		/* detach */
	nodev,			/* reset */
	&sbus_cb_ops,		/* driver operations */
	&sbus_bus_ops,		/* bus operations */
	nulldev,		/* power */
	ddi_quiesce_not_supported,	/* devo_quiesce */
};

/* global data */
void *sbusp;		/* sbus soft state hook */
void *sbus_cprp;	/* subs suspend/resume soft state hook */
static kstat_t *sbus_picN_ksp[SBUS_NUM_PICS]; /* performance picN kstats */
static int	sbus_attachcnt = 0;   /* number of instances attached */
static kmutex_t	sbus_attachcnt_mutex; /* sbus_attachcnt lock - attach/detach */

#include <sys/modctl.h>
extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops, 	/* Type of module.  This one is a driver */
	"SBus (sysio) nexus driver",	/* Name of module. */
	&sbus_ops,		/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modldrv, NULL
};

/*
 * These are the module initialization routines.
 */
int
_init(void)
{
	int error;

	if ((error = ddi_soft_state_init(&sbusp,
	    sizeof (struct sbus_soft_state), 1)) != 0)
		return (error);

	/*
	 * Initialize cpr soft state structure
	 */
	if ((error = ddi_soft_state_init(&sbus_cprp,
	    sizeof (uint64_t) * MAX_INO_TABLE_SIZE, 0)) != 0)
		return (error);

	/* Initialize global mutex */
	mutex_init(&sbus_attachcnt_mutex, NULL, MUTEX_DRIVER, NULL);

	return (mod_install(&modlinkage));
}

int
_fini(void)
{
	int error;

	if ((error = mod_remove(&modlinkage)) != 0)
		return (error);

	mutex_destroy(&sbus_attachcnt_mutex);
	ddi_soft_state_fini(&sbusp);
	ddi_soft_state_fini(&sbus_cprp);
	return (0);
}

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

/*ARGSUSED*/
static int
sbus_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	struct sbus_soft_state *softsp;
	int instance, error;
	uint64_t *cpr_softsp;
	ddi_device_acc_attr_t attr;


#ifdef	DEBUG
	debug_info = 1;
	debug_print_level = 0;
#endif

	instance = ddi_get_instance(devi);

	switch (cmd) {
	case DDI_ATTACH:
		break;

	case DDI_RESUME:
		softsp = ddi_get_soft_state(sbusp, instance);

		if ((error = iommu_resume_init(softsp)) != DDI_SUCCESS)
			return (error);

		if ((error = sbus_resume_init(softsp, 1)) != DDI_SUCCESS)
			return (error);

		if ((error = stream_buf_resume_init(softsp)) != DDI_SUCCESS)
			return (error);

		/*
		 * Restore Interrupt Mapping registers
		 */
		cpr_softsp = ddi_get_soft_state(sbus_cprp, instance);

		if (cpr_softsp != NULL) {
			sbus_cpr_handle_intr_map_reg(cpr_softsp,
			    softsp->intr_mapping_reg, 0);
			ddi_soft_state_free(sbus_cprp, instance);
		}

		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	if (ddi_soft_state_zalloc(sbusp, instance) != DDI_SUCCESS)
		return (DDI_FAILURE);

	softsp = ddi_get_soft_state(sbusp, instance);

	/* Set the dip in the soft state */
	softsp->dip = devi;

	if ((softsp->upa_id = (int)ddi_getprop(DDI_DEV_T_ANY, softsp->dip,
	    DDI_PROP_DONTPASS, "upa-portid", -1)) == -1) {
		cmn_err(CE_WARN, "Unable to retrieve sbus upa-portid"
		    "property.");
		error = DDI_FAILURE;
		goto bad;
	}

	/*
	 * The firmware maps in all 3 pages of the sysio chips device
	 * device registers and exports the mapping in the int-sized
	 * property "address".  Read in this address and pass it to
	 * the subsidiary *_init functions, so we don't create extra
	 * mappings to the same physical pages and we don't have to
	 * retrieve the more than once.
	 */
	/*
	 * Implement new policy to start ignoring the "address" property
	 * due to new requirements from DR.  The problem is that the contents
	 * of the "address" property contain vm mappings from OBP which needs
	 * to be recaptured into kernel vm.  Instead of relying on a blanket
	 * recapture during boot time, we map psycho registers each time during
	 * attach and unmap the during detach.  In some future point of time
	 * OBP will drop creating "address" property but this driver will
	 * will already not rely on this property any more.
	 */

	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
	attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
	if (ddi_regs_map_setup(softsp->dip, 0, &softsp->address, 0, 0,
	    &attr, &softsp->ac) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s%d: unable to map reg set 0\n",
		    ddi_get_name(softsp->dip),
		    ddi_get_instance(softsp->dip));
		return (0);
	}
	if (softsp->address == (caddr_t)-1) {
		cmn_err(CE_CONT, "?sbus%d: No sysio <address> property\n",
		    ddi_get_instance(softsp->dip));
		return (DDI_FAILURE);
	}

	DPRINTF(SBUS_ATTACH_DEBUG, ("sbus: devi=0x%p, softsp=0x%p\n",
	    (void *)devi, (void *)softsp));

#ifdef	notdef
	/*
	 * This bit of code, plus the firmware, will tell us if
	 * the #size-cells infrastructure code works, to some degree.
	 * You should be able to use the firmware to determine if
	 * the address returned by ddi_map_regs maps the correct phys. pages.
	 */

	{
		caddr_t addr;
		int rv;

		cmn_err(CE_CONT, "?sbus: address property = 0x%x\n", address);

		if ((rv = ddi_map_regs(softsp->dip, 0, &addr,
		    (off_t)0, (off_t)0)) != DDI_SUCCESS)  {
			cmn_err(CE_CONT, "?sbus: ddi_map_regs failed: %d\n",
			    rv);
		} else {
			cmn_err(CE_CONT, "?sbus: ddi_map_regs returned "
			    " virtual address 0x%x\n", addr);
		}
	}
#endif	/* notdef */

	if ((error = iommu_init(softsp, softsp->address)) != DDI_SUCCESS)
		goto bad;

	if ((error = sbus_init(softsp, softsp->address)) != DDI_SUCCESS)
		goto bad;

	if ((error = sysio_err_init(softsp, softsp->address)) != DDI_SUCCESS)
		goto bad;

	if ((error = stream_buf_init(softsp, softsp->address)) != DDI_SUCCESS)
		goto bad;

	/* Init the pokefault mutex for sbus devices */
	mutex_init(&softsp->pokefault_mutex, NULL, MUTEX_SPIN,
	    (void *)ipltospl(SBUS_ERR_PIL - 1));

	sbus_add_kstats(softsp);

	bus_func_register(BF_TYPE_RESINTR, sbus_intr_reset, devi);

	intr_dist_add(sbus_intrdist, devi);

	ddi_report_dev(devi);

	return (DDI_SUCCESS);

bad:
	ddi_soft_state_free(sbusp, instance);
	return (error);
}

/* ARGSUSED */
static int
sbus_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance;
	struct sbus_soft_state *softsp;
	uint64_t *cpr_softsp;

	switch (cmd) {
	case DDI_SUSPEND:
		/*
		 * Allocate the cpr  soft data structure to save the current
		 * state of the interrupt mapping registers.
		 * This structure will be deallocated after the system
		 * is resumed.
		 */
		instance = ddi_get_instance(devi);

		if (ddi_soft_state_zalloc(sbus_cprp, instance)
		    != DDI_SUCCESS)
			return (DDI_FAILURE);

		cpr_softsp = ddi_get_soft_state(sbus_cprp, instance);

		softsp = ddi_get_soft_state(sbusp, instance);

		sbus_cpr_handle_intr_map_reg(cpr_softsp,
		    softsp->intr_mapping_reg, 1);
		return (DDI_SUCCESS);

	case DDI_DETACH:
		return (sbus_do_detach(devi));
	default:
		return (DDI_FAILURE);
	}
}

static int
sbus_do_detach(dev_info_t *devi)
{
	int instance, pic;
	struct sbus_soft_state *softsp;

	instance = ddi_get_instance(devi);
	softsp = ddi_get_soft_state(sbusp, instance);
	ASSERT(softsp != NULL);

	bus_func_unregister(BF_TYPE_RESINTR, sbus_intr_reset, devi);

	intr_dist_rem(sbus_intrdist, devi);

	/* disable the streamming cache */
	if (stream_buf_uninit(softsp) == DDI_FAILURE) {
		goto err;
	}

	/* remove the interrupt handlers from the system */
	if (sysio_err_uninit(softsp) == DDI_FAILURE) {
		goto err;
	}

	/* disable the IOMMU */
	if (iommu_uninit(softsp)) {
		goto err;
	}

	/* unmap register space if we have a handle */
	if (softsp->ac) {
		ddi_regs_map_free(&softsp->ac);
		softsp->address = NULL;
	}

	/*
	 * remove counter kstats for this device
	 */
	if (softsp->sbus_counters_ksp != (kstat_t *)NULL)
		kstat_delete(softsp->sbus_counters_ksp);

	/*
	 * if we are the last instance to detach we need to
	 * remove the picN kstats. We use sbus_attachcnt as a
	 * count of how many instances are still attached. This
	 * is protected by a mutex.
	 */
	mutex_enter(&sbus_attachcnt_mutex);
	sbus_attachcnt --;
	if (sbus_attachcnt == 0) {
		for (pic = 0; pic < SBUS_NUM_PICS; pic++) {
			if (sbus_picN_ksp[pic] != (kstat_t *)NULL) {
				kstat_delete(sbus_picN_ksp[pic]);
				sbus_picN_ksp[pic] = NULL;
			}
		}
	}
	mutex_exit(&sbus_attachcnt_mutex);

#ifdef _STARFIRE
	/* free starfire specific soft intr mapping structure */
	pc_ittrans_uninit(softsp->ittrans_cookie);
#endif /* _STARFIRE */

	/* free the soft state structure */
	ddi_soft_state_free(sbusp, instance);

	return (DDI_SUCCESS);
err:
	return (DDI_FAILURE);
}

static int
sbus_init(struct sbus_soft_state *softsp, caddr_t address)
{
	int i;
	extern void set_intr_mapping_reg(int, uint64_t *, int);
	int numproxy;

	/*
	 * Simply add each registers offset to the base address
	 * to calculate the already mapped virtual address of
	 * the device register...
	 *
	 * define a macro for the pointer arithmetic; all registers
	 * are 64 bits wide and are defined as uint64_t's.
	 */

#define	REG_ADDR(b, o)	(uint64_t *)((caddr_t)(b) + (o))

	softsp->sysio_ctrl_reg = REG_ADDR(address, OFF_SYSIO_CTRL_REG);
	softsp->sbus_ctrl_reg = REG_ADDR(address, OFF_SBUS_CTRL_REG);
	softsp->sbus_slot_config_reg = REG_ADDR(address, OFF_SBUS_SLOT_CONFIG);
	softsp->intr_mapping_reg = REG_ADDR(address, OFF_INTR_MAPPING_REG);
	softsp->clr_intr_reg = REG_ADDR(address, OFF_CLR_INTR_REG);
	softsp->intr_retry_reg = REG_ADDR(address, OFF_INTR_RETRY_REG);
	softsp->sbus_intr_state = REG_ADDR(address, OFF_SBUS_INTR_STATE_REG);
	softsp->sbus_pcr = REG_ADDR(address, OFF_SBUS_PCR);
	softsp->sbus_pic = REG_ADDR(address, OFF_SBUS_PIC);

#undef	REG_ADDR

	DPRINTF(SBUS_REGISTERS_DEBUG, ("SYSIO Control reg: 0x%p\n"
	    "SBUS Control reg: 0x%p", (void *)softsp->sysio_ctrl_reg,
	    (void *)softsp->sbus_ctrl_reg));

#ifdef _STARFIRE
	/* Setup interrupt target translation for starfire */
	pc_ittrans_init(softsp->upa_id, &softsp->ittrans_cookie);
#endif /* _STARFIRE */

	softsp->intr_mapping_ign =
	    UPAID_TO_IGN(softsp->upa_id) << IMR_IGN_SHIFT;

	/* Diag reg 2 is the next 64 bit word after diag reg 1 */
	softsp->obio_intr_state = softsp->sbus_intr_state + 1;

	(void) sbus_resume_init(softsp, 0);

	/*
	 * Set the initial burstsizes for each slot to all 1's.  This will
	 * get changed at initchild time.
	 */
	for (i = 0; i < MAX_SBUS_SLOTS; i++)
		softsp->sbus_slave_burstsizes[i] = 0xffffffffu;

	/*
	 * Since SYSIO is used as an interrupt mastering device for slave
	 * only UPA devices, we call a dedicated kernel function to register
	 * The address of the interrupt mapping register for the slave device.
	 *
	 * If RISC/sysio is wired to support 2 upa slave interrupt
	 * devices then register 2nd mapping register with system.
	 * The slave/proxy portid algorithm (decribed in Fusion Desktop Spec)
	 * allows for upto 3 slaves per proxy but Psycho/SYSIO only support 2.
	 *
	 * #upa-interrupt-proxies property defines how many UPA interrupt
	 * slaves a bridge is wired to support. Older systems that lack
	 * this property will default to 1.
	 */
	numproxy = ddi_prop_get_int(DDI_DEV_T_ANY, softsp->dip,
	    DDI_PROP_DONTPASS, "#upa-interrupt-proxies", 1);

	if (numproxy > 0)
		set_intr_mapping_reg(softsp->upa_id,
		    (uint64_t *)(softsp->intr_mapping_reg +
		    FFB_MAPPING_REG), 1);

	if (numproxy > 1)
		set_intr_mapping_reg(softsp->upa_id,
		    (uint64_t *)(softsp->intr_mapping_reg +
		    EXP_MAPPING_REG), 2);

	/* support for a 3 interrupt proxy would go here */

	/* Turn on spurious interrupt counter if we're not a DEBUG kernel. */
#ifndef DEBUG
	intr_cntr_on = 1;
#else
	intr_cntr_on = 0;
#endif


	return (DDI_SUCCESS);
}

/*
 * This procedure is part of sbus initialization. It is called by
 * sbus_init() and is invoked when the system is being resumed.
 */
static int
sbus_resume_init(struct sbus_soft_state *softsp, int resume)
{
	int i;
	uint_t sbus_burst_sizes;

	/*
	 * This shouldn't be needed when we have a real OBP PROM.
	 * (RAZ) Get rid of this later!!!
	 */

#ifdef _STARFIRE
	/*
	 * For Starfire, we need to program a
	 * constant odd value.
	 * Zero out the MID field before ORing
	 * We leave the LSB of the MID field intact since
	 * we cannot have a zero(even) MID value
	 */
	uint64_t tmpconst = 0x1DULL;
	*softsp->sysio_ctrl_reg &= 0xFF0FFFFFFFFFFFFFULL;
	*softsp->sysio_ctrl_reg |= tmpconst << 51;

	/*
	 * Program in the interrupt group number
	 * Here we have to convert the starfire
	 * 7 bit upaid into a 5bit value.
	 */
	*softsp->sysio_ctrl_reg |=
	    (uint64_t)STARFIRE_UPAID2HWIGN(softsp->upa_id)
	    << SYSIO_IGN;
#else
	/* for the rest of sun4u's */
	*softsp->sysio_ctrl_reg |=
	    (uint64_t)softsp->upa_id << 51;

	/* Program in the interrupt group number */
	*softsp->sysio_ctrl_reg |=
	    (uint64_t)softsp->upa_id << SYSIO_IGN;
#endif /* _STARFIRE */

	/*
	 * Set appropriate fields of sbus control register.
	 * Set DVMA arbitration enable for all devices.
	 */
	*softsp->sbus_ctrl_reg |= SBUS_ARBIT_ALL;

	/* Calculate our burstsizes now so we don't have to do it later */
	sbus_burst_sizes = (SYSIO64_BURST_RANGE << SYSIO64_BURST_SHIFT)
	    | SYSIO_BURST_RANGE;

	sbus_burst_sizes = ddi_getprop(DDI_DEV_T_ANY, softsp->dip,
	    DDI_PROP_DONTPASS, "up-burst-sizes", sbus_burst_sizes);

	softsp->sbus_burst_sizes = sbus_burst_sizes & SYSIO_BURST_MASK;
	softsp->sbus64_burst_sizes = sbus_burst_sizes & SYSIO64_BURST_MASK;

	if (!resume) {
		/* Set burstsizes to smallest value */
		for (i = 0; i < MAX_SBUS_SLOTS; i++) {
			volatile uint64_t *config;
			uint64_t tmpreg;

			config = softsp->sbus_slot_config_reg + i;

			/* Write out the burst size */
			tmpreg = (uint64_t)0;
			*config = tmpreg;

			/* Flush any write buffers */
			tmpreg = *softsp->sbus_ctrl_reg;

			DPRINTF(SBUS_REGISTERS_DEBUG, ("Sbus slot 0x%x slot "
			    "configuration reg: 0x%p", (i > 3) ? i + 9 : i,
			    (void *)config));
		}
	} else {
		/* Program the slot configuration registers */
		for (i = 0; i < MAX_SBUS_SLOTS; i++) {
			volatile uint64_t *config;
#ifndef lint
			uint64_t tmpreg;
#endif /* !lint */
			uint_t slave_burstsizes;

			slave_burstsizes = 0;
			if (softsp->sbus_slave_burstsizes[i] != 0xffffffffu) {
				config = softsp->sbus_slot_config_reg + i;

				if (softsp->sbus_slave_burstsizes[i] &
				    SYSIO64_BURST_MASK) {
					/* get the 64 bit burstsizes */
					slave_burstsizes =
					    softsp->sbus_slave_burstsizes[i] >>
					    SYSIO64_BURST_SHIFT;

					/* Turn on 64 bit PIO's on the sbus */
					*config |= SBUS_ETM;
				} else {
					slave_burstsizes =
					    softsp->sbus_slave_burstsizes[i] &
					    SYSIO_BURST_MASK;
				}

				/* Get burstsizes into sysio register format */
				slave_burstsizes >>= SYSIO_SLAVEBURST_REGSHIFT;

				/* Program the burstsizes */
				*config |= (uint64_t)slave_burstsizes;

				/* Flush any write buffers */
#ifndef lint
				tmpreg = *softsp->sbus_ctrl_reg;
#endif /* !lint */
			}
		}
	}

	return (DDI_SUCCESS);
}

#define	get_prop(di, pname, flag, pval, plen)	\
	(ddi_prop_op(DDI_DEV_T_NONE, di, PROP_LEN_AND_VAL_ALLOC, \
	flag | DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, \
	pname, (caddr_t)pval, plen))

struct prop_ispec {
	uint_t	pri, vec;
};

/*
 * Create a sysio_parent_private_data structure from the ddi properties of
 * the dev_info node.
 *
 * The "reg" and either an "intr" or "interrupts" properties are required
 * if the driver wishes to create mappings or field interrupts on behalf
 * of the device.
 *
 * The "reg" property is assumed to be a list of at least one triple
 *
 *	<bustype, address, size>*1
 *
 * On pre-fusion machines, the "intr" property was the IPL for the system.
 * Most new sbus devices post an "interrupts" property that corresponds to
 * a particular bus level.  All devices on fusion using an "intr" property
 * will have it's contents translated into a bus level.  Hence, "intr" and
 * "interrupts on the fusion platform can be treated the same.
 *
 * The "interrupts" property is assumed to be a list of at least one
 * n-tuples that describes the interrupt capabilities of the bus the device
 * is connected to.  For SBus, this looks like
 *
 *	<SBus-level>*1
 *
 * (This property obsoletes the 'intr' property).
 *
 * The OBP_RANGES property is optional.
 */
static void
make_sbus_ppd(dev_info_t *child)
{
	struct sysio_parent_private_data *pdptr;
	int n;
	int *reg_prop, *rgstr_prop, *rng_prop;
	int reg_len, rgstr_len, rng_len;

	/*
	 * Make the function idempotent, because name_child could
	 * be called multiple times on a node.
	 */
	if (ddi_get_parent_data(child) != NULL)
		return;

	pdptr = kmem_zalloc(sizeof (*pdptr), KM_SLEEP);
	ddi_set_parent_data(child, pdptr);

	/*
	 * Handle the 'reg'/'registers' properties.
	 * "registers" overrides "reg", but requires that "reg" be exported,
	 * so we can handle wildcard specifiers.  "registers" implies an
	 * sbus style device.  "registers" implies that we insert the
	 * correct value in the regspec_bustype field of each spec for a real
	 * (non-pseudo) device node.  "registers" is a s/w only property, so
	 * we inhibit the prom search for this property.
	 */
	if (get_prop(child, OBP_REG, 0, &reg_prop, &reg_len) != DDI_SUCCESS)
		reg_len = 0;

	/*
	 * Save the underlying slot number and slot offset.
	 * Among other things, we use these to name the child node.
	 */
	pdptr->slot = (uint_t)-1;
	if (reg_len != 0) {
		pdptr->slot = ((struct regspec *)reg_prop)->regspec_bustype;
		pdptr->offset = ((struct regspec *)reg_prop)->regspec_addr;
	}

	rgstr_len = 0;
	(void) get_prop(child, "registers", DDI_PROP_NOTPROM,
	    &rgstr_prop, &rgstr_len);

	if (rgstr_len != 0)  {
		if (ndi_dev_is_persistent_node(child) && (reg_len != 0))  {
			/*
			 * Convert wildcard "registers" for a real node...
			 * (Else, this is the wildcard prototype node)
			 */
			struct regspec *rp = (struct regspec *)reg_prop;
			uint_t slot = rp->regspec_bustype;
			int i;

			rp = (struct regspec *)rgstr_prop;
			n = rgstr_len / sizeof (struct regspec);
			for (i = 0; i < n; ++i, ++rp)
				rp->regspec_bustype = slot;
		}

		if (reg_len != 0)
			kmem_free(reg_prop, reg_len);

		reg_prop = rgstr_prop;
		reg_len = rgstr_len;
	}
	if (reg_len != 0)  {
		pdptr->par_nreg = reg_len / (int)sizeof (struct regspec);
		pdptr->par_reg = (struct regspec *)reg_prop;
	}

	/*
	 * See if I have ranges.
	 */
	if (get_prop(child, OBP_RANGES, 0, &rng_prop, &rng_len) ==
	    DDI_SUCCESS) {
		pdptr->par_nrng = rng_len / (int)(sizeof (struct rangespec));
		pdptr->par_rng = (struct rangespec *)rng_prop;
	}
}

/*
 * Special handling for "sbusmem" pseudo device nodes.
 * The special handling automatically creates the "reg"
 * property in the sbusmem nodes, based on the parent's
 * property so that each slot will automtically have a
 * correctly sized "reg" property, once created,
 * sbus_initchild does the rest of the work to init
 * the child node.
 */
static int
sbusmem_initchild(dev_info_t *dip, dev_info_t *child)
{
	int i, n;
	int slot, size;
	char ident[10];

	slot = ddi_getprop(DDI_DEV_T_NONE, child,
	    DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "slot", -1);
	if (slot == -1) {
		DPRINTF(SBUS_SBUSMEM_DEBUG, ("can't get slot property\n"));
		return (DDI_FAILURE);
	}

	/*
	 * Find the parent range corresponding to this "slot",
	 * so we can set the size of the child's "reg" property.
	 */
	for (i = 0, n = sparc_pd_getnrng(dip); i < n; i++) {
		struct rangespec *rp = sparc_pd_getrng(dip, i);

		if (rp->rng_cbustype == (uint_t)slot) {
			struct regspec r;

			/* create reg property */

			r.regspec_bustype = (uint_t)slot;
			r.regspec_addr = 0;
			r.regspec_size = rp->rng_size;
			(void) ddi_prop_update_int_array(DDI_DEV_T_NONE,
			    child, "reg", (int *)&r,
			    sizeof (struct regspec) / sizeof (int));

			/* create size property for slot */

			size = rp->rng_size;
			(void) ddi_prop_update_int(DDI_DEV_T_NONE,
			    child, "size", size);

			(void) sprintf(ident, "slot%x", slot);
			(void) ddi_prop_update_string(DDI_DEV_T_NONE,
			    child, "ident", ident);

			return (DDI_SUCCESS);
		}
	}
	return (DDI_FAILURE);
}

/*
 * Nexus routine to name a child.
 * It takes a dev_info node and a buffer, returns the name
 * in the buffer.
 */
static int
sysio_name_child(dev_info_t *child, char *name, int namelen)
{
	/*
	 * Fill in parent-private data
	 */
	make_sbus_ppd(child);

	/*
	 * Name the device node using the underlying (prom) values
	 * of the first entry in the "reg" property.  For SBus devices,
	 * the textual form of the name is <name>@<slot#>,<offset>.
	 * This must match the prom's pathname or mountroot, etc, won't
	 */
	name[0] = '\0';
	if (sysio_pd_getslot(child) != (uint_t)-1) {
		(void) snprintf(name, namelen, "%x,%x",
		    sysio_pd_getslot(child), sysio_pd_getoffset(child));
	}
	return (DDI_SUCCESS);
}

/*
 * Called from the bus_ctl op of sysio sbus nexus driver
 * to implement the DDI_CTLOPS_INITCHILD operation.  That is, it names
 * the children of sysio sbusses based on the reg spec.
 *
 * Handles the following properties:
 *
 *	Property		value
 *	  Name			type
 *
 *	reg		register spec
 *	registers	wildcard s/w sbus register spec (.conf file property)
 *	intr		old-form interrupt spec
 *	interrupts	new (bus-oriented) interrupt spec
 *	ranges		range spec
 */
static int
sbus_initchild(dev_info_t *dip, dev_info_t *child)
{
	char name[MAXNAMELEN];
	ulong_t slave_burstsizes;
	int slot;
	volatile uint64_t *slot_reg;
#ifndef lint
	uint64_t tmp;
#endif /* !lint */
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));

	if (strcmp(ddi_get_name(child), "sbusmem") == 0) {
		if (sbusmem_initchild(dip, child) != DDI_SUCCESS)
			return (DDI_FAILURE);
	}

	/*
	 * If this is a s/w node defined with the "registers" property,
	 * this means that this is a wildcard specifier, whose properties
	 * get applied to all previously defined h/w nodes with the same
	 * name and same parent.
	 */
	if (ndi_dev_is_persistent_node(child) == 0) {
		int len = 0;
		if ((ddi_getproplen(DDI_DEV_T_ANY, child, DDI_PROP_NOTPROM,
		    "registers", &len) == DDI_SUCCESS) && (len != 0)) {
			ndi_merge_wildcard_node(child);
			return (DDI_FAILURE);
		}
	}

	/* name the child */
	(void) sysio_name_child(child, name, MAXNAMELEN);
	ddi_set_name_addr(child, name);

	/*
	 * If a pseudo node, attempt to merge it into a hw node.
	 * If merge is successful, we uinitialize the node and
	 * return failure, to allow caller to remove the node.
	 * The merge fails, this is a real pseudo node. Allow
	 * initchild to continue.
	 */
	if ((ndi_dev_is_persistent_node(child) == 0) &&
	    (ndi_merge_node(child, sysio_name_child) == DDI_SUCCESS)) {
		(void) sbus_uninitchild(child);
		return (DDI_FAILURE);
	}

	/* Figure out the child devices slot number */
	slot = sysio_pd_getslot(child);

	/* If we don't have a reg property, bypass slot specific programming */
	if (slot < 0 || slot >= MAX_SBUS_SLOT_ADDR) {
#ifdef DEBUG
		cmn_err(CE_WARN, "?Invalid sbus slot address 0x%x for %s "
		    "device\n", slot, ddi_get_name(child));
#endif /* DEBUG */
		goto done;
	}

	/* Modify the onboard slot numbers if applicable. */
	slot = (slot > 3) ? slot - 9 : slot;

	/* Get the slot configuration register for the child device. */
	slot_reg = softsp->sbus_slot_config_reg + slot;

	/*
	 * Program the devices slot configuration register for the
	 * appropriate slave burstsizes.
	 * The upper 16 bits of the slave-burst-sizes are for 64 bit sbus
	 * and the lower 16 bits are the burst sizes for 32 bit sbus. If
	 * we see that a device supports both 64 bit and 32 bit slave accesses,
	 * we default to 64 bit and turn it on in the slot config reg.
	 *
	 * For older devices, make sure we check the "burst-sizes" property
	 * too.
	 */
	if ((slave_burstsizes = (ulong_t)ddi_getprop(DDI_DEV_T_ANY, child,
	    DDI_PROP_DONTPASS, "slave-burst-sizes", 0)) != 0 ||
	    (slave_burstsizes = (ulong_t)ddi_getprop(DDI_DEV_T_ANY, child,
	    DDI_PROP_DONTPASS, "burst-sizes", 0)) != 0) {
		uint_t burstsizes = 0;

		/*
		 * If we only have 32 bit burst sizes from a previous device,
		 * mask out any burstsizes for 64 bit mode.
		 */
		if (((softsp->sbus_slave_burstsizes[slot] &
		    0xffff0000u) == 0) &&
		    ((softsp->sbus_slave_burstsizes[slot] & 0xffff) != 0)) {
			slave_burstsizes &= 0xffff;
		}

		/*
		 * If "slave-burst-sizes was defined but we have 0 at this
		 * point, we must have had 64 bit burstsizes, however a prior
		 * device can only burst in 32 bit mode.  Therefore, we leave
		 * the burstsizes in the 32 bit mode and disregard the 64 bit.
		 */
		if (slave_burstsizes == 0)
			goto done;

		/*
		 * We and in the new burst sizes with that of prior devices.
		 * This ensures that we always take the least common
		 * denominator of the burst sizes.
		 */
		softsp->sbus_slave_burstsizes[slot] &=
		    (slave_burstsizes &
		    ((SYSIO64_SLAVEBURST_RANGE <<
		    SYSIO64_BURST_SHIFT) |
		    SYSIO_SLAVEBURST_RANGE));

		/* Get the 64 bit burstsizes. */
		if (softsp->sbus_slave_burstsizes[slot] &
		    SYSIO64_BURST_MASK) {
			/* get the 64 bit burstsizes */
			burstsizes = softsp->sbus_slave_burstsizes[slot] >>
			    SYSIO64_BURST_SHIFT;

			/* Turn on 64 bit PIO's on the sbus */
			*slot_reg |= SBUS_ETM;
		} else {
			/* Turn off 64 bit PIO's on the sbus */
			*slot_reg &= ~SBUS_ETM;

			/* Get the 32 bit burstsizes if we don't have 64 bit. */
			if (softsp->sbus_slave_burstsizes[slot] &
			    SYSIO_BURST_MASK) {
				burstsizes =
				    softsp->sbus_slave_burstsizes[slot] &
				    SYSIO_BURST_MASK;
			}
		}

		/* Get the burstsizes into sysio register format */
		burstsizes >>= SYSIO_SLAVEBURST_REGSHIFT;

		/* Reset reg in case we're scaling back */
		*slot_reg &= (uint64_t)~SYSIO_SLAVEBURST_MASK;

		/* Program the burstsizes */
		*slot_reg |= (uint64_t)burstsizes;

		/* Flush system load/store buffers */
#ifndef lint
		tmp = *slot_reg;
#endif /* !lint */
	}

done:
	return (DDI_SUCCESS);
}

static int
sbus_uninitchild(dev_info_t *dip)
{
	struct sysio_parent_private_data *pdptr;
	size_t n;

	if ((pdptr = ddi_get_parent_data(dip)) != NULL)  {
		if ((n = (size_t)pdptr->par_nrng) != 0)
			kmem_free(pdptr->par_rng, n *
			    sizeof (struct rangespec));

		if ((n = pdptr->par_nreg) != 0)
			kmem_free(pdptr->par_reg, n * sizeof (struct regspec));

		kmem_free(pdptr, sizeof (*pdptr));
		ddi_set_parent_data(dip, NULL);
	}
	ddi_set_name_addr(dip, NULL);
	/*
	 * Strip the node to properly convert it back to prototype form
	 */
	ddi_remove_minor_node(dip, NULL);
	impl_rem_dev_props(dip);
	return (DDI_SUCCESS);
}

#ifdef  DEBUG
int	sbus_peekfault_cnt = 0;
int	sbus_pokefault_cnt = 0;
#endif  /* DEBUG */

static int
sbus_ctlops_poke(struct sbus_soft_state *softsp, peekpoke_ctlops_t *in_args)
{
	int err = DDI_SUCCESS;
	on_trap_data_t otd;
	volatile uint64_t tmpreg;

	/* Cautious access not supported. */
	if (in_args->handle != NULL)
		return (DDI_FAILURE);

	mutex_enter(&softsp->pokefault_mutex);
	softsp->ontrap_data = &otd;

	/* Set up protected environment. */
	if (!on_trap(&otd, OT_DATA_ACCESS)) {
		uintptr_t tramp = otd.ot_trampoline;

		otd.ot_trampoline = (uintptr_t)&poke_fault;
		err = do_poke(in_args->size, (void *)in_args->dev_addr,
		    (void *)in_args->host_addr);
		otd.ot_trampoline = tramp;
	} else
		err = DDI_FAILURE;

	/* Flush any sbus store buffers. */
	tmpreg = *softsp->sbus_ctrl_reg;

	/*
	 * Read the sbus error reg and see if a fault occured.  If
	 * one has, give the SYSIO time to packetize the interrupt
	 * for the fault and send it out.  The sbus error handler will
	 * 0 these fields when it's called to service the fault.
	 */
	tmpreg = *softsp->sbus_err_reg;
	while (tmpreg & SB_AFSR_P_TO || tmpreg & SB_AFSR_P_BERR)
		tmpreg = *softsp->sbus_err_reg;

	/* Take down protected environment. */
	no_trap();

	softsp->ontrap_data = NULL;
	mutex_exit(&softsp->pokefault_mutex);

#ifdef  DEBUG
	if (err == DDI_FAILURE)
		sbus_pokefault_cnt++;
#endif
	return (err);
}

/*ARGSUSED*/
static int
sbus_ctlops_peek(struct sbus_soft_state *softsp, peekpoke_ctlops_t *in_args,
    void *result)
{
	int err = DDI_SUCCESS;
	on_trap_data_t otd;

	/* No safe access except for peek is supported. */
	if (in_args->handle != NULL)
		return (DDI_FAILURE);

	if (!on_trap(&otd, OT_DATA_ACCESS)) {
		uintptr_t tramp = otd.ot_trampoline;

		otd.ot_trampoline = (uintptr_t)&peek_fault;
		err = do_peek(in_args->size, (void *)in_args->dev_addr,
		    (void *)in_args->host_addr);
		otd.ot_trampoline = tramp;
		result = (void *)in_args->host_addr;
	} else
		err = DDI_FAILURE;

#ifdef  DEBUG
	if (err == DDI_FAILURE)
		sbus_peekfault_cnt++;
#endif
	no_trap();
	return (err);
}

static int
sbus_ctlops(dev_info_t *dip, dev_info_t *rdip,
    ddi_ctl_enum_t op, void *arg, void *result)
{
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));

	switch (op) {

	case DDI_CTLOPS_INITCHILD:
		return (sbus_initchild(dip, (dev_info_t *)arg));

	case DDI_CTLOPS_UNINITCHILD:
		return (sbus_uninitchild(arg));

	case DDI_CTLOPS_IOMIN: {
		int val = *((int *)result);

		/*
		 * The 'arg' value of nonzero indicates 'streaming' mode.
		 * If in streaming mode, pick the largest of our burstsizes
		 * available and say that that is our minimum value (modulo
		 * what mincycle is).
		 */
		if ((int)(uintptr_t)arg)
			val = maxbit(val,
			    (1 << (ddi_fls(softsp->sbus_burst_sizes) - 1)));
		else
			val = maxbit(val,
			    (1 << (ddi_ffs(softsp->sbus_burst_sizes) - 1)));

		*((int *)result) = val;
		return (ddi_ctlops(dip, rdip, op, arg, result));
	}

	case DDI_CTLOPS_REPORTDEV: {
		dev_info_t *pdev;
		int i, n, len, f_len;
		char *msgbuf;

	/*
	 * So we can do one atomic cmn_err call, we allocate a 4k
	 * buffer, and format the reportdev message into that buffer,
	 * send it to cmn_err, and then free the allocated buffer.
	 * If message is longer than 1k, the message is truncated and
	 * an error message is emitted (debug kernel only).
	 */
#define	REPORTDEV_BUFSIZE	1024

		int sbusid = ddi_get_instance(dip);

		if (ddi_get_parent_data(rdip) == NULL)
			return (DDI_FAILURE);

		msgbuf = kmem_zalloc(REPORTDEV_BUFSIZE, KM_SLEEP);

		pdev = ddi_get_parent(rdip);
		f_len = snprintf(msgbuf, REPORTDEV_BUFSIZE,
		    "%s%d at %s%d: SBus%d ",
		    ddi_driver_name(rdip), ddi_get_instance(rdip),
		    ddi_driver_name(pdev), ddi_get_instance(pdev), sbusid);
		len = strlen(msgbuf);

		for (i = 0, n = sysio_pd_getnreg(rdip); i < n; i++) {
			struct regspec *rp;

			rp = sysio_pd_getreg(rdip, i);
			if (i != 0) {
				f_len += snprintf(msgbuf + len,
				    REPORTDEV_BUFSIZE - len, " and ");
				len = strlen(msgbuf);
			}

			f_len += snprintf(msgbuf + len, REPORTDEV_BUFSIZE - len,
			    "slot 0x%x offset 0x%x",
			    rp->regspec_bustype, rp->regspec_addr);
			len = strlen(msgbuf);
		}

		for (i = 0, n = i_ddi_get_intx_nintrs(rdip); i < n; i++) {
			uint32_t sbuslevel, inum, pri;

			if (i != 0) {
				f_len += snprintf(msgbuf + len,
				    REPORTDEV_BUFSIZE - len, ",");
				len = strlen(msgbuf);
			}

			sbuslevel = inum = i_ddi_get_inum(rdip, i);
			pri = i_ddi_get_intr_pri(rdip, i);

			(void) sbus_xlate_intrs(dip, rdip, &inum,
			    &pri, softsp->intr_mapping_ign);

			if (sbuslevel > MAX_SBUS_LEVEL)
				f_len += snprintf(msgbuf + len,
				    REPORTDEV_BUFSIZE - len,
				    " Onboard device ");
			else
				f_len += snprintf(msgbuf + len,
				    REPORTDEV_BUFSIZE - len, " SBus level %d ",
				    sbuslevel);
			len = strlen(msgbuf);

			f_len += snprintf(msgbuf + len, REPORTDEV_BUFSIZE - len,
			    "sparc9 ipl %d", pri);
			len = strlen(msgbuf);
		}
#ifdef DEBUG
	if (f_len + 1 >= REPORTDEV_BUFSIZE) {
		cmn_err(CE_NOTE, "next message is truncated: "
		    "printed length 1024, real length %d", f_len);
	}
#endif /* DEBUG */

		cmn_err(CE_CONT, "?%s\n", msgbuf);
		kmem_free(msgbuf, REPORTDEV_BUFSIZE);
		return (DDI_SUCCESS);

#undef	REPORTDEV_BUFSIZE
	}

	case DDI_CTLOPS_SLAVEONLY:
		return (DDI_FAILURE);

	case DDI_CTLOPS_AFFINITY: {
		dev_info_t *dipb = (dev_info_t *)arg;
		int r_slot, b_slot;

		if ((b_slot = find_sbus_slot(dip, dipb)) < 0)
			return (DDI_FAILURE);

		if ((r_slot = find_sbus_slot(dip, rdip)) < 0)
			return (DDI_FAILURE);

		return ((b_slot == r_slot)? DDI_SUCCESS : DDI_FAILURE);

	}
	case DDI_CTLOPS_DMAPMAPC:
		cmn_err(CE_CONT, "?DDI_DMAPMAPC called!!\n");
		return (DDI_FAILURE);

	case DDI_CTLOPS_POKE:
		return (sbus_ctlops_poke(softsp, (peekpoke_ctlops_t *)arg));

	case DDI_CTLOPS_PEEK:
		return (sbus_ctlops_peek(softsp, (peekpoke_ctlops_t *)arg,
		    result));

	case DDI_CTLOPS_DVMAPAGESIZE:
		*(ulong_t *)result = IOMMU_PAGESIZE;
		return (DDI_SUCCESS);

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

static int
find_sbus_slot(dev_info_t *dip, dev_info_t *rdip)
{
	dev_info_t *child;
	int slot = -1;

	/*
	 * look for the node that's a direct child of this Sbus node.
	 */
	while (rdip && (child = ddi_get_parent(rdip)) != dip) {
		rdip = child;
	}

	/*
	 * If there is one, get the slot number of *my* child
	 */
	if (child == dip)
		slot = sysio_pd_getslot(rdip);

	return (slot);
}

/*
 * This is the sbus interrupt routine wrapper function.  This function
 * installs itself as a child devices interrupt handler.  It's function is
 * to dispatch a child devices interrupt handler, and then
 * reset the interrupt clear register for the child device.
 *
 * Warning: This routine may need to be implemented as an assembly level
 * routine to improve performance.
 */

#define	MAX_INTR_CNT 10

static uint_t
sbus_intr_wrapper(caddr_t arg)
{
	uint_t intr_return = DDI_INTR_UNCLAIMED;
	volatile uint64_t tmpreg;
	struct sbus_wrapper_arg *intr_info;
	struct sbus_intr_handler *intr_handler;
	uchar_t *spurious_cntr;

	intr_info = (struct sbus_wrapper_arg *)arg;
	spurious_cntr = &intr_info->softsp->spurious_cntrs[intr_info->pil];
	intr_handler = intr_info->handler_list;

	while (intr_handler) {
		caddr_t arg1 = intr_handler->arg1;
		caddr_t arg2 = intr_handler->arg2;
		uint_t (*funcp)() = intr_handler->funcp;
		dev_info_t *dip = intr_handler->dip;
		int r;

		if (intr_handler->intr_state == SBUS_INTR_STATE_DISABLE) {
			intr_handler = intr_handler->next;
			continue;
		}

		DTRACE_PROBE4(interrupt__start, dev_info_t, dip,
		    void *, funcp, caddr_t, arg1, caddr_t, arg2);

		r = (*funcp)(arg1, arg2);

		DTRACE_PROBE4(interrupt__complete, dev_info_t, dip,
		    void *, funcp, caddr_t, arg1, int, r);

		intr_return |= r;
		intr_handler = intr_handler->next;
	}

	/* Set the interrupt state machine to idle */
	tmpreg = *intr_info->softsp->sbus_ctrl_reg;
	tmpreg = SBUS_INTR_IDLE;
	*intr_info->clear_reg = tmpreg;
	tmpreg = *intr_info->softsp->sbus_ctrl_reg;

	if (intr_return == DDI_INTR_UNCLAIMED) {
		(*spurious_cntr)++;

		if (*spurious_cntr < MAX_INTR_CNT) {
			if (intr_cntr_on)
				return (DDI_INTR_CLAIMED);
		}
#ifdef DEBUG
		else if (intr_info->pil >= LOCK_LEVEL) {
			cmn_err(CE_PANIC, "%d unclaimed interrupts at "
			    "interrupt level %d", MAX_INTR_CNT,
			    intr_info->pil);
		}
#endif

		/*
		 * Reset spurious counter once we acknowledge
		 * it to the system level.
		 */
		*spurious_cntr = (uchar_t)0;
	} else {
		*spurious_cntr = (uchar_t)0;
	}

	return (intr_return);
}

/*
 * add_intrspec - Add an interrupt specification.
 */
static int
sbus_add_intr_impl(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp)
{
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));
	volatile uint64_t *mondo_vec_reg;
	volatile uint64_t tmp_mondo_vec;
	volatile uint64_t *intr_state_reg;
	volatile uint64_t tmpreg;	/* HW flush reg */
	uint_t start_bit;
	int ino;
	uint_t cpu_id;
	struct sbus_wrapper_arg *sbus_arg;
	struct sbus_intr_handler *intr_handler;
	uint32_t slot;
	/* Interrupt state machine reset flag */
	int reset_ism_register = 1;
	int ret = DDI_SUCCESS;

	/* Check if we have a valid sbus slot address */
	if (((slot = (uint_t)find_sbus_slot(dip, rdip)) >=
	    MAX_SBUS_SLOT_ADDR) || (slot < (uint_t)0)) {
		cmn_err(CE_WARN, "Invalid sbus slot 0x%x during add intr\n",
		    slot);
		return (DDI_FAILURE);
	}

	DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: sbus interrupt %d "
	    "for device %s%d\n", hdlp->ih_vector, ddi_driver_name(rdip),
	    ddi_get_instance(rdip)));

	/* Xlate the interrupt */
	if (sbus_xlate_intrs(dip, rdip, (uint32_t *)&hdlp->ih_vector,
	    &hdlp->ih_pri, softsp->intr_mapping_ign) == DDI_FAILURE) {
		cmn_err(CE_WARN, "Can't xlate SBUS devices %s interrupt.\n",
		    ddi_driver_name(rdip));
		return (DDI_FAILURE);
	}

	/* get the ino number */
	ino = hdlp->ih_vector & SBUS_MAX_INO;
	mondo_vec_reg = (softsp->intr_mapping_reg +
	    ino_table[ino]->mapping_reg);

	/*
	 * This is an intermediate step in identifying
	 * the exact bits which represent the device in the interrupt
	 * state diagnostic register.
	 */
	if (ino > MAX_MONDO_EXTERNAL) {
		start_bit = ino_table[ino]->diagreg_shift;
		intr_state_reg = softsp->obio_intr_state;
	} else {
		start_bit = 16 * (ino >> 3) + 2 * (ino & 0x7);
		intr_state_reg = softsp->sbus_intr_state;
	}


	/* Allocate a nexus interrupt data structure */
	intr_handler = kmem_zalloc(sizeof (struct sbus_intr_handler), KM_SLEEP);
	intr_handler->dip = rdip;
	intr_handler->funcp = hdlp->ih_cb_func;
	intr_handler->arg1 = hdlp->ih_cb_arg1;
	intr_handler->arg2 = hdlp->ih_cb_arg2;
	intr_handler->inum = hdlp->ih_inum;

	DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: xlated interrupt 0x%x "
	    "intr_handler 0x%p\n", hdlp->ih_vector, (void *)intr_handler));

	/*
	 * Grab this lock here. So it will protect the poll list.
	 */
	mutex_enter(&softsp->intr_poll_list_lock);

	sbus_arg = softsp->intr_list[ino];
	/* Check if we have a poll list to deal with */
	if (sbus_arg) {
		tmp_mondo_vec = *mondo_vec_reg;
		tmp_mondo_vec &= ~INTERRUPT_VALID;
		*mondo_vec_reg = tmp_mondo_vec;

		tmpreg = *softsp->sbus_ctrl_reg;
#ifdef	lint
		tmpreg = tmpreg;
#endif

		DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr:sbus_arg exists "
		    "0x%p\n", (void *)sbus_arg));
		/*
		 * Two bits per ino in the diagnostic register
		 * indicate the status of its interrupt.
		 * 0 - idle, 1 - transmit, 3 - pending.
		 */
		while (((*intr_state_reg >>
		    start_bit) & 0x3) == INT_PENDING && !panicstr)
			/* empty */;

		intr_handler->next = sbus_arg->handler_list;
		sbus_arg->handler_list = intr_handler;

		reset_ism_register = 0;
	} else {
		sbus_arg = kmem_zalloc(sizeof (struct sbus_wrapper_arg),
		    KM_SLEEP);

		softsp->intr_list[ino] = sbus_arg;
		sbus_arg->clear_reg = (softsp->clr_intr_reg +
		    ino_table[ino]->clear_reg);
		DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr:Ino 0x%x Interrupt "
		    "clear reg: 0x%p\n", ino, (void *)sbus_arg->clear_reg));
		sbus_arg->softsp = softsp;
		sbus_arg->handler_list = intr_handler;

		/*
		 * No handler added yet in the interrupt vector
		 * table for this ino.
		 * Install the nexus interrupt wrapper in the
		 * system. The wrapper will call the device
		 * interrupt handler.
		 */
		DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp,
		    (ddi_intr_handler_t *)sbus_intr_wrapper,
		    (caddr_t)sbus_arg, NULL);

		ret = i_ddi_add_ivintr(hdlp);

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

		if (ret != DDI_SUCCESS) {
			mutex_exit(&softsp->intr_poll_list_lock);
			goto done;
		}

		if ((slot >= EXT_SBUS_SLOTS) ||
		    (softsp->intr_hndlr_cnt[slot] == 0)) {

			cpu_id = intr_dist_cpuid();
#ifdef	_STARFIRE
			tmp_mondo_vec = pc_translate_tgtid(
			    softsp->ittrans_cookie, cpu_id,
			    mondo_vec_reg) << IMR_TID_SHIFT;
#else
			tmp_mondo_vec =
			    cpu_id << IMR_TID_SHIFT;
			DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: initial "
			    "mapping reg 0x%lx\n", tmp_mondo_vec));
#endif	/* _STARFIRE */
		} else {
			/*
			 * There is already a different
			 * ino programmed at this IMR.
			 * Just read the IMR out to get the
			 * correct MID target.
			 */
			tmp_mondo_vec = *mondo_vec_reg;
			tmp_mondo_vec &= ~INTERRUPT_VALID;
			*mondo_vec_reg = tmp_mondo_vec;
			DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: existing "
			    "mapping reg 0x%lx\n", tmp_mondo_vec));
		}

		sbus_arg->pil = hdlp->ih_pri;

		DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr:Alloc sbus_arg "
		    "0x%p\n", (void *)sbus_arg));
	}

	softsp->intr_hndlr_cnt[slot]++;

	mutex_exit(&softsp->intr_poll_list_lock);

	/*
	 * Program the ino vector accordingly.  This MUST be the
	 * last thing we do.  Once we program the ino, the device
	 * may begin to interrupt. Add this hardware interrupt to
	 * the interrupt lists, and get the CPU to target it at.
	 */

	tmp_mondo_vec |= INTERRUPT_VALID;

	DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: Ino 0x%x mapping reg: 0x%p "
	    "Intr cntr %d\n", ino, (void *)mondo_vec_reg,
	    softsp->intr_hndlr_cnt[slot]));

	/* Force the interrupt state machine to idle. */
	if (reset_ism_register) {
		tmpreg = SBUS_INTR_IDLE;
		*sbus_arg->clear_reg = tmpreg;
	}

	/* Store it in the hardware reg. */
	*mondo_vec_reg = tmp_mondo_vec;

	/* Flush store buffers */
	tmpreg = *softsp->sbus_ctrl_reg;

done:
	return (ret);
}

static void
sbus_free_handler(dev_info_t *dip, uint32_t inum,
    struct sbus_wrapper_arg *sbus_arg)
{
	struct sbus_intr_handler *listp, *prevp;

	if (sbus_arg) {
		prevp = NULL;
		listp = sbus_arg->handler_list;

		while (listp) {
			if (listp->dip == dip && listp->inum == inum) {
				if (prevp)
					prevp->next = listp->next;
				else {
					prevp = listp->next;
					sbus_arg->handler_list = prevp;
				}

				kmem_free(listp,
				    sizeof (struct sbus_intr_handler));
				break;
			}
			prevp = listp;
			listp = listp->next;
		}
	}
}

/*
 * remove_intrspec - Remove an interrupt specification.
 */
/*ARGSUSED*/
static void
sbus_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp)
{
	volatile uint64_t *mondo_vec_reg;
	volatile uint64_t *intr_state_reg;
#ifndef lint
	volatile uint64_t tmpreg;
#endif /* !lint */
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));
	int start_bit, ino, slot;
	struct sbus_wrapper_arg *sbus_arg;

	/* Grab the mutex protecting the poll list */
	mutex_enter(&softsp->intr_poll_list_lock);

	/* Xlate the interrupt */
	if (sbus_xlate_intrs(dip, rdip, (uint32_t *)&hdlp->ih_vector,
	    &hdlp->ih_pri, softsp->intr_mapping_ign) == DDI_FAILURE) {
		cmn_err(CE_WARN, "Can't xlate SBUS devices %s interrupt.\n",
		    ddi_driver_name(rdip));
		goto done;
	}

	ino = ((int32_t)hdlp->ih_vector) & SBUS_MAX_INO;

	mondo_vec_reg = (softsp->intr_mapping_reg +
	    ino_table[ino]->mapping_reg);

	/* Turn off the valid bit in the mapping register. */
	*mondo_vec_reg &= ~INTERRUPT_VALID;
#ifndef lint
	tmpreg = *softsp->sbus_ctrl_reg;
#endif /* !lint */

	/* Get our bit position for checking intr pending */
	if (ino > MAX_MONDO_EXTERNAL) {
		start_bit = ino_table[ino]->diagreg_shift;
		intr_state_reg = softsp->obio_intr_state;
	} else {
		start_bit = 16 * (ino >> 3) + 2 * (ino & 0x7);
		intr_state_reg = softsp->sbus_intr_state;
	}

	while (((*intr_state_reg >> start_bit) & 0x3) == INT_PENDING &&
	    !panicstr)
		/* empty */;

	slot = find_sbus_slot(dip, rdip);

	/* Return if the slot is invalid */
	if (slot >= MAX_SBUS_SLOT_ADDR || slot < 0) {
		goto done;
	}

	sbus_arg = softsp->intr_list[ino];

	/* Decrement the intr handler count on this slot */
	softsp->intr_hndlr_cnt[slot]--;

	DPRINTF(SBUS_INTERRUPT_DEBUG, ("Rem intr: Softsp 0x%p, Mondo 0x%x, "
	    "ino 0x%x, sbus_arg 0x%p intr cntr %d\n", (void *)softsp,
	    hdlp->ih_vector, ino, (void *)sbus_arg,
	    softsp->intr_hndlr_cnt[slot]));

	ASSERT(sbus_arg != NULL);
	ASSERT(sbus_arg->handler_list != NULL);
	sbus_free_handler(rdip, hdlp->ih_inum, sbus_arg);

	/* If we still have a list, we're done. */
	if (sbus_arg->handler_list == NULL)
		i_ddi_rem_ivintr(hdlp);

	/*
	 * If other devices are still installed for this slot, we need to
	 * turn the valid bit back on.
	 */
	if (softsp->intr_hndlr_cnt[slot] > 0) {
		*mondo_vec_reg |= INTERRUPT_VALID;
#ifndef lint
		tmpreg = *softsp->sbus_ctrl_reg;
#endif /* !lint */
	}

	if ((softsp->intr_hndlr_cnt[slot] == 0) || (slot >= EXT_SBUS_SLOTS)) {
		ASSERT(sbus_arg->handler_list == NULL);
#ifdef	_STARFIRE
		/* Do cleanup for interrupt target translation */
		pc_ittrans_cleanup(softsp->ittrans_cookie, mondo_vec_reg);
#endif	/* _STARFIRE */
	}


	/* Free up the memory used for the sbus interrupt handler */
	if (sbus_arg->handler_list == NULL) {
		DPRINTF(SBUS_INTERRUPT_DEBUG, ("Rem intr: Freeing sbus arg "
		    "0x%p\n", (void *)sbus_arg));
		kmem_free(sbus_arg, sizeof (struct sbus_wrapper_arg));
		softsp->intr_list[ino] = NULL;
	}

done:
	mutex_exit(&softsp->intr_poll_list_lock);
}

/*
 * We're prepared to claim that the interrupt string is in
 * the form of a list of <SBusintr> specifications, or we're dealing
 * with on-board devices and we have an interrupt_number property which
 * gives us our mondo number.
 * Translate the sbus levels or mondos into sysiointrspecs.
 */
static int
sbus_xlate_intrs(dev_info_t *dip, dev_info_t *rdip, uint32_t *intr,
    uint32_t *pil, int32_t ign)
{
	uint32_t ino, slot, level = *intr;
	int ret = DDI_SUCCESS;

	/*
	 * Create the sysio ino number.  onboard devices will have
	 * an "interrupts" property, that is equal to the ino number.
	 * If the devices are from the
	 * expansion slots, we construct the ino number by putting
	 * the slot number in the upper three bits, and the sbus
	 * interrupt level in the lower three bits.
	 */
	if (level > MAX_SBUS_LEVEL) {
		ino = level;
	} else {
		/* Construct ino from slot and interrupts */
		if ((slot = find_sbus_slot(dip, rdip)) == -1) {
			cmn_err(CE_WARN, "Can't determine sbus slot "
			    "of %s device\n", ddi_driver_name(rdip));
			ret = DDI_FAILURE;
			goto done;
		}

		if (slot >= MAX_SBUS_SLOT_ADDR) {
			cmn_err(CE_WARN, "Invalid sbus slot 0x%x"
			    "in %s device\n", slot, ddi_driver_name(rdip));
			ret = DDI_FAILURE;
			goto done;
		}

		ino = slot << 3;
		ino |= level;
	}

	/* Sanity check the inos range */
	if (ino >= MAX_INO_TABLE_SIZE) {
		cmn_err(CE_WARN, "Ino vector 0x%x out of range", ino);
		ret = DDI_FAILURE;
		goto done;
	}
	/* Sanity check the inos value */
	if (!ino_table[ino]) {
		cmn_err(CE_WARN, "Ino vector 0x%x is invalid", ino);
		ret = DDI_FAILURE;
		goto done;
	}

	if (*pil == 0) {
#define	SOC_PRIORITY 5
		/* The sunfire i/o board has a soc in the printer slot */
		if ((ino_table[ino]->clear_reg == PP_CLEAR) &&
		    ((strcmp(ddi_get_name(rdip), "soc") == 0) ||
		    (strcmp(ddi_get_name(rdip), "SUNW,soc") == 0))) {
			*pil = SOC_PRIORITY;
		} else {
			/* Figure out the pil associated with this interrupt */
			*pil = interrupt_priorities[ino];
		}
	}

	/* Or in the upa_id into the interrupt group number field */
	*intr = (uint32_t)(ino | ign);

	DPRINTF(SBUS_INTERRUPT_DEBUG, ("Xlate intr: Interrupt info for "
	    "device %s Mondo: 0x%x, ino: 0x%x, Pil: 0x%x, sbus level: 0x%x\n",
	    ddi_driver_name(rdip), *intr, ino, *pil, level));

done:
	return (ret);
}

/* new intr_ops structure */
int
sbus_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));
	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) {
			/* Xlate the interrupt */
			(void) sbus_xlate_intrs(dip, rdip,
			    (uint32_t *)&hdlp->ih_vector, &hdlp->ih_pri,
			    softsp->intr_mapping_ign);
		}

		*(int *)result = hdlp->ih_pri;
		break;
	case DDI_INTROP_SETPRI:
		break;
	case DDI_INTROP_ADDISR:
		ret = sbus_add_intr_impl(dip, rdip, hdlp);
		break;
	case DDI_INTROP_REMISR:
		sbus_remove_intr_impl(dip, rdip, hdlp);
		break;
	case DDI_INTROP_ENABLE:
		ret = sbus_update_intr_state(dip, rdip, hdlp,
		    SBUS_INTR_STATE_ENABLE);
		break;
	case DDI_INTROP_DISABLE:
		ret = sbus_update_intr_state(dip, rdip, hdlp,
		    SBUS_INTR_STATE_DISABLE);
		break;
	case DDI_INTROP_NINTRS:
	case DDI_INTROP_NAVAIL:
		*(int *)result = i_ddi_get_intx_nintrs(rdip);
		break;
	case DDI_INTROP_SETCAP:
	case DDI_INTROP_SETMASK:
	case DDI_INTROP_CLRMASK:
	case DDI_INTROP_GETPENDING:
		ret = DDI_ENOTSUP;
		break;
	case DDI_INTROP_SUPPORTED_TYPES:
		/* Sbus nexus driver supports only fixed interrupts */
		*(int *)result = i_ddi_get_intx_nintrs(rdip) ?
		    DDI_INTR_TYPE_FIXED : 0;
		break;
	default:
		ret = i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result);
		break;
	}

	return (ret);
}


/*
 * Called by suspend/resume to save/restore the interrupt status (valid bit)
 * of the interrupt mapping registers.
 */
static void
sbus_cpr_handle_intr_map_reg(uint64_t *cpr_softsp, volatile uint64_t *baddr,
    int save)
{
	int i;
	volatile uint64_t *mondo_vec_reg;

	for (i = 0; i < MAX_INO_TABLE_SIZE; i++) {
		if (ino_table[i] != NULL) {
			mondo_vec_reg = baddr + ino_table[i]->mapping_reg;
			if (save) {
				if (*mondo_vec_reg & INTERRUPT_VALID) {
					cpr_softsp[i] = *mondo_vec_reg;
				}
			} else {
				if (cpr_softsp[i]) {
					*mondo_vec_reg = cpr_softsp[i];
				}
			}
		}
	}
}

#define	SZ_INO_TABLE (sizeof (ino_table) / sizeof (ino_table[0]))

/*
 * sbus_intrdist
 *
 * This function retargets active interrupts by reprogramming the mondo
 * vec register. If the CPU ID of the target has not changed, then
 * the mondo is not reprogrammed. The routine must hold the mondo
 * lock for this instance of the sbus.
 */
static void
sbus_intrdist(void *arg)
{
	struct sbus_soft_state *softsp;
	dev_info_t *dip = (dev_info_t *)arg;
	volatile uint64_t *mondo_vec_reg;
	uint64_t *last_mondo_vec_reg;
	uint64_t mondo_vec;
	volatile uint64_t *intr_state_reg;
	uint_t start_bit;
	volatile uint64_t tmpreg; /* HW flush reg */
	uint_t mondo;
	uint_t cpu_id;

	/* extract the soft state pointer */
	softsp = ddi_get_soft_state(sbusp, ddi_get_instance(dip));

	last_mondo_vec_reg = NULL;
	for (mondo = 0; mondo < SZ_INO_TABLE; mondo++) {
		if (ino_table[mondo] == NULL)
			continue;

		mondo_vec_reg = (softsp->intr_mapping_reg +
		    ino_table[mondo]->mapping_reg);

		/* Don't reprogram the same register twice */
		if (mondo_vec_reg == last_mondo_vec_reg)
			continue;

		if ((*mondo_vec_reg & INTERRUPT_VALID) == 0)
			continue;

		last_mondo_vec_reg = (uint64_t *)mondo_vec_reg;

		cpu_id = intr_dist_cpuid();
#ifdef _STARFIRE
		/*
		 * For Starfire it is a pain to check the current target for
		 * the mondo since we have to read the PC asics ITTR slot
		 * assigned to this mondo. It will be much easier to assume
		 * the current target is always different and do the target
		 * reprogram all the time.
		 */
#else
		if (((*mondo_vec_reg & IMR_TID) >> IMR_TID_SHIFT) == cpu_id) {
			/* It is the same, don't reprogram */
			return;
		}
#endif	/* _STARFIRE */

		/* So it's OK to reprogram the CPU target */

		/* turn off valid bit and wait for the state machine to idle */
		*mondo_vec_reg &= ~INTERRUPT_VALID;

		tmpreg = *softsp->sbus_ctrl_reg;

#ifdef	lint
		tmpreg = tmpreg;
#endif	/* lint */

		if (mondo > MAX_MONDO_EXTERNAL) {
			start_bit = ino_table[mondo]->diagreg_shift;
			intr_state_reg = softsp->obio_intr_state;

			/*
			 * Loop waiting for state machine to idle. Do not keep
			 * looping on a panic so that the system does not hang.
			 */
			while ((((*intr_state_reg >> start_bit) & 0x3) ==
			    INT_PENDING) && !panicstr)
				/* empty */;
		} else {
			int int_pending = 0;	/* interrupts pending */

			/*
			 * Shift over to first bit for this Sbus slot, 16
			 * bits per slot, bits 0-1 of each slot are reserved.
			 */
			start_bit = 16 * (mondo >> 3) + 2;
			intr_state_reg = softsp->sbus_intr_state;

			/*
			 * Make sure interrupts for levels 1-7 of this slot
			 * are not pending.
			 */
			do {
				int level;	/* Sbus interrupt level */
				int shift;		/* # of bits to shift */
				uint64_t state_reg = *intr_state_reg;

				int_pending = 0;

				for (shift = start_bit, level = 1; level < 8;
				    level++, shift += 2) {
					if (((state_reg >> shift) &
					    0x3) == INT_PENDING) {
						int_pending = 1;
						break;
					}
				}
			} while (int_pending && !panicstr);
		}

		/* re-target the mondo and turn it on */
#ifdef _STARFIRE
		mondo_vec = (pc_translate_tgtid(softsp->ittrans_cookie,
		    cpu_id, mondo_vec_reg) <<
		    INTERRUPT_CPU_FIELD) |
		    INTERRUPT_VALID;
#else
		mondo_vec = (cpu_id << INTERRUPT_CPU_FIELD) | INTERRUPT_VALID;
#endif	/* _STARFIRE */

		/* write it back to the hardware. */
		*mondo_vec_reg = mondo_vec;

		/* flush the hardware buffers. */
		tmpreg = *mondo_vec_reg;

#ifdef	lint
		tmpreg = tmpreg;
#endif	/* lint */
	}
}

/*
 * Reset interrupts to IDLE.  This function is called during
 * panic handling after redistributing interrupts; it's needed to
 * support dumping to network devices after 'sync' from OBP.
 *
 * N.B.  This routine runs in a context where all other threads
 * are permanently suspended.
 */
static uint_t
sbus_intr_reset(void *arg)
{
	dev_info_t *dip = (dev_info_t *)arg;
	struct sbus_soft_state *softsp;
	uint_t mondo;
	volatile uint64_t *mondo_clear_reg;

	softsp = ddi_get_soft_state(sbusp, ddi_get_instance(dip));

	for (mondo = 0; mondo < SZ_INO_TABLE; mondo++) {
		if (ino_table[mondo] == NULL ||
		    ino_table[mondo]->clear_reg == NULL) {

			continue;
		}

		mondo_clear_reg = (softsp->clr_intr_reg +
		    ino_table[mondo]->clear_reg);
		*mondo_clear_reg = SBUS_INTR_IDLE;
	}

	return (BF_NONE);
}

/*
 * called from sbus_add_kstats() to create a kstat for each %pic
 * that the SBUS supports. These (read-only) kstats export the
 * event names that each %pic supports.
 *
 * if we fail to create any of these kstats we must remove any
 * that we have already created and return;
 *
 * NOTE: because all sbus devices use the same events we only
 *	 need to create the picN kstats once. All instances can
 *	 use the same picN kstats.
 *
 *       The flexibility exists to allow each device specify it's
 *       own events by creating picN kstats with the instance number
 *       set to ddi_get_instance(softsp->dip).
 *
 *       When searching for a picN kstat for a device you should
 *       first search for a picN kstat using the instance number
 *       of the device you are interested in. If that fails you
 *       should use the first picN kstat found for that device.
 */
static	void
sbus_add_picN_kstats(dev_info_t *dip)
{
	/*
	 * SBUS Performance Events.
	 *
	 * We declare an array of event-names and event-masks.
	 * The num of events in this array is AC_NUM_EVENTS.
	 */
	sbus_event_mask_t sbus_events_arr[SBUS_NUM_EVENTS] = {
		{"dvma_stream_rd", 0x0}, {"dvma_stream_wr", 0x1},
		{"dvma_const_rd", 0x2}, {"dvma_const_wr", 0x3},
		{"dvma_tlb_misses", 0x4}, {"dvma_stream_buf_mis", 0x5},
		{"dvma_cycles", 0x6}, {"dvma_bytes_xfr", 0x7},
		{"interrupts", 0x8}, {"upa_inter_nack", 0x9},
		{"pio_reads", 0xA}, {"pio_writes", 0xB},
		{"sbus_reruns", 0xC}, {"pio_cycles", 0xD}
	};

	/*
	 * We declare an array of clear masks for each pic.
	 * These masks are used to clear the %pcr bits for
	 * each pic.
	 */
	sbus_event_mask_t sbus_clear_pic[SBUS_NUM_PICS] = {
		/* pic0 */
		{"clear_pic", (uint64_t)~(0xf)},
		/* pic1 */
		{"clear_pic", (uint64_t)~(0xf << 8)}
	};

	struct kstat_named *sbus_pic_named_data;
	int  		event, pic;
	char 		pic_name[30];
	int		instance = ddi_get_instance(dip);
	int		pic_shift = 0;

	for (pic = 0; pic < SBUS_NUM_PICS; pic++) {
		/*
		 * create the picN kstat. The size of this kstat is
		 * SBUS_NUM_EVENTS + 1 for the clear_event_mask
		 */
		(void) sprintf(pic_name, "pic%d", pic);	/* pic0, pic1 ... */
		if ((sbus_picN_ksp[pic] = kstat_create("sbus",
		    instance, pic_name, "bus", KSTAT_TYPE_NAMED,
		    SBUS_NUM_EVENTS + 1, NULL)) == NULL) {
				cmn_err(CE_WARN, "sbus %s: kstat_create failed",
				    pic_name);

			/* remove pic0 kstat if pic1 create fails */
			if (pic == 1) {
				kstat_delete(sbus_picN_ksp[0]);
				sbus_picN_ksp[0] = NULL;
			}
			return;
		}

		sbus_pic_named_data =
		    (struct kstat_named *)(sbus_picN_ksp[pic]->ks_data);

		/*
		 * when we are writing pcr_masks to the kstat we need to
		 * shift bits left by 8 for pic1 events.
		 */
		if (pic == 1)
			pic_shift = 8;

		/*
		 * for each picN event we need to write a kstat record
		 * (name = EVENT, value.ui64 = PCR_MASK)
		 */
		for (event = 0; event < SBUS_NUM_EVENTS; event ++) {

			/* pcr_mask */
			sbus_pic_named_data[event].value.ui64 =
			    sbus_events_arr[event].pcr_mask << pic_shift;

			/* event-name */
			kstat_named_init(&sbus_pic_named_data[event],
			    sbus_events_arr[event].event_name,
			    KSTAT_DATA_UINT64);
		}

		/*
		 * we add the clear_pic event and mask as the last
		 * record in the kstat
		 */
		/* pcr mask */
		sbus_pic_named_data[SBUS_NUM_EVENTS].value.ui64 =
		    sbus_clear_pic[pic].pcr_mask;

		/* event-name */
		kstat_named_init(&sbus_pic_named_data[SBUS_NUM_EVENTS],
		    sbus_clear_pic[pic].event_name,
		    KSTAT_DATA_UINT64);

		kstat_install(sbus_picN_ksp[pic]);
	}
}

static	void
sbus_add_kstats(struct sbus_soft_state *softsp)
{
	struct kstat *sbus_counters_ksp;
	struct kstat_named *sbus_counters_named_data;

	/*
	 * Create the picN kstats if we are the first instance
	 * to attach. We use sbus_attachcnt as a count of how
	 * many instances have attached. This is protected by
	 * a mutex.
	 */
	mutex_enter(&sbus_attachcnt_mutex);
	if (sbus_attachcnt == 0)
		sbus_add_picN_kstats(softsp->dip);

	sbus_attachcnt ++;
	mutex_exit(&sbus_attachcnt_mutex);

	/*
	 * A "counter" kstat is created for each sbus
	 * instance that provides access to the %pcr and %pic
	 * registers for that instance.
	 *
	 * The size of this kstat is SBUS_NUM_PICS + 1 for %pcr
	 */
	if ((sbus_counters_ksp = kstat_create("sbus",
	    ddi_get_instance(softsp->dip), "counters",
	    "bus", KSTAT_TYPE_NAMED, SBUS_NUM_PICS + 1,
	    KSTAT_FLAG_WRITABLE)) == NULL) {

			cmn_err(CE_WARN, "sbus%d counters: kstat_create"
			    " failed", ddi_get_instance(softsp->dip));
		return;
	}

	sbus_counters_named_data =
	    (struct kstat_named *)(sbus_counters_ksp->ks_data);

	/* initialize the named kstats */
	kstat_named_init(&sbus_counters_named_data[0],
	    "pcr", KSTAT_DATA_UINT64);

	kstat_named_init(&sbus_counters_named_data[1],
	    "pic0", KSTAT_DATA_UINT64);

	kstat_named_init(&sbus_counters_named_data[2],
	    "pic1", KSTAT_DATA_UINT64);

	sbus_counters_ksp->ks_update = sbus_counters_kstat_update;
	sbus_counters_ksp->ks_private = (void *)softsp;

	kstat_install(sbus_counters_ksp);

	/* update the sofstate */
	softsp->sbus_counters_ksp = sbus_counters_ksp;
}

static	int
sbus_counters_kstat_update(kstat_t *ksp, int rw)
{
	struct kstat_named *sbus_counters_data;
	struct sbus_soft_state *softsp;
	uint64_t pic_register;

	sbus_counters_data = (struct kstat_named *)ksp->ks_data;
	softsp = (struct sbus_soft_state *)ksp->ks_private;

	if (rw == KSTAT_WRITE) {

		/*
		 * Write the pcr value to the softsp->sbus_pcr.
		 * The pic register is read-only so we don't
		 * attempt to write to it.
		 */

		*softsp->sbus_pcr =
		    (uint32_t)sbus_counters_data[0].value.ui64;

	} else {
		/*
		 * Read %pcr and %pic register values and write them
		 * into counters kstat.
		 *
		 * Due to a hardware bug we need to right shift the %pcr
		 * by 4 bits. This is only done when reading the %pcr.
		 *
		 */
		/* pcr */
		sbus_counters_data[0].value.ui64 = *softsp->sbus_pcr >> 4;

		pic_register = *softsp->sbus_pic;
		/*
		 * sbus pic register:
		 *  (63:32) = pic0
		 *  (31:00) = pic1
		 */

		/* pic0 */
		sbus_counters_data[1].value.ui64 = pic_register >> 32;
		/* pic1 */
		sbus_counters_data[2].value.ui64 =
		    pic_register & SBUS_PIC0_MASK;

	}
	return (0);
}

static int
sbus_update_intr_state(dev_info_t *dip, dev_info_t *rdip,
    ddi_intr_handle_impl_t *hdlp, uint_t new_intr_state)
{
	struct sbus_soft_state *softsp = (struct sbus_soft_state *)
	    ddi_get_soft_state(sbusp, ddi_get_instance(dip));
	int ino;
	struct sbus_wrapper_arg *sbus_arg;
	struct sbus_intr_handler *intr_handler;

	/* Xlate the interrupt */
	if (sbus_xlate_intrs(dip, rdip, (uint32_t *)&hdlp->ih_vector,
	    &hdlp->ih_pri, softsp->intr_mapping_ign) == DDI_FAILURE) {
		cmn_err(CE_WARN, "sbus_update_intr_state() can't xlate SBUS "
		    "devices %s interrupt.", ddi_driver_name(rdip));
		return (DDI_FAILURE);
	}

	ino = ((int32_t)hdlp->ih_vector) & SBUS_MAX_INO;
	sbus_arg = softsp->intr_list[ino];

	ASSERT(sbus_arg != NULL);
	ASSERT(sbus_arg->handler_list != NULL);
	intr_handler = sbus_arg->handler_list;

	while (intr_handler) {
		if ((intr_handler->inum == hdlp->ih_inum) &&
		    (intr_handler->dip == rdip)) {
			intr_handler->intr_state = new_intr_state;
			return (DDI_SUCCESS);
		}

		intr_handler = intr_handler->next;
	}

	return (DDI_FAILURE);
}