root/usr/src/uts/common/io/pci_intr_lib.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.
 * Copyright 2013 Pluribus Networks, Inc.
 */

/*
 * Support for MSI, MSIX and INTx
 */

#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/pci.h>
#include <sys/pci_cap.h>
#include <sys/pci_intr_lib.h>
#include <sys/sunddi.h>
#include <sys/bitmap.h>

/*
 * MSI-X BIR Index Table:
 *
 * BAR indicator register (BIR) to Base Address register.
 */
static  uchar_t pci_msix_bir_index[8] = {0x10, 0x14, 0x18, 0x1c,
                                        0x20, 0x24, 0xff, 0xff};

/* default class to pil value mapping */
pci_class_val_t pci_default_pil [] = {
        {0x000000, 0xff0000, 0x1},      /* Class code for pre-2.0 devices */
        {0x010000, 0xff0000, 0x5},      /* Mass Storage Controller */
        {0x020000, 0xff0000, 0x6},      /* Network Controller */
        {0x030000, 0xff0000, 0x9},      /* Display Controller */
        {0x040000, 0xff0000, 0x8},      /* Multimedia Controller */
        {0x050000, 0xff0000, 0x9},      /* Memory Controller */
        {0x060000, 0xff0000, 0x9},      /* Bridge Controller */
        {0x0c0000, 0xffff00, 0x9},      /* Serial Bus, FireWire (IEEE 1394) */
        {0x0c0100, 0xffff00, 0x4},      /* Serial Bus, ACCESS.bus */
        {0x0c0200, 0xffff00, 0x4},      /* Serial Bus, SSA */
        {0x0c0300, 0xffff00, 0x9},      /* Serial Bus Universal Serial Bus */
/*
 * XXX - This is a temporary workaround and it will be removed
 *       after x86 interrupt scalability support.
 */
#if defined(__x86)
        {0x0c0400, 0xffff00, 0x5},      /* Serial Bus, Fibre Channel */
#else
        {0x0c0400, 0xffff00, 0x6},      /* Serial Bus, Fibre Channel */
#endif
        {0x0c0600, 0xffff00, 0x6}       /* Serial Bus, Infiniband */
};

/*
 * Default class to intr_weight value mapping (% of CPU).  A driver.conf
 * entry on or above the pci node like
 *
 *      pci-class-intr-weights= 0x020000, 0xff0000, 30;
 *
 * can be used to augment or override entries in the default table below.
 *
 * NB: The values below give NICs preference on redistribution, and provide
 * NICs some isolation from other interrupt sources. We need better interfaces
 * that allow the NIC driver to identify a specific NIC instance as high
 * bandwidth, and thus deserving of separation from other low bandwidth
 * NICs additional isolation from other interrupt sources.
 *
 * NB: We treat Infiniband like a NIC.
 */
pci_class_val_t pci_default_intr_weight [] = {
        {0x020000, 0xff0000, 35},       /* Network Controller */
        {0x010000, 0xff0000, 10},       /* Mass Storage Controller */
        {0x0c0400, 0xffff00, 10},       /* Serial Bus, Fibre Channel */
        {0x0c0600, 0xffff00, 50}        /* Serial Bus, Infiniband */
};

/*
 * Library utility functions
 */

/*
 * pci_get_msi_ctrl:
 *
 *      Helper function that returns with 'cfg_hdl', MSI/X ctrl pointer,
 *      and caps_ptr for MSI/X if these are found.
 */
static int
pci_get_msi_ctrl(dev_info_t *dip, int type, ushort_t *msi_ctrl,
    ushort_t *caps_ptr, ddi_acc_handle_t *h)
{
        *msi_ctrl = *caps_ptr = 0;

        if (pci_config_setup(dip, h) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_get_msi_ctrl: "
                    "%s%d can't get config handle",
                    ddi_driver_name(dip), ddi_get_instance(dip)));

                return (DDI_FAILURE);
        }

        if ((PCI_CAP_LOCATE(*h, PCI_CAP_ID_MSI, caps_ptr) == DDI_SUCCESS) &&
            (type == DDI_INTR_TYPE_MSI)) {
                if ((*msi_ctrl = PCI_CAP_GET16(*h, 0, *caps_ptr,
                    PCI_MSI_CTRL)) == PCI_CAP_EINVAL16)
                        goto done;

                DDI_INTR_NEXDBG((CE_CONT, "pci_get_msi_ctrl: MSI "
                    "caps_ptr=%x msi_ctrl=%x\n", *caps_ptr, *msi_ctrl));

                return (DDI_SUCCESS);
        }

        if ((PCI_CAP_LOCATE(*h, PCI_CAP_ID_MSI_X, caps_ptr) == DDI_SUCCESS) &&
            (type == DDI_INTR_TYPE_MSIX)) {
                if ((*msi_ctrl = PCI_CAP_GET16(*h, 0, *caps_ptr,
                    PCI_MSIX_CTRL)) == PCI_CAP_EINVAL16)
                        goto done;

                DDI_INTR_NEXDBG((CE_CONT, "pci_get_msi_ctrl: MSI-X "
                    "caps_ptr=%x msi_ctrl=%x\n", *caps_ptr, *msi_ctrl));

                return (DDI_SUCCESS);
        }

done:
        pci_config_teardown(h);
        return (DDI_FAILURE);
}


/*
 * pci_msi_get_cap:
 *
 * Get the capabilities of the MSI/X interrupt
 */
int
pci_msi_get_cap(dev_info_t *rdip, int type, int *flagsp)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_cap: rdip = 0x%p\n",
            (void *)rdip));

        *flagsp = 0;

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                if (msi_ctrl &  PCI_MSI_64BIT_MASK)
                        *flagsp |= DDI_INTR_FLAG_MSI64;
                if (msi_ctrl & PCI_MSI_PVM_MASK)
                        *flagsp |= (DDI_INTR_FLAG_MASKABLE |
                            DDI_INTR_FLAG_PENDING);
                else
                        *flagsp |= DDI_INTR_FLAG_BLOCK;
        } else if (type == DDI_INTR_TYPE_MSIX) {
                /* MSI-X supports PVM, 64bit by default */
                *flagsp |= (DDI_INTR_FLAG_MASKABLE | DDI_INTR_FLAG_MSI64 |
                    DDI_INTR_FLAG_PENDING);
        }

        *flagsp |= DDI_INTR_FLAG_EDGE;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_cap: flags = 0x%x\n", *flagsp));

        pci_config_teardown(&cfg_hdle);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_configure:
 *
 * Configure address/data and number MSI/Xs fields in the MSI/X
 * capability structure.
 */
/* ARGSUSED */
int
pci_msi_configure(dev_info_t *rdip, int type, int count, int inum,
    uint64_t addr, uint64_t data)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        h;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: rdip = 0x%p type 0x%x "
            "count 0x%x inum 0x%x addr 0x%" PRIx64 " data 0x%" PRIx64 "\n",
            (void *)rdip, type, count, inum, addr, data));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &h) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                /* Set the bits to inform how many MSIs are enabled */
                msi_ctrl |= ((highbit(count) -1) << PCI_MSI_MME_SHIFT);
                PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_CTRL, msi_ctrl);

                DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_ctrl = %x\n",
                    PCI_CAP_GET16(h, 0, caps_ptr, PCI_MSI_CTRL)));

                /* Set the "data" and "addr" bits */
                PCI_CAP_PUT32(h, 0, caps_ptr, PCI_MSI_ADDR_OFFSET, addr);

                DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_addr = %x\n",
                    PCI_CAP_GET32(h, 0, caps_ptr, PCI_MSI_ADDR_OFFSET)));

                if (msi_ctrl &  PCI_MSI_64BIT_MASK) {
                        PCI_CAP_PUT32(h, 0, caps_ptr, PCI_MSI_ADDR_OFFSET
                            + 4, addr >> 32);

                        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: upper "
                            "32bit msi_addr = %x\n", PCI_CAP_GET32(h, 0,
                            caps_ptr, PCI_MSI_ADDR_OFFSET + 4)));

                        PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_64BIT_DATA, data);

                        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_data "
                            "= %x\n", PCI_CAP_GET16(h, 0, caps_ptr,
                            PCI_MSI_64BIT_DATA)));
                } else {
                        PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_32BIT_DATA, data);

                        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_data "
                            "= %x\n", PCI_CAP_GET16(h, 0, caps_ptr,
                            PCI_MSI_32BIT_DATA)));
                }
        } else if (type == DDI_INTR_TYPE_MSIX) {
                uintptr_t       off;
                ddi_intr_msix_t *msix_p = i_ddi_get_msix(rdip);

                /* Offset into the "inum"th entry in the MSI-X table */
                off = (uintptr_t)msix_p->msix_tbl_addr +
                    (inum * PCI_MSIX_VECTOR_SIZE);

                /* Set the "data" and "addr" bits */
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_DATA_OFFSET), data);

                /*
                 * Note that the spec only requires 32-bit accesses
                 * to be supported.  Apparently some chipsets don't
                 * support 64-bit accesses.
                 */
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_LOWER_ADDR_OFFSET), addr);
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_UPPER_ADDR_OFFSET),
                    addr >> 32);

                DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: "
                    "msix_addr 0x%x.%x msix_data 0x%x\n",
                    ddi_get32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_UPPER_ADDR_OFFSET)),
                    ddi_get32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_LOWER_ADDR_OFFSET)),
                    ddi_get32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_DATA_OFFSET))));
        }

        pci_config_teardown(&h);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_unconfigure:
 *
 * Unconfigure address/data and number MSI/Xs fields in the MSI/X
 * capability structure.
 */
/* ARGSUSED */
int
pci_msi_unconfigure(dev_info_t *rdip, int type, int inum)
{
        ushort_t                msi_ctrl, caps_ptr;
        ddi_acc_handle_t        h;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_unconfigure: rdip = 0x%p type 0x%x "
            "inum 0x%x\n", (void *)rdip, type, inum));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl, &caps_ptr, &h) !=
            DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                msi_ctrl &= (~PCI_MSI_MME_MASK);
                PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_CTRL, msi_ctrl);

                PCI_CAP_PUT32(h, 0, caps_ptr, PCI_MSI_ADDR_OFFSET, 0);

                if (msi_ctrl &  PCI_MSI_64BIT_MASK) {
                        PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_64BIT_DATA, 0);
                        PCI_CAP_PUT32(h, 0, caps_ptr, PCI_MSI_ADDR_OFFSET
                            + 4, 0);
                } else {
                        PCI_CAP_PUT16(h, 0, caps_ptr, PCI_MSI_32BIT_DATA, 0);
                }

                DDI_INTR_NEXDBG((CE_CONT, "pci_msi_unconfigure: msi_ctrl "
                    "= %x\n", PCI_CAP_GET16(h, 0, caps_ptr, PCI_MSI_CTRL)));

        } else if (type == DDI_INTR_TYPE_MSIX) {
                uintptr_t       off;
                ddi_intr_msix_t *msix_p = i_ddi_get_msix(rdip);

                /* Offset into the "inum"th entry in the MSI-X table */
                off = (uintptr_t)msix_p->msix_tbl_addr +
                    (inum * PCI_MSIX_VECTOR_SIZE);

                /* Reset the "data" and "addr" bits */
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_DATA_OFFSET), 0);

                /*
                 * Note that the spec only requires 32-bit accesses
                 * to be supported.  Apparently some chipsets don't
                 * support 64-bit accesses.
                 */
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_LOWER_ADDR_OFFSET), 0);
                ddi_put32(msix_p->msix_tbl_hdl,
                    (uint32_t *)(off + PCI_MSIX_UPPER_ADDR_OFFSET), 0);
        }

        pci_config_teardown(&h);
        return (DDI_SUCCESS);
}


/*
 * pci_is_msi_enabled:
 *
 * This function returns DDI_SUCCESS if MSI/X is already enabled, otherwise
 * it returns DDI_FAILURE.
 */
int
pci_is_msi_enabled(dev_info_t *rdip, int type)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;
        int                     ret = DDI_FAILURE;

        DDI_INTR_NEXDBG((CE_CONT, "pci_is_msi_enabled: rdip = 0x%p, "
            "type  = 0x%x\n", (void *)rdip, type));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if ((type == DDI_INTR_TYPE_MSI) && (msi_ctrl & PCI_MSI_ENABLE_BIT))
                ret = DDI_SUCCESS;

        if ((type == DDI_INTR_TYPE_MSIX) && (msi_ctrl & PCI_MSIX_ENABLE_BIT))
                ret = DDI_SUCCESS;

        pci_config_teardown(&cfg_hdle);
        return (ret);
}


/*
 * pci_msi_enable_mode:
 *
 * This function sets the MSI_ENABLE bit in the capability structure
 * (for MSI) and MSIX_ENABLE bit in the MSI-X capability structure.
 *
 * NOTE: It is the nexus driver's responsibility to clear the MSI/X
 * interrupt's mask bit in the MSI/X capability structure before the
 * interrupt can be used.
 */
int
pci_msi_enable_mode(dev_info_t *rdip, int type)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_enable_mode: rdip = 0x%p\n",
            (void *)rdip));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                if (msi_ctrl & PCI_MSI_ENABLE_BIT)
                        goto finished;

                msi_ctrl |= PCI_MSI_ENABLE_BIT;
                PCI_CAP_PUT16(cfg_hdle, 0, caps_ptr, PCI_MSI_CTRL, msi_ctrl);

        } else if (type == DDI_INTR_TYPE_MSIX) {
                if (msi_ctrl & PCI_MSIX_ENABLE_BIT)
                        goto finished;

                msi_ctrl |= PCI_MSIX_ENABLE_BIT;
                PCI_CAP_PUT16(cfg_hdle, 0, caps_ptr, PCI_MSIX_CTRL,
                    msi_ctrl);
        }

finished:
        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_enable_mode: msi_ctrl = %x\n",
            msi_ctrl));

        pci_config_teardown(&cfg_hdle);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_disable_mode:
 *
 * This function resets the MSI_ENABLE bit in the capability structure
 * (for MSI) and MSIX_ENABLE bit in the MSI-X capability structure.
 *
 * NOTE: It is the nexus driver's responsibility to set the MSI/X
 * interrupt's mask bit in the MSI/X capability structure before the
 * interrupt can be disabled.
 */
int
pci_msi_disable_mode(dev_info_t *rdip, int type)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_disable_mode: rdip = 0x%p\n",
            (void *)rdip));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        /* Reset the "enable" bit */
        if (type == DDI_INTR_TYPE_MSI) {
                if (!(msi_ctrl & PCI_MSI_ENABLE_BIT))
                        goto finished;
                msi_ctrl &= ~PCI_MSI_ENABLE_BIT;
                PCI_CAP_PUT16(cfg_hdle, 0, caps_ptr, PCI_MSI_CTRL, msi_ctrl);
        } else if (type == DDI_INTR_TYPE_MSIX) {
                if (!(msi_ctrl & PCI_MSIX_ENABLE_BIT))
                        goto finished;

                msi_ctrl &= ~PCI_MSIX_ENABLE_BIT;
                PCI_CAP_PUT16(cfg_hdle, 0, caps_ptr, PCI_MSIX_CTRL, msi_ctrl);
        }

finished:
        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_disable_mode: msi_ctrl = %x\n",
            msi_ctrl));

        pci_config_teardown(&cfg_hdle);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_set_mask:
 *
 * Set the mask bit in the MSI/X capability structure
 */
/* ARGSUSED */
int
pci_msi_set_mask(dev_info_t *rdip, int type, int inum)
{
        int                     offset;
        int                     ret = DDI_FAILURE;
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;
        uint32_t                mask_bits;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_set_mask: rdip = 0x%p, "
            "type = 0x%x\n", (void *)rdip, type));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                if (!(msi_ctrl &  PCI_MSI_PVM_MASK))
                        goto done;

                offset = (msi_ctrl &  PCI_MSI_64BIT_MASK) ?
                    PCI_MSI_64BIT_MASKBITS : PCI_MSI_32BIT_MASK;

                if ((mask_bits = PCI_CAP_GET32(cfg_hdle, 0, caps_ptr,
                    offset)) == PCI_CAP_EINVAL32)
                        goto done;

                mask_bits |= (1 << inum);

                PCI_CAP_PUT32(cfg_hdle, 0, caps_ptr, offset, mask_bits);

        } else if (type == DDI_INTR_TYPE_MSIX) {
                uintptr_t               off;
                ddi_intr_msix_t         *msix_p;

                /* Set function mask */
                if (msi_ctrl & PCI_MSIX_FUNCTION_MASK) {
                        ret = DDI_SUCCESS;
                        goto done;
                }

                msix_p = i_ddi_get_msix(rdip);

                /* Offset into the "inum"th entry in the MSI-X table */
                off = (uintptr_t)msix_p->msix_tbl_addr + (inum *
                    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

                /* Set the Mask bit */
                ddi_put32(msix_p->msix_tbl_hdl, (uint32_t *)off, 0x1);
        }

        ret = DDI_SUCCESS;
done:
        pci_config_teardown(&cfg_hdle);
        return (ret);
}


/*
 * pci_msi_clr_mask:
 *
 * Clear the mask bit in the MSI/X capability structure
 */
/* ARGSUSED */
int
pci_msi_clr_mask(dev_info_t *rdip, int type, int inum)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;
        int                     offset;
        int                     ret = DDI_FAILURE;
        uint32_t                mask_bits;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_clr_mask: rdip = 0x%p, "
            "type = 0x%x\n", (void *)rdip, type));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                if (!(msi_ctrl &  PCI_MSI_PVM_MASK))
                        goto done;

                offset = (msi_ctrl &  PCI_MSI_64BIT_MASK) ?
                    PCI_MSI_64BIT_MASKBITS : PCI_MSI_32BIT_MASK;
                if ((mask_bits = PCI_CAP_GET32(cfg_hdle, 0, caps_ptr,
                    offset)) == PCI_CAP_EINVAL32)
                        goto done;

                mask_bits &= ~(1 << inum);

                PCI_CAP_PUT32(cfg_hdle, 0, caps_ptr, offset, mask_bits);

        } else if (type == DDI_INTR_TYPE_MSIX) {
                uintptr_t               off;
                ddi_intr_msix_t         *msix_p;

                if (msi_ctrl & PCI_MSIX_FUNCTION_MASK) {
                        ret = DDI_SUCCESS;
                        goto done;
                }

                msix_p = i_ddi_get_msix(rdip);

                /* Offset into the "inum"th entry in the MSI-X table */
                off = (uintptr_t)msix_p->msix_tbl_addr + (inum *
                    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

                /* Clear the Mask bit */
                ddi_put32(msix_p->msix_tbl_hdl, (uint32_t *)off, 0x0);
        }

        ret = DDI_SUCCESS;
done:
        pci_config_teardown(&cfg_hdle);
        return (ret);
}


/*
 * pci_msi_get_pending:
 *
 * Get the pending bit from the MSI/X capability structure
 */
/* ARGSUSED */
int
pci_msi_get_pending(dev_info_t *rdip, int type, int inum, int *pendingp)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;
        int                     offset;
        int                     ret = DDI_FAILURE;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_pending: rdip = 0x%p\n",
            (void *)rdip));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                uint32_t        pending_bits;

                if (!(msi_ctrl &  PCI_MSI_PVM_MASK)) {
                        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_pending: "
                            "PVM is not supported\n"));
                        goto done;
                }

                offset = (msi_ctrl &  PCI_MSI_64BIT_MASK) ?
                    PCI_MSI_64BIT_PENDING : PCI_MSI_32BIT_PENDING;

                if ((pending_bits = PCI_CAP_GET32(cfg_hdle, 0, caps_ptr,
                    offset)) == PCI_CAP_EINVAL32)
                        goto done;

                *pendingp = pending_bits & ~(1 >> inum);

        } else if (type == DDI_INTR_TYPE_MSIX) {
                uintptr_t       off;
                uint64_t        pending_bits;
                ddi_intr_msix_t *msix_p = i_ddi_get_msix(rdip);

                /* Offset into the PBA array which has entry for "inum" */
                off = (uintptr_t)msix_p->msix_pba_addr + (inum / 64);

                /* Read the PBA array */
                pending_bits = ddi_get64(msix_p->msix_pba_hdl, (uint64_t *)off);

                *pendingp = pending_bits & ~(1 >> inum);
        }

        ret = DDI_SUCCESS;
done:
        pci_config_teardown(&cfg_hdle);
        return (ret);
}


/*
 * pci_msi_get_nintrs:
 *
 * For a given type (MSI/X) returns the number of interrupts supported
 */
int
pci_msi_get_nintrs(dev_info_t *rdip, int type, int *nintrs)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_nintrs: rdip = 0x%p\n",
            (void *)rdip));

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                *nintrs = 1 << ((msi_ctrl & PCI_MSI_MMC_MASK) >>
                    PCI_MSI_MMC_SHIFT);
        } else if (type == DDI_INTR_TYPE_MSIX) {
                if (msi_ctrl &  PCI_MSIX_TBL_SIZE_MASK)
                        *nintrs = (msi_ctrl & PCI_MSIX_TBL_SIZE_MASK) + 1;
        }

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_nintrs: "
            "nintr = 0x%x\n", *nintrs));

        pci_config_teardown(&cfg_hdle);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_set_nintrs:
 *
 * For a given type (MSI/X) sets the number of interrupts supported
 * by the system.
 * For MSI: Return an error if this func is called for navail > 32
 * For MSI-X: Return an error if this func is called for navail > 2048
 */
int
pci_msi_set_nintrs(dev_info_t *rdip, int type, int navail)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_set_nintrs: rdip = 0x%p, "
            "navail = 0x%x\n", (void *)rdip, navail));

        /* Check for valid input argument */
        if (((type == DDI_INTR_TYPE_MSI) && (navail > PCI_MSI_MAX_INTRS)) ||
            ((type == DDI_INTR_TYPE_MSIX) && (navail >  PCI_MSIX_MAX_INTRS)))
                return (DDI_EINVAL);

        if (pci_get_msi_ctrl(rdip, type, &msi_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (DDI_FAILURE);

        if (type == DDI_INTR_TYPE_MSI) {
                msi_ctrl |= ((highbit(navail) -1) << PCI_MSI_MME_SHIFT);

                PCI_CAP_PUT16(cfg_hdle, 0, caps_ptr, PCI_MSI_CTRL, msi_ctrl);
        } else if (type == DDI_INTR_TYPE_MSIX) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msi_set_nintrs: unsupported\n"));
        }

        pci_config_teardown(&cfg_hdle);
        return (DDI_SUCCESS);
}


/*
 * pci_msi_get_supported_type:
 *
 * Returns DDI_INTR_TYPE_MSI and/or DDI_INTR_TYPE_MSIX as supported
 * types if device supports them. A DDI_FAILURE is returned otherwise.
 */
int
pci_msi_get_supported_type(dev_info_t *rdip, int *typesp)
{
        ushort_t                caps_ptr, msi_ctrl;
        ddi_acc_handle_t        cfg_hdle;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_supported_type: "
            "rdip = 0x%p\n", (void *)rdip));

        *typesp = 0;

        if (pci_get_msi_ctrl(rdip, DDI_INTR_TYPE_MSI, &msi_ctrl,
            &caps_ptr, &cfg_hdle) == DDI_SUCCESS) {
                *typesp |= DDI_INTR_TYPE_MSI;
                pci_config_teardown(&cfg_hdle);
        }

        if (pci_get_msi_ctrl(rdip, DDI_INTR_TYPE_MSIX, &msi_ctrl,
            &caps_ptr, &cfg_hdle) == DDI_SUCCESS) {
                *typesp |= DDI_INTR_TYPE_MSIX;
                pci_config_teardown(&cfg_hdle);
        }

        DDI_INTR_NEXDBG((CE_CONT, "pci_msi_get_supported_type: "
            "rdip = 0x%p types 0x%x\n", (void *)rdip, *typesp));

        return (*typesp == 0 ? DDI_FAILURE : DDI_SUCCESS);
}


/*
 * pci_msix_init:
 *      This function initializes the various handles/addrs etc.
 *      needed for MSI-X support. It also allocates a private
 *      structure to keep track of these.
 */
ddi_intr_msix_t *
pci_msix_init(dev_info_t *rdip)
{
        uint_t                  rnumber, breg, nregs;
        size_t                  msix_tbl_size;
        size_t                  pba_tbl_size;
        ushort_t                caps_ptr, msix_ctrl;
        ddi_intr_msix_t         *msix_p;
        ddi_acc_handle_t        cfg_hdle;
        pci_regspec_t           *rp;
        int                     reg_size, addr_space, offset, *regs_list;
        int                     i, ret;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: rdip = %p\n", (void *)rdip));

        if (pci_get_msi_ctrl(rdip, DDI_INTR_TYPE_MSIX, &msix_ctrl,
            &caps_ptr, &cfg_hdle) != DDI_SUCCESS)
                return (NULL);

        msix_p = kmem_zalloc(sizeof (ddi_intr_msix_t), KM_SLEEP);

        /*
         * Initialize the devacc structure
         */
        msix_p->msix_dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
        msix_p->msix_dev_attr.devacc_attr_endian_flags =
            DDI_STRUCTURE_LE_ACC;
        msix_p->msix_dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

        /* Map the entire MSI-X vector table */
        msix_p->msix_tbl_offset = PCI_CAP_GET32(cfg_hdle, 0, caps_ptr,
            PCI_MSIX_TBL_OFFSET);

        if ((breg = pci_msix_bir_index[msix_p->msix_tbl_offset &
            PCI_MSIX_TBL_BIR_MASK]) == 0xff)
                goto fail1;

        msix_p->msix_tbl_offset = msix_p->msix_tbl_offset &
            ~PCI_MSIX_TBL_BIR_MASK;
        msix_tbl_size = ((msix_ctrl & PCI_MSIX_TBL_SIZE_MASK) + 1) *
            PCI_MSIX_VECTOR_SIZE;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: MSI-X table offset 0x%x "
            "breg 0x%x size 0x%lx\n", msix_p->msix_tbl_offset, breg,
            msix_tbl_size));

        if ((ret = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip,
            DDI_PROP_DONTPASS, "reg", (int **)&regs_list, &nregs))
            != DDI_PROP_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: "
                    "ddi_prop_lookup_int_array failed %d\n", ret));

                goto fail1;
        }

        reg_size = sizeof (pci_regspec_t) / sizeof (int);

        for (i = 1, rnumber = 0; i < nregs/reg_size; i++) {
                rp = (pci_regspec_t *)&regs_list[i * reg_size];
                addr_space = rp->pci_phys_hi & PCI_ADDR_MASK;
                offset = PCI_REG_REG_G(rp->pci_phys_hi);

                if ((offset == breg) && ((addr_space == PCI_ADDR_MEM32) ||
                    (addr_space == PCI_ADDR_MEM64))) {
                        rnumber = i;
                        break;
                }
        }

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: MSI-X rnum = %d\n", rnumber));

        if (rnumber == 0) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: "
                    "no mtaching reg number for offset 0x%x\n", breg));

                goto fail2;
        }

        if ((ret = ddi_regs_map_setup(rdip, rnumber,
            (caddr_t *)&msix_p->msix_tbl_addr, msix_p->msix_tbl_offset,
            msix_tbl_size, &msix_p->msix_dev_attr,
            &msix_p->msix_tbl_hdl)) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: MSI-X Table "
                    "ddi_regs_map_setup failed %d\n", ret));

                goto fail2;
        }

        /*
         * Map in the MSI-X Pending Bit Array
         */
        msix_p->msix_pba_offset = PCI_CAP_GET32(cfg_hdle, 0, caps_ptr,
            PCI_MSIX_PBA_OFFSET);

        if ((breg = pci_msix_bir_index[msix_p->msix_pba_offset &
            PCI_MSIX_PBA_BIR_MASK]) == 0xff)
                goto fail3;

        msix_p->msix_pba_offset = msix_p->msix_pba_offset &
            ~PCI_MSIX_PBA_BIR_MASK;
        pba_tbl_size = ((msix_ctrl & PCI_MSIX_TBL_SIZE_MASK) + 1)/8;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: PBA table offset 0x%x "
            "breg 0x%x size 0x%lx\n", msix_p->msix_pba_offset, breg,
            pba_tbl_size));

        for (i = 1, rnumber = 0; i < nregs/reg_size; i++) {
                rp = (pci_regspec_t *)&regs_list[i * reg_size];
                addr_space = rp->pci_phys_hi & PCI_ADDR_MASK;
                offset = PCI_REG_REG_G(rp->pci_phys_hi);

                if ((offset == breg) && ((addr_space == PCI_ADDR_MEM32) ||
                    (addr_space == PCI_ADDR_MEM64))) {
                        rnumber = i;
                        break;
                }
        }

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: PBA rnum = %d\n", rnumber));

        if (rnumber == 0) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: "
                    "no matching reg number for offset 0x%x\n", breg));

                goto fail3;
        }

        if ((ret = ddi_regs_map_setup(rdip, rnumber,
            (caddr_t *)&msix_p->msix_pba_addr, msix_p->msix_pba_offset,
            pba_tbl_size, &msix_p->msix_dev_attr,
            &msix_p->msix_pba_hdl)) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: PBA "
                    "ddi_regs_map_setup failed %d\n", ret));

                goto fail3;
        }

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_init: msix_p = 0x%p DONE!!\n",
            (void *)msix_p));

        ddi_prop_free(regs_list);
        goto done;

fail3:
        ddi_regs_map_free(&msix_p->msix_tbl_hdl);
fail2:
        ddi_prop_free(regs_list);
fail1:
        kmem_free(msix_p, sizeof (ddi_intr_msix_t));
        msix_p = NULL;
done:
        pci_config_teardown(&cfg_hdle);
        return (msix_p);
}


/*
 * pci_msix_fini:
 *      This function cleans up previously allocated handles/addrs etc.
 *      It is only called if no more MSI-X interrupts are being used.
 */
void
pci_msix_fini(ddi_intr_msix_t *msix_p)
{
        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_fini: msix_p = 0x%p\n",
            (void *)msix_p));

        ddi_regs_map_free(&msix_p->msix_pba_hdl);
        ddi_regs_map_free(&msix_p->msix_tbl_hdl);
        kmem_free(msix_p, sizeof (ddi_intr_msix_t));
}


/*
 * pci_msix_dup:
 *      This function duplicates the address and data pair of one msi-x
 *      vector to another msi-x vector.
 */
int
pci_msix_dup(dev_info_t *rdip, int org_inum, int dup_inum)
{
        ddi_intr_msix_t *msix_p = i_ddi_get_msix(rdip);
        uint64_t        addr;
        uint64_t        data;
        uintptr_t       off;

        DDI_INTR_NEXDBG((CE_CONT, "pci_msix_dup: dip = %p, inum = 0x%x, "
            "to_vector = 0x%x\n", (void *)rdip, org_inum, dup_inum));

        /* Offset into the original inum's entry in the MSI-X table */
        off = (uintptr_t)msix_p->msix_tbl_addr +
            (org_inum * PCI_MSIX_VECTOR_SIZE);

        /*
         * For the MSI-X number passed in, get the "data" and "addr" fields.
         *
         * Note that the spec only requires 32-bit accesses to be supported.
         * Apparently some chipsets don't support 64-bit accesses.
         */
        addr = ddi_get32(msix_p->msix_tbl_hdl,
            (uint32_t *)(off + PCI_MSIX_UPPER_ADDR_OFFSET));
        addr = (addr << 32) | ddi_get32(msix_p->msix_tbl_hdl,
            (uint32_t *)(off + PCI_MSIX_LOWER_ADDR_OFFSET));

        data = ddi_get32(msix_p->msix_tbl_hdl,
            (uint32_t *)(off + PCI_MSIX_DATA_OFFSET));

        /* Program new vector with these existing values */
        return (pci_msi_configure(rdip, DDI_INTR_TYPE_MSIX, 1, dup_inum, addr,
            data));
}


/*
 * Next set of routines are for INTx (legacy) PCI interrupt
 * support only.
 */

/*
 * pci_intx_get_cap:
 *      For non-MSI devices that comply to PCI v2.3 or greater;
 *      read the command register. Bit 10 implies interrupt disable.
 *      Set this bit and then read the status register bit 3.
 *      Bit 3 of status register is Interrupt state.
 *      If it is set; then the device supports 'Masking'
 *
 *      Reset the device back to the original state.
 */
int
pci_intx_get_cap(dev_info_t *dip, int *flagsp)
{
        uint16_t                cmdreg, savereg;
        ddi_acc_handle_t        cfg_hdl;
#ifdef  DEBUG
        uint16_t                statreg;
#endif /* DEBUG */

        *flagsp = 0;
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: %s%d: called\n",
            ddi_driver_name(dip), ddi_get_instance(dip)));

        if (pci_config_setup(dip, &cfg_hdl) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: can't get "
                    "config handle\n"));
                return (DDI_FAILURE);
        }

        savereg = pci_config_get16(cfg_hdl, PCI_CONF_COMM);
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: "
            "command register was 0x%x\n", savereg));

        /* Disable the interrupts */
        cmdreg = savereg | PCI_COMM_INTX_DISABLE;
        pci_config_put16(cfg_hdl, PCI_CONF_COMM, cmdreg);

#ifdef  DEBUG
        statreg = pci_config_get16(cfg_hdl, PCI_CONF_STAT);
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: "
            "status register is 0x%x\n", statreg));
#endif /* DEBUG */

        /* Read the bit back */
        cmdreg = pci_config_get16(cfg_hdl, PCI_CONF_COMM);
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: "
            "command register is now 0x%x\n", cmdreg));

        *flagsp = DDI_INTR_FLAG_LEVEL;

        if (cmdreg & PCI_COMM_INTX_DISABLE) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_cap: "
                    "masking supported\n"));
                *flagsp |= (DDI_INTR_FLAG_MASKABLE |
                    DDI_INTR_FLAG_PENDING);
        }

        /* Restore the device back to the original state and return */
        pci_config_put16(cfg_hdl, PCI_CONF_COMM, savereg);

        pci_config_teardown(&cfg_hdl);
        return (DDI_SUCCESS);
}


/*
 * pci_intx_clr_mask:
 *      For non-MSI devices that comply to PCI v2.3 or greater;
 *      clear the bit10 in the command register.
 */
int
pci_intx_clr_mask(dev_info_t *dip)
{
        uint16_t                cmdreg;
        ddi_acc_handle_t        cfg_hdl;

        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_clr_mask: %s%d: called\n",
            ddi_driver_name(dip), ddi_get_instance(dip)));

        if (pci_config_setup(dip, &cfg_hdl) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_clr_mask: can't get "
                    "config handle\n"));
                return (DDI_FAILURE);
        }

        cmdreg = pci_config_get16(cfg_hdl, PCI_CONF_COMM);
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_clr_mask: "
            "command register was 0x%x\n", cmdreg));

        /* Enable the interrupts */
        cmdreg &= ~PCI_COMM_INTX_DISABLE;
        pci_config_put16(cfg_hdl, PCI_CONF_COMM, cmdreg);
        pci_config_teardown(&cfg_hdl);
        return (DDI_SUCCESS);
}


/*
 * pci_intx_set_mask:
 *      For non-MSI devices that comply to PCI v2.3 or greater;
 *      set the bit10 in the command register.
 */
int
pci_intx_set_mask(dev_info_t *dip)
{
        uint16_t                cmdreg;
        ddi_acc_handle_t        cfg_hdl;

        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_set_mask: %s%d: called\n",
            ddi_driver_name(dip), ddi_get_instance(dip)));

        if (pci_config_setup(dip, &cfg_hdl) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_set_mask: can't get "
                    "config handle\n"));
                return (DDI_FAILURE);
        }

        cmdreg = pci_config_get16(cfg_hdl, PCI_CONF_COMM);
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_set_mask: "
            "command register was 0x%x\n", cmdreg));

        /* Disable the interrupts */
        cmdreg |= PCI_COMM_INTX_DISABLE;
        pci_config_put16(cfg_hdl, PCI_CONF_COMM, cmdreg);
        pci_config_teardown(&cfg_hdl);
        return (DDI_SUCCESS);
}

/*
 * pci_intx_get_pending:
 *      For non-MSI devices that comply to PCI v2.3 or greater;
 *      read the status register. Bit 3 of status register is
 *      Interrupt state. If it is set; then the interrupt is
 *      'Pending'.
 */
int
pci_intx_get_pending(dev_info_t *dip, int *pendingp)
{
        uint16_t                statreg;
        ddi_acc_handle_t        cfg_hdl;

        *pendingp = 0;
        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_pending: %s%d: called\n",
            ddi_driver_name(dip), ddi_get_instance(dip)));

        if (pci_config_setup(dip, &cfg_hdl) != DDI_SUCCESS) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_pending: can't get "
                    "config handle\n"));
                return (DDI_FAILURE);
        }

        statreg = pci_config_get16(cfg_hdl, PCI_CONF_STAT);

        if (statreg & PCI_STAT_INTR) {
                DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_pending: "
                    "interrupt is pending\n"));
                *pendingp = 1;
        }

        pci_config_teardown(&cfg_hdl);
        return (DDI_SUCCESS);
}


/*
 * pci_intx_get_ispec:
 *      Get intrspec for PCI devices (legacy support)
 *      NOTE: This is moved here from x86 pci.c and is
 *      needed here as pci-ide.c uses it as well
 */
/*ARGSUSED*/
ddi_intrspec_t
pci_intx_get_ispec(dev_info_t *dip, dev_info_t *rdip, int inum)
{
        int                             *intpriorities;
        uint_t                          num_intpriorities;
        struct intrspec                 *ispec;
        ddi_acc_handle_t                cfg_hdl;
        struct ddi_parent_private_data  *pdptr;

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

        ispec = pdptr->par_intr;
        ASSERT(ispec);

        /* check if the intrspec_pri has been initialized */
        if (!ispec->intrspec_pri) {
                if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip,
                    DDI_PROP_DONTPASS, "interrupt-priorities",
                    &intpriorities, &num_intpriorities) == DDI_PROP_SUCCESS) {
                        if (inum < num_intpriorities)
                                ispec->intrspec_pri = intpriorities[inum];
                        ddi_prop_free(intpriorities);
                }

                /* If still no priority, guess based on the class code */
                if (ispec->intrspec_pri == 0)
                        ispec->intrspec_pri = pci_class_to_pil(rdip);
        }

        /* Get interrupt line value */
        if (!ispec->intrspec_vec) {
                if (pci_config_setup(rdip, &cfg_hdl) != DDI_SUCCESS) {
                        DDI_INTR_NEXDBG((CE_CONT, "pci_intx_get_iline: "
                            "can't get config handle\n"));
                        return ((ddi_intrspec_t)ispec);
                }

                ispec->intrspec_vec = pci_config_get8(cfg_hdl, PCI_CONF_ILINE);
                pci_config_teardown(&cfg_hdl);
        }

        return ((ddi_intrspec_t)ispec);
}

static uint32_t
pci_match_class_val(uint32_t key, pci_class_val_t *rec_p, int nrec,
    uint32_t default_val)
{
        int i;

        for (i = 0; i < nrec; rec_p++, i++) {
                if ((rec_p->class_code & rec_p->class_mask) ==
                    (key & rec_p->class_mask))
                        return (rec_p->class_val);
        }

        return (default_val);
}

/*
 * Return the configuration value, based on class code and sub class code,
 * from the specified property based or default pci_class_val_t table.
 */
uint32_t
pci_class_to_val(dev_info_t *rdip, char *property_name, pci_class_val_t *rec_p,
    int nrec, uint32_t default_val)
{
        int property_len;
        uint32_t class_code;
        pci_class_val_t *conf;
        uint32_t val = default_val;

        /*
         * Use the "class-code" property to get the base and sub class
         * codes for the requesting device.
         */
        class_code = (uint32_t)ddi_prop_get_int(DDI_DEV_T_ANY, rdip,
            DDI_PROP_DONTPASS, "class-code", -1);

        if (class_code == -1)
                return (val);

        /* look up the val from the default table */
        val = pci_match_class_val(class_code, rec_p, nrec, val);


        /* see if there is a more specific property specified value */
        if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_NOTPROM,
            property_name, (caddr_t)&conf, &property_len))
                        return (val);

        if ((property_len % sizeof (pci_class_val_t)) == 0)
                val = pci_match_class_val(class_code, conf,
                    property_len / sizeof (pci_class_val_t), val);
        kmem_free(conf, property_len);
        return (val);
}

/*
 * pci_class_to_pil:
 *
 * Return the pil for a given PCI device.
 */
uint32_t
pci_class_to_pil(dev_info_t *rdip)
{
        uint32_t pil;

        /* Default pil is 1 */
        pil = pci_class_to_val(rdip,
            "pci-class-priorities", pci_default_pil,
            sizeof (pci_default_pil) / sizeof (pci_class_val_t), 1);

        /* Range check the result */
        if (pil >= 0xf)
                pil = 1;

        return (pil);
}

/*
 * pci_class_to_intr_weight:
 *
 * Return the intr_weight for a given PCI device.
 */
int32_t
pci_class_to_intr_weight(dev_info_t *rdip)
{
        int32_t intr_weight;

        /* default weight is 0% */
        intr_weight = pci_class_to_val(rdip,
            "pci-class-intr-weights", pci_default_intr_weight,
            sizeof (pci_default_intr_weight) / sizeof (pci_class_val_t), 0);

        /* range check the result */
        if (intr_weight < 0)
                intr_weight = 0;
        if (intr_weight > 1000)
                intr_weight = 1000;

        return (intr_weight);
}