root/usr/src/uts/sun4u/io/pci/pci_tools.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/param.h>

#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/promif.h>
#include <sys/cpuvar.h>

#include <sys/pci/pci_obj.h>
#include <sys/hotplug/pci/pcihp.h>

#include <sys/pci_tools.h>
#include <sys/pci/pci_tools_ext.h>

/*
 * Number of interrupts supported per PCI bus.
 */
#define PCI_MAX_INO             0x3f

/*
 * PCI Space definitions.
 */
#define PCI_CONFIG_RANGE_BANK   (PCI_REG_ADDR_G(PCI_ADDR_CONFIG))
#define PCI_IO_RANGE_BANK       (PCI_REG_ADDR_G(PCI_ADDR_IO))
#define PCI_MEM_RANGE_BANK      (PCI_REG_ADDR_G(PCI_ADDR_MEM32))
#define PCI_MEM64_RANGE_BANK    (PCI_REG_ADDR_G(PCI_ADDR_MEM64))

/*
 * Extract 64 bit parent or size values from 32 bit cells of
 * pci_ranges_t property.
 *
 * Only bits 42-32 are relevant in parent_high.
 */
#define PCI_GET_RANGE_PROP(ranges, bank) \
        ((((uint64_t)(ranges[bank].parent_high & 0x7ff)) << 32) | \
        ranges[bank].parent_low)

#define PCI_GET_RANGE_PROP_SIZE(ranges, bank) \
        ((((uint64_t)(ranges[bank].size_high)) << 32) | \
        ranges[bank].size_low)

#define PCI_BAR_OFFSET(x)       (pci_bars[x.barnum])

/* Big and little endian as boolean values. */
#define BE B_TRUE
#define LE B_FALSE

#define SUCCESS 0

/* Mechanism for getting offsets of smaller datatypes aligned in 64 bit long */
typedef union {
        uint64_t u64;
        uint32_t u32;
        uint16_t u16;
        uint8_t u8;
} peek_poke_value_t;

/*
 * Offsets of BARS in config space.  First entry of 0 means config space.
 * Entries here correlate to pcitool_bars_t enumerated type.
 */
static uint8_t pci_bars[] = {
        0x0,
        PCI_CONF_BASE0,
        PCI_CONF_BASE1,
        PCI_CONF_BASE2,
        PCI_CONF_BASE3,
        PCI_CONF_BASE4,
        PCI_CONF_BASE5,
        PCI_CONF_ROM
};

/*LINTLIBRARY*/

static int pcitool_phys_peek(pci_t *pci_p, boolean_t type, size_t size,
    uint64_t paddr, uint64_t *value_p);
static int pcitool_phys_poke(pci_t *pci_p, boolean_t type, size_t size,
    uint64_t paddr, uint64_t value);
static int pcitool_access(pci_t *pci_p, uint64_t phys_addr, uint64_t max_addr,
    uint64_t *data, uint8_t size, boolean_t write, boolean_t endian,
    uint32_t *pcitool_status);
static int pcitool_validate_barnum_bdf(pcitool_reg_t *prg);
static int pcitool_get_bar(pci_t *pci_p, pcitool_reg_t *prg,
    uint64_t config_base_addr, uint64_t config_max_addr, uint64_t *bar,
    boolean_t *is_io_space);
static int pcitool_config_request(pci_t *pci_p, pcitool_reg_t *prg,
    uint64_t base_addr, uint64_t max_addr, uint8_t size, boolean_t write_flag);
static int pcitool_intr_get_max_ino(uint32_t *arg, int mode);
static int pcitool_get_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p);
static int pcitool_set_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p);

extern int pci_do_phys_peek(size_t, uint64_t, uint64_t *, int);
extern int pci_do_phys_poke(size_t, uint64_t, uint64_t *, int);

/*
 * Safe C wrapper around assy language routine pci_do_phys_peek
 *
 * Type is TRUE for big endian, FALSE for little endian.
 * Size is 1, 2, 4 or 8 bytes.
 * paddr is the physical address in IO space to access read.
 * value_p is where the value is returned.
 */
static int
pcitool_phys_peek(pci_t *pci_p, boolean_t type, size_t size,
        uint64_t paddr, uint64_t *value_p)
{
        on_trap_data_t otd;
        int err = DDI_SUCCESS;
        peek_poke_value_t peek_value;

        pbm_t *pbm_p = pci_p->pci_pbm_p;

        pbm_p->pbm_ontrap_data = &otd;

        /* Set up trap handling to make the access safe. */

        /*
         * on_trap works like setjmp.
         * Set it up to not panic on data access error,
         * but to call peek_fault instead.
         * Call pci_do_phys_peek after trap handling is setup.
         * When on_trap returns FALSE, it has been setup.
         * When it returns TRUE, an it has caught an error.
         */
        if (!on_trap(&otd, OT_DATA_ACCESS)) {
                otd.ot_trampoline = (uintptr_t)&peek_fault;
                err = pci_do_phys_peek(size, paddr, &peek_value.u64, type);
        } else {
                err = DDI_FAILURE;
        }

        pbm_p->pbm_ontrap_data = NULL;
        no_trap();

        if (err != DDI_FAILURE) {
                switch (size) {
                case 8:
                        *value_p = (uint64_t)peek_value.u64;
                        break;
                case 4:
                        *value_p = (uint64_t)peek_value.u32;
                        break;
                case 2:
                        *value_p = (uint64_t)peek_value.u16;
                        break;
                case 1:
                        *value_p = (uint64_t)peek_value.u8;
                        break;
                default:
                        err = DDI_FAILURE;
                }
        }

        return (err);
}

/*
 * Safe C wrapper around assy language routine pci_do_phys_poke
 *
 * Type is TRUE for big endian, FALSE for little endian.
 * Size is 1,2,4 or 8 bytes.
 * paddr is the physical address in IO space to access read.
 * value contains the value to be written.
 */
static int
pcitool_phys_poke(pci_t *pci_p, boolean_t type, size_t size,
        uint64_t paddr, uint64_t value)
{
        on_trap_data_t otd;
        int err = DDI_SUCCESS;
        peek_poke_value_t poke_value;

        pbm_t *pbm_p = pci_p->pci_pbm_p;

        switch (size) {
        case 8:
                poke_value.u64 = value;
                break;
        case 4:
                poke_value.u32 = (uint32_t)value;
                break;
        case 2:
                poke_value.u16 = (uint16_t)value;
                break;
        case 1:
                poke_value.u8 = (uint8_t)value;
                break;
        default:
                return (DDI_FAILURE);
        }

        mutex_enter(&pbm_p->pbm_pokefault_mutex);

        pbm_p->pbm_ontrap_data = &otd;

        /*
         * on_trap works like setjmp.
         * Set it up to not panic on data access error,
         * but to call poke_fault instead.
         * Call pci_do_phys_poke after trap handling is setup.
         * When on_trap returns FALSE, it has been setup.
         * When it returns TRUE, an it has caught an error.
         */
        if (!on_trap(&otd, OT_DATA_ACCESS)) {
                otd.ot_trampoline = (uintptr_t)&poke_fault;
                err = pci_do_phys_poke(size, paddr, &poke_value.u64, type);
        }

        /* Let the dust settle and errors occur if they will. */
        pbm_clear_error(pbm_p);

        /* Check for an error. */
        if (otd.ot_trap == OT_DATA_ACCESS) {
                err = DDI_FAILURE;
        }

        pbm_p->pbm_ontrap_data = NULL;
        mutex_exit(&pbm_p->pbm_pokefault_mutex);

        no_trap();
        return (err);
}


/*ARGSUSED*/
static int
pcitool_intr_info(dev_info_t *dip, void *arg, int mode)
{
        pcitool_intr_info_t intr_info;
        int rval = SUCCESS;

        /* If we need user_version, and to ret same user version as passed in */
        if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
            DDI_SUCCESS) {
                return (EFAULT);
        }

        if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
                return (ENOTSUP);

        intr_info.ctlr_version = 0;     /* XXX how to get real version? */
        intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
        intr_info.num_intr = PCI_MAX_INO;

        intr_info.drvr_version = PCITOOL_VERSION;
        if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
            DDI_SUCCESS) {
                rval = EFAULT;
        }

        return (rval);
}


/*
 * Get interrupt information for a given ino.
 * Returns info only for inos mapped to devices.
 *
 * Returned info is valid only when iget.num_devs is returned > 0.
 * If ino is not enabled or is not mapped to a device, num_devs will be = 0.
 */
/*ARGSUSED*/
static int
pcitool_get_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p)
{
        /* Array part isn't used here, but oh well... */
        pcitool_intr_get_t partial_iget;
        pcitool_intr_get_t *iget = &partial_iget;
        size_t  iget_kmem_alloc_size = 0;
        ib_t *ib_p = pci_p->pci_ib_p;
        volatile uint64_t *imregp;
        uint64_t imregval;
        uint32_t ino;
        uint8_t num_devs_ret;
        int cpu_id;
        int copyout_rval;
        int rval = SUCCESS;

        /* Read in just the header part, no array section. */
        if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
            DDI_SUCCESS) {

                return (EFAULT);
        }

        if (partial_iget.flags & PCITOOL_INTR_FLAG_GET_MSI) {
                partial_iget.status = PCITOOL_IO_ERROR;
                partial_iget.num_devs_ret = 0;
                rval = ENOTSUP;
                goto done_get_intr;
        }

        ino = partial_iget.ino;
        num_devs_ret = partial_iget.num_devs_ret;

        /* Validate argument. */
        if (ino > PCI_MAX_INO) {
                partial_iget.status = PCITOOL_INVALID_INO;
                partial_iget.num_devs_ret = 0;
                rval = EINVAL;
                goto done_get_intr;
        }

        /* Caller wants device information returned. */
        if (num_devs_ret > 0) {

                /*
                 * Allocate room.
                 * Note if num_devs_ret == 0 iget remains pointing to
                 * partial_iget.
                 */
                iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
                iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);

                /* Read in whole structure to verify there's room. */
                if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
                    SUCCESS) {

                        /* Be consistent and just return EFAULT here. */
                        kmem_free(iget, iget_kmem_alloc_size);

                        return (EFAULT);
                }
        }

        bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
        iget->ino = ino;
        iget->num_devs_ret = num_devs_ret;

        imregp = ib_intr_map_reg_addr(ib_p, ino);
        imregval = *imregp;

        /*
         * Read "valid" bit.  If set, interrupts are enabled.
         * This bit happens to be the same on Fire and Tomatillo.
         */
        if (imregval & COMMON_INTR_MAP_REG_VALID) {
                /*
                 * The following looks up the ib_ino_info and returns
                 * info of devices mapped to this ino.
                 */
                iget->num_devs = ib_get_ino_devs(
                    ib_p, ino, &iget->num_devs_ret, iget->dev);

                if (ib_get_intr_target(pci_p, ino, &cpu_id) != DDI_SUCCESS) {
                        iget->status = PCITOOL_IO_ERROR;
                        rval = EIO;
                        goto done_get_intr;
                }

                /*
                 * Consider only inos mapped to devices (as opposed to
                 * inos mapped to the bridge itself.
                 */
                if (iget->num_devs > 0) {
                        /*
                         * These 2 items are platform specific,
                         * extracted from the bridge.
                         */
                        iget->ctlr = 0;
                        iget->cpu_id = cpu_id;
                }
        }
done_get_intr:
        iget->drvr_version = PCITOOL_VERSION;
        copyout_rval = ddi_copyout(iget, arg,
            PCITOOL_IGET_SIZE(num_devs_ret), mode);

        if (iget_kmem_alloc_size > 0) {
                kmem_free(iget, iget_kmem_alloc_size);
        }

        if (copyout_rval != DDI_SUCCESS) {
                rval = EFAULT;
        }

        return (rval);
}

/*
 * Associate a new CPU with a given ino.
 *
 * Operate only on inos which are already mapped to devices.
 */
static int
pcitool_set_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p)
{
        ib_t *ib_p = pci_p->pci_ib_p;
        int rval = SUCCESS;
        int ret = DDI_SUCCESS;
        uint8_t zero = 0;
        pcitool_intr_set_t iset;
        volatile uint64_t *imregp;
        uint64_t imregval;

        size_t copyinout_size;
        int old_cpu_id;

        bzero(&iset, sizeof (pcitool_intr_set_t));

        /* Version 1 of pcitool_intr_set_t doesn't have flags. */
        copyinout_size = (size_t)&iset.flags - (size_t)&iset;

        if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
                return (EFAULT);

        switch (iset.user_version) {
        case PCITOOL_V1:
                break;

        case PCITOOL_V2:
                copyinout_size = sizeof (pcitool_intr_set_t);
                if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
                        return (EFAULT);
                break;

        default:
                iset.status = PCITOOL_OUT_OF_RANGE;
                rval = ENOTSUP;
                goto done_set_intr;
        }

        if ((iset.flags & PCITOOL_INTR_FLAG_SET_GROUP) ||
            (iset.flags & PCITOOL_INTR_FLAG_SET_MSI)) {
                iset.status = PCITOOL_IO_ERROR;
                rval = ENOTSUP;
                goto done_set_intr;
        }

        /* Validate input argument and that ino given belongs to a device. */
        if ((iset.ino > PCI_MAX_INO) ||
            (ib_get_ino_devs(ib_p, iset.ino, &zero, NULL) == 0)) {
                iset.status = PCITOOL_INVALID_INO;
                rval = EINVAL;
                goto done_set_intr;
        }

        imregp = (uint64_t *)ib_intr_map_reg_addr(ib_p, iset.ino);
        imregval = *imregp;

        /* Operate only on inos which are already enabled. */
        if (!(imregval & COMMON_INTR_MAP_REG_VALID)) {
                iset.status = PCITOOL_INVALID_INO;
                rval = EINVAL;
                goto done_set_intr;
        }

        if (ib_get_intr_target(pci_p, iset.ino, &old_cpu_id) != DDI_SUCCESS) {
                iset.status = PCITOOL_INVALID_INO;
                rval = EINVAL;
                goto done_set_intr;
        }

        if ((ret = ib_set_intr_target(pci_p, iset.ino,
            iset.cpu_id)) == DDI_SUCCESS) {
                iset.cpu_id = old_cpu_id;
                iset.status = PCITOOL_SUCCESS;
                goto done_set_intr;
        }

        switch (ret) {
        case DDI_EPENDING:
                iset.status = PCITOOL_PENDING_INTRTIMEOUT;
                rval = ETIME;
                break;
        case DDI_EINVAL:
                iset.status = PCITOOL_INVALID_CPUID;
                rval = EINVAL;
                break;
        default:
                iset.status = PCITOOL_INVALID_INO;
                rval = EINVAL;
                break;
        }
done_set_intr:
        iset.drvr_version = PCITOOL_VERSION;
        if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
                rval = EFAULT;

        return (rval);
}


/* Main function for handling interrupt CPU binding requests and queries. */
int
pcitool_intr_admn(dev_t dev, void *arg, int cmd, int mode)
{
        pci_t           *pci_p = DEV_TO_SOFTSTATE(dev);
        dev_info_t      *dip = pci_p->pci_dip;
        int             rval = SUCCESS;

        switch (cmd) {

        /* Get system interrupt information. */
        case PCITOOL_SYSTEM_INTR_INFO:
                rval = pcitool_intr_info(dip, arg, mode);
                break;

        /* Get interrupt information for a given ino. */
        case PCITOOL_DEVICE_GET_INTR:
                rval = pcitool_get_intr(dip, arg, mode, pci_p);
                break;

        /* Associate a new CPU with a given ino. */
        case PCITOOL_DEVICE_SET_INTR:
                rval = pcitool_set_intr(dip, arg, mode, pci_p);
                break;

        default:
                rval = ENOTTY;
        }

        return (rval);
}


/*
 * Wrapper around pcitool_phys_peek/poke.
 *
 * Validates arguments and calls pcitool_phys_peek/poke appropriately.
 *
 * Dip is of the nexus,
 * phys_addr is the address to write in physical space,
 * max_addr is the upper bound on the physical space used for bounds checking,
 * pcitool_status returns more detailed status in addition to a more generic
 * errno-style function return value.
 * other args are self-explanatory.
 */
static int
pcitool_access(pci_t *pci_p, uint64_t phys_addr, uint64_t max_addr,
        uint64_t *data, uint8_t size, boolean_t write, boolean_t endian,
        uint32_t *pcitool_status)
{

        int rval = SUCCESS;
        dev_info_t *dip = pci_p->pci_dip;

        /* Upper bounds checking. */
        if (phys_addr > max_addr) {
                DEBUG2(DBG_TOOLS, dip,
                    "Phys addr 0x%llx out of range (max 0x%llx).\n",
                    phys_addr, max_addr);
                *pcitool_status = PCITOOL_INVALID_ADDRESS;

                rval = EINVAL;

        /* Alignment checking. */
        } else if (!IS_P2ALIGNED(phys_addr, size)) {
                DEBUG0(DBG_TOOLS, dip, "not aligned.\n");
                *pcitool_status = PCITOOL_NOT_ALIGNED;

                rval = EINVAL;

        /* Made it through checks.  Do the access. */
        } else if (write) {

                DEBUG3(DBG_PHYS_ACC, dip,
                    "%d byte %s pcitool_phys_poke at addr 0x%llx\n",
                    size, (endian ? "BE" : "LE"), phys_addr);

                if (pcitool_phys_poke(pci_p, endian, size, phys_addr,
                    *data) != DDI_SUCCESS) {
                        DEBUG3(DBG_PHYS_ACC, dip,
                            "%d byte %s pcitool_phys_poke at addr "
                            "0x%llx failed\n",
                            size, (endian ? "BE" : "LE"), phys_addr);
                        *pcitool_status = PCITOOL_INVALID_ADDRESS;

                        rval = EFAULT;
                }

        } else {        /* Read */

                DEBUG3(DBG_PHYS_ACC, dip,
                    "%d byte %s pcitool_phys_peek at addr 0x%llx\n",
                    size, (endian ? "BE" : "LE"), phys_addr);

                if (pcitool_phys_peek(pci_p, endian, size, phys_addr,
                    data) != DDI_SUCCESS) {
                        DEBUG3(DBG_PHYS_ACC, dip,
                            "%d byte %s pcitool_phys_peek at addr "
                            "0x%llx failed\n",
                            size, (endian ? "BE" : "LE"), phys_addr);
                        *pcitool_status = PCITOOL_INVALID_ADDRESS;

                        rval = EFAULT;
                }
        }
        return (rval);
}

/*
 * Perform register accesses on the nexus device itself.
 */
int
pcitool_bus_reg_ops(dev_t dev, void *arg, int cmd, int mode)
{

        pci_t                   *pci_p = DEV_TO_SOFTSTATE(dev);
        dev_info_t              *dip = pci_p->pci_dip;
        pci_nexus_regspec_t     *pci_rp = NULL;
        boolean_t               write_flag = B_FALSE;
        pcitool_reg_t           prg;
        uint64_t                base_addr;
        uint64_t                max_addr;
        uint32_t                reglen;
        uint8_t                 size;
        uint32_t                rval = 0;

        if (cmd == PCITOOL_NEXUS_SET_REG)
                write_flag = B_TRUE;

        DEBUG0(DBG_TOOLS, dip, "pcitool_bus_reg_ops nexus set/get reg\n");

        /* Read data from userland. */
        if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
            DDI_SUCCESS) {
                DEBUG0(DBG_TOOLS, dip, "Error reading arguments\n");
                return (EFAULT);
        }

        /* Read reg property which contains starting addr and size of banks. */
        if (ddi_prop_lookup_int_array(
            DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", (int **)&pci_rp, &reglen) == DDI_SUCCESS) {
                if (((reglen * sizeof (int)) %
                    sizeof (pci_nexus_regspec_t)) != 0) {
                        DEBUG0(DBG_TOOLS, dip, "reg prop not well-formed");
                        prg.status = PCITOOL_REGPROP_NOTWELLFORMED;
                        rval = EIO;
                        goto done;
                }
        }

        /* Bounds check the bank number. */
        if (prg.barnum >=
            (reglen * sizeof (int)) / sizeof (pci_nexus_regspec_t)) {
                prg.status = PCITOOL_OUT_OF_RANGE;
                rval = EINVAL;
                goto done;
        }

        size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);
        base_addr = pci_rp[prg.barnum].phys_addr;
        max_addr = base_addr + pci_rp[prg.barnum].size;
        prg.phys_addr = base_addr + prg.offset;

        DEBUG4(DBG_TOOLS, dip,
            "pcitool_bus_reg_ops: nexus: base:0x%llx, offset:0x%llx, "
            "addr:0x%llx, max_addr:0x%llx\n",
            base_addr, prg.offset, prg.phys_addr, max_addr);

        /* Access device.  prg.status is modified. */
        rval = pcitool_access(pci_p,
            prg.phys_addr, max_addr, &prg.data, size, write_flag,
            PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr), &prg.status);

done:
        if (pci_rp != NULL)
                ddi_prop_free(pci_rp);

        prg.drvr_version = PCITOOL_VERSION;
        if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
            DDI_SUCCESS) {
                DEBUG0(DBG_TOOLS, dip, "Copyout failed.\n");
                return (EFAULT);
        }

        return (rval);
}


static int
pcitool_validate_barnum_bdf(pcitool_reg_t *prg)
{
        int rval = SUCCESS;

        if (prg->barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
                prg->status = PCITOOL_OUT_OF_RANGE;
                rval = EINVAL;

        /* Validate address arguments of bus / dev / func */
        } else if (((prg->bus_no &
            (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) != prg->bus_no) ||
            ((prg->dev_no &
            (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) != prg->dev_no) ||
            ((prg->func_no &
            (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) != prg->func_no)) {
                prg->status = PCITOOL_INVALID_ADDRESS;
                rval = EINVAL;
        }

        return (rval);
}

static int
pcitool_get_bar(pci_t *pci_p, pcitool_reg_t *prg, uint64_t config_base_addr,
        uint64_t config_max_addr, uint64_t *bar, boolean_t *is_io_space)
{

        uint8_t bar_offset;
        int rval;
        dev_info_t *dip = pci_p->pci_dip;

        *bar = 0;
        *is_io_space = B_FALSE;

        /*
         * Translate BAR number into offset of the BAR in
         * the device's config space.
         */
        bar_offset = PCI_BAR_OFFSET((*prg));

        DEBUG2(DBG_TOOLS, dip, "barnum:%d, bar_offset:0x%x\n",
            prg->barnum, bar_offset);

        /*
         * Get Bus Address Register (BAR) from config space.
         * bar_offset is the offset into config space of the BAR desired.
         * prg->status is modified on error.
         */
        rval = pcitool_access(pci_p, config_base_addr + bar_offset,
            config_max_addr, bar,
            4,          /* 4 bytes. */
            B_FALSE,    /* Read */
            B_FALSE,    /* Little endian. */
            &prg->status);
        if (rval != SUCCESS)
                return (rval);

        DEBUG1(DBG_TOOLS, dip, "bar returned is 0x%llx\n", *bar);
        if (!(*bar)) {
                prg->status = PCITOOL_INVALID_ADDRESS;
                return (EINVAL);
        }

        /*
         * BAR has bits saying this space is IO space, unless
         * this is the ROM address register.
         */
        if (((PCI_BASE_SPACE_M & *bar) == PCI_BASE_SPACE_IO) &&
            (bar_offset != PCI_CONF_ROM)) {
                *is_io_space = B_TRUE;
                *bar &= PCI_BASE_IO_ADDR_M;

        /*
         * BAR has bits saying this space is 64 bit memory
         * space, unless this is the ROM address register.
         *
         * The 64 bit address stored in two BAR cells is not necessarily
         * aligned on an 8-byte boundary.  Need to keep the first 4
         * bytes read, and do a separate read of the high 4 bytes.
         */

        } else if ((PCI_BASE_TYPE_ALL & *bar) && (bar_offset != PCI_CONF_ROM)) {

                uint32_t low_bytes = (uint32_t)(*bar & ~PCI_BASE_TYPE_ALL);

                /* Don't try to read past the end of BARs. */
                if (bar_offset >= PCI_CONF_BASE5) {
                        prg->status = PCITOOL_OUT_OF_RANGE;
                        return (EIO);
                }

                /* Access device.  prg->status is modified on error. */
                rval = pcitool_access(pci_p,
                    config_base_addr + bar_offset + 4, config_max_addr, bar,
                    4,          /* 4 bytes. */
                    B_FALSE,    /* Read */
                    B_FALSE,    /* Little endian. */
                    &prg->status);
                if (rval != SUCCESS)
                        return (rval);

                *bar = (*bar << 32) + low_bytes;
        }

        return (SUCCESS);
}


static int
pcitool_config_request(pci_t *pci_p, pcitool_reg_t *prg, uint64_t base_addr,
        uint64_t max_addr, uint8_t size, boolean_t write_flag)
{
        int rval;
        dev_info_t *dip = pci_p->pci_dip;

        /* Access config space and we're done. */
        prg->phys_addr = base_addr + prg->offset;

        DEBUG4(DBG_TOOLS, dip, "config access: base:0x%llx, "
            "offset:0x%llx, phys_addr:0x%llx, end:%s\n",
            base_addr, prg->offset, prg->phys_addr,
            (PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr)? "big" : "ltl"));

        /* Access device.  pr.status is modified. */
        rval = pcitool_access(pci_p, prg->phys_addr, max_addr, &prg->data, size,
            write_flag, PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr), &prg->status);

        DEBUG1(DBG_TOOLS, dip, "config access: data:0x%llx\n", prg->data);

        return (rval);
}

/* Perform register accesses on PCI leaf devices. */
int
pcitool_dev_reg_ops(dev_t dev, void *arg, int cmd, int mode)
{
        pci_t           *pci_p = DEV_TO_SOFTSTATE(dev);
        dev_info_t      *dip = pci_p->pci_dip;
        pci_ranges_t    *rp = pci_p->pci_ranges;
        pcitool_reg_t   prg;
        uint64_t        max_addr;
        uint64_t        base_addr;
        uint64_t        range_prop;
        uint64_t        range_prop_size;
        uint64_t        bar = 0;
        int             rval = 0;
        boolean_t       write_flag = B_FALSE;
        boolean_t       is_io_space = B_FALSE;
        uint8_t         size;

        if (cmd == PCITOOL_DEVICE_SET_REG)
                write_flag = B_TRUE;

        DEBUG0(DBG_TOOLS, dip, "pcitool_dev_reg_ops set/get reg\n");
        if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
            DDI_SUCCESS) {
                DEBUG0(DBG_TOOLS, dip, "Error reading arguments\n");
                return (EFAULT);
        }

        DEBUG3(DBG_TOOLS, dip, "raw bus:0x%x, dev:0x%x, func:0x%x\n",
            prg.bus_no, prg.dev_no, prg.func_no);

        if ((rval = pcitool_validate_barnum_bdf(&prg)) != SUCCESS)
                goto done_reg;

        size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);

        /* Get config space first. */
        range_prop = PCI_GET_RANGE_PROP(rp, PCI_CONFIG_RANGE_BANK);
        range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp, PCI_CONFIG_RANGE_BANK);
        max_addr = range_prop + range_prop_size;

        /*
         * Build device address based on base addr from range prop, and bus,
         * dev and func values passed in.  This address is where config space
         * begins.
         */
        base_addr = range_prop +
            (prg.bus_no << PCI_REG_BUS_SHIFT) +
            (prg.dev_no << PCI_REG_DEV_SHIFT) +
            (prg.func_no << PCI_REG_FUNC_SHIFT);

        if ((base_addr < range_prop) || (base_addr >= max_addr)) {
                prg.status = PCITOOL_OUT_OF_RANGE;
                rval = EINVAL;
                goto done_reg;
        }

        DEBUG5(DBG_TOOLS, dip, "range_prop:0x%llx, shifted: bus:0x%x, dev:0x%x "
            "func:0x%x, addr:0x%x\n", range_prop,
            prg.bus_no << PCI_REG_BUS_SHIFT, prg.dev_no << PCI_REG_DEV_SHIFT,
            prg.func_no << PCI_REG_FUNC_SHIFT, base_addr);

        /* Proper config space desired. */
        if (prg.barnum == 0) {

                rval = pcitool_config_request(pci_p, &prg, base_addr, max_addr,
                    size, write_flag);

        } else {        /* IO / MEM / MEM64 space. */

                if (pcitool_get_bar(pci_p, &prg, base_addr, max_addr, &bar,
                    &is_io_space) != SUCCESS)
                        goto done_reg;

                /* IO space. */
                if (is_io_space) {

                        DEBUG0(DBG_TOOLS, dip, "IO space\n");

                        /* Reposition to focus on IO space. */
                        range_prop = PCI_GET_RANGE_PROP(rp, PCI_IO_RANGE_BANK);
                        range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
                            PCI_IO_RANGE_BANK);

                /* 64 bit memory space. */
                } else if ((bar >> 32) != 0) {

                        DEBUG1(DBG_TOOLS, dip,
                            "64 bit mem space.  64-bit bar is 0x%llx\n", bar);

                        /* Reposition to MEM64 range space. */
                        range_prop = PCI_GET_RANGE_PROP(rp,
                            PCI_MEM64_RANGE_BANK);
                        range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
                            PCI_MEM64_RANGE_BANK);

                } else {        /* Mem32 space, including ROM */

                        DEBUG0(DBG_TOOLS, dip, "32 bit mem space\n");

                        if (PCI_BAR_OFFSET(prg) == PCI_CONF_ROM) {

                                DEBUG0(DBG_TOOLS, dip,
                                    "Additional ROM checking\n");

                                /* Can't write to ROM */
                                if (write_flag) {
                                        prg.status = PCITOOL_ROM_WRITE;
                                        rval = EIO;
                                        goto done_reg;

                                /* ROM disabled for reading */
                                } else if (!(bar & 0x00000001)) {
                                        prg.status = PCITOOL_ROM_DISABLED;
                                        rval = EIO;
                                        goto done_reg;
                                }
                        }

                        range_prop = PCI_GET_RANGE_PROP(rp, PCI_MEM_RANGE_BANK);
                        range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
                            PCI_MEM_RANGE_BANK);
                }

                /* Common code for all IO/MEM range spaces. */
                max_addr = range_prop + range_prop_size;
                base_addr = range_prop + bar;

                DEBUG3(DBG_TOOLS, dip,
                    "addr portion of bar is 0x%llx, base=0x%llx, "
                    "offset:0x%lx\n", bar, base_addr, prg.offset);

                /*
                 * Use offset provided by caller to index into
                 * desired space, then access.
                 * Note that prg.status is modified on error.
                 */
                prg.phys_addr = base_addr + prg.offset;
                rval = pcitool_access(pci_p, prg.phys_addr,
                    max_addr, &prg.data, size, write_flag,
                    PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr), &prg.status);
        }

done_reg:
        prg.drvr_version = PCITOOL_VERSION;
        if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
            DDI_SUCCESS) {
                DEBUG0(DBG_TOOLS, dip, "Error returning arguments.\n");
                rval = EFAULT;
        }
        return (rval);
}

int
pcitool_init(dev_info_t *dip)
{
        int instance = ddi_get_instance(dip);

        if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
            PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
            DDI_NT_REGACC, 0) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
            PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
            DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
                ddi_remove_minor_node(dip, PCI_MINOR_REG);
                return (DDI_FAILURE);
        }

        return (DDI_SUCCESS);
}

void
pcitool_uninit(dev_info_t *dip)
{
        ddi_remove_minor_node(dip, PCI_MINOR_REG);
        ddi_remove_minor_node(dip, PCI_MINOR_INTR);
}