root/usr/src/uts/sun4u/io/sysiosbus.c
/*
 * 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.
 */

/*
 * Copyright 2019 Peter Tribble.
 */

#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>
#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, 0};
static struct sbus_slot_entry ino_2 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L2_CLEAR, 0};
static struct sbus_slot_entry ino_3 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L3_CLEAR, 0};
static struct sbus_slot_entry ino_4 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L4_CLEAR, 0};
static struct sbus_slot_entry ino_5 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L5_CLEAR, 0};
static struct sbus_slot_entry ino_6 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L6_CLEAR, 0};
static struct sbus_slot_entry ino_7 = {SBUS_SLOT0_CONFIG, SBUS_SLOT0_MAPREG,
                                        SBUS_SLOT0_L7_CLEAR, 0};
static struct sbus_slot_entry ino_9 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L1_CLEAR, 0};
static struct sbus_slot_entry ino_10 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L2_CLEAR, 0};
static struct sbus_slot_entry ino_11 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L3_CLEAR, 0};
static struct sbus_slot_entry ino_12 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L4_CLEAR, 0};
static struct sbus_slot_entry ino_13 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L5_CLEAR, 0};
static struct sbus_slot_entry ino_14 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L6_CLEAR, 0};
static struct sbus_slot_entry ino_15 = {SBUS_SLOT1_CONFIG, SBUS_SLOT1_MAPREG,
                                        SBUS_SLOT1_L7_CLEAR, 0};
static struct sbus_slot_entry ino_17 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L1_CLEAR, 0};
static struct sbus_slot_entry ino_18 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L2_CLEAR, 0};
static struct sbus_slot_entry ino_19 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L3_CLEAR, 0};
static struct sbus_slot_entry ino_20 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L4_CLEAR, 0};
static struct sbus_slot_entry ino_21 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L5_CLEAR, 0};
static struct sbus_slot_entry ino_22 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L6_CLEAR, 0};
static struct sbus_slot_entry ino_23 = {SBUS_SLOT2_CONFIG, SBUS_SLOT2_MAPREG,
                                        SBUS_SLOT2_L7_CLEAR, 0};
static struct sbus_slot_entry ino_25 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L1_CLEAR, 0};
static struct sbus_slot_entry ino_26 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L2_CLEAR, 0};
static struct sbus_slot_entry ino_27 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L3_CLEAR, 0};
static struct sbus_slot_entry ino_28 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L4_CLEAR, 0};
static struct sbus_slot_entry ino_29 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L5_CLEAR, 0};
static struct sbus_slot_entry ino_30 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L6_CLEAR, 0};
static struct sbus_slot_entry ino_31 = {SBUS_SLOT3_CONFIG, SBUS_SLOT3_MAPREG,
                                        SBUS_SLOT3_L7_CLEAR, 0};
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 = {0, FFB_MAPPING_REG, 0, 0};
static struct sbus_slot_entry ino_exp = {0, EXP_MAPPING_REG, 0, 0};

/* 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);

/*
 * 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);

        /* 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));

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

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

        /*
         * 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;
        int slot;
        /* Interrupt state machine reset flag */
        int reset_ism_register = 1;
        int ret = DDI_SUCCESS;

        /* Check if we have a valid sbus slot address */
        slot = find_sbus_slot(dip, rdip);
        if (slot >= MAX_SBUS_SLOT_ADDR || slot < 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();
                        tmp_mondo_vec =
                            cpu_id << IMR_TID_SHIFT;
                        DPRINTF(SBUS_INTERRUPT_DEBUG, ("Add intr: initial "
                            "mapping reg 0x%lx\n", tmp_mondo_vec));
                } 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);
        }


        /* 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();
                if (((*mondo_vec_reg & IMR_TID) >> IMR_TID_SHIFT) == cpu_id) {
                        /* It is the same, don't reprogram */
                        return;
                }

                /* 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 */
                mondo_vec = (cpu_id << INTERRUPT_CPU_FIELD) | INTERRUPT_VALID;

                /* 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 == 0) {
                        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, 0)) == 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);
}