/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

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

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

/*
 * Library utility functions
 */


/*
 * pci_check_pciex:
 *
 * check whether the device has PCI-E capability
 */
int
pci_check_pciex(dev_info_t *dip)
{
	ddi_acc_handle_t cfg_handle;
	ushort_t status;
	ushort_t cap;
	ushort_t capsp;
	ushort_t cap_count = PCI_CAP_MAX_PTR;

	DDI_INTR_NEXDBG((CE_CONT, "pci_check_pciex: dip: 0x%p, driver: %s, "
	    "binding: %s, nodename: %s\n", (void *)dip, ddi_driver_name(dip),
	    ddi_binding_name(dip), ddi_node_name(dip)));

	if (pci_config_setup(dip, &cfg_handle) != DDI_SUCCESS) {
		DDI_INTR_NEXDBG((CE_CONT, "pci_check_pciex: "
		    "pci_config_setup() failed\n"));
		return (DDI_FAILURE);
	}
	status = pci_config_get16(cfg_handle, PCI_CONF_STAT);
	if (!(status & PCI_STAT_CAP))
		goto notpciex;

	capsp = pci_config_get8(cfg_handle, PCI_CONF_CAP_PTR);
	while (cap_count-- && capsp >= PCI_CAP_PTR_OFF) {
		capsp &= PCI_CAP_PTR_MASK;
		cap = pci_config_get8(cfg_handle, capsp);
		DDI_INTR_NEXDBG((CE_CONT, "pci_check_pciex: capid=0x%x\n",
		    cap));
		if (cap == PCI_CAP_ID_PCI_E) {
			DDI_INTR_NEXDBG((CE_CONT, "pci_check_pciex: "
			    "PCI-Express capability found\n"));
			pci_config_teardown(&cfg_handle);
			return (DDI_SUCCESS);
		}
		capsp = pci_config_get8(cfg_handle, capsp + PCI_CAP_NEXT_PTR);
	}
notpciex:
	pci_config_teardown(&cfg_handle);
	return (DDI_FAILURE);
}


/*
 * 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 *cfg_hdle)
{
	ushort_t	cap, cap_count;

	*msi_ctrl = *caps_ptr = 0;

	if (pci_config_setup(dip, cfg_hdle) != 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);
	}

	/* Are capabilities supported? */
	if (!(pci_config_get16(*cfg_hdle, PCI_CONF_STAT) & PCI_STAT_CAP)) {
		DDI_INTR_NEXDBG((CE_CONT, "pci_get_msi_ctrl: "
		    "%p doesn't support capabilities\n", (void *)dip));

		pci_config_teardown(cfg_hdle);
		return (DDI_FAILURE);
	}

	*caps_ptr = pci_config_get8(*cfg_hdle, PCI_CONF_CAP_PTR);
	cap_count = PCI_CAP_MAX_PTR;

	while ((cap_count--) && (*caps_ptr >= PCI_CAP_PTR_OFF)) {
		*caps_ptr &= PCI_CAP_PTR_MASK;
		cap = pci_config_get8(*cfg_hdle, *caps_ptr);

		if ((cap == PCI_CAP_ID_MSI) && (type == DDI_INTR_TYPE_MSI)) {
			*msi_ctrl = pci_config_get16(*cfg_hdle,
			    *caps_ptr + PCI_MSI_CTRL);

			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 ((cap == PCI_CAP_ID_MSI_X) && (type == DDI_INTR_TYPE_MSIX)) {
			*msi_ctrl = pci_config_get16(*cfg_hdle,
			    *caps_ptr + PCI_MSIX_CTRL);

			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);
		}

		*caps_ptr = pci_config_get8(*cfg_hdle,
		    *caps_ptr + PCI_CAP_NEXT_PTR);
	}

	pci_config_teardown(cfg_hdle);

	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	cfg_hdle;

	DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: 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) {
		/* Set the bits to inform how many MSIs are enabled */
		msi_ctrl |= ((highbit(count) -1) << PCI_MSI_MME_SHIFT);
		pci_config_put16(cfg_hdle,
		    caps_ptr + PCI_MSI_CTRL, msi_ctrl);

		/* Set the "data" and "addr" bits */
		pci_config_put32(cfg_hdle,
		    caps_ptr + PCI_MSI_ADDR_OFFSET, addr);

		if (msi_ctrl &  PCI_MSI_64BIT_MASK) {
			pci_config_put32(cfg_hdle,
			    caps_ptr + PCI_MSI_ADDR_OFFSET + 4, 0);
			pci_config_put16(cfg_hdle,
			    caps_ptr + PCI_MSI_64BIT_DATA, data);
		} else {
			pci_config_put16(cfg_hdle,
			    caps_ptr + PCI_MSI_32BIT_DATA, data);
		}

		DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_ctrl = %x\n",
		    pci_config_get16(cfg_hdle, 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 - 1) * PCI_MSIX_VECTOR_SIZE);

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

		ddi_put64(msix_p->msix_tbl_hdl, (uint64_t *)off, addr);
	}

	pci_config_teardown(&cfg_hdle);
	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	cfg_hdle;

	DDI_INTR_NEXDBG((CE_CONT, "pci_msi_unconfigure: 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) {
		msi_ctrl &= (~PCI_MSI_MME_MASK);
		pci_config_put16(cfg_hdle, caps_ptr + PCI_MSI_CTRL, msi_ctrl);

		pci_config_put32(cfg_hdle,
		    caps_ptr + PCI_MSI_ADDR_OFFSET, 0);
		if (msi_ctrl &  PCI_MSI_64BIT_MASK) {
			pci_config_put16(cfg_hdle,
			    caps_ptr + PCI_MSI_64BIT_DATA, 0);
			pci_config_put32(cfg_hdle,
			    caps_ptr + PCI_MSI_ADDR_OFFSET + 4, 0);
		} else {
			pci_config_put16(cfg_hdle,
			    caps_ptr + PCI_MSI_32BIT_DATA, 0);
		}

		DDI_INTR_NEXDBG((CE_CONT, "pci_msi_unconfigure: "
		    "msi_ctrl = %x\n",
		    pci_config_get16(cfg_hdle, 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 - 1) * PCI_MSIX_VECTOR_SIZE);

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

		ddi_put64(msix_p->msix_tbl_hdl, (uint64_t *)off, 0);
	}

	pci_config_teardown(&cfg_hdle);
	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.
 */
int
pci_msi_enable_mode(dev_info_t *rdip, int type, int inum)
{
	ushort_t		caps_ptr, msi_ctrl;
	uint16_t		cmd_reg;
	ddi_acc_handle_t	cfg_hdle;

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

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

	/* Disable INTx simulation, if applicable */
	cmd_reg = pci_config_get16(cfg_hdle, PCI_CONF_COMM);

	/* This write succeeds only for devices > PCI2.3 */
	pci_config_put16(cfg_hdle, PCI_CONF_COMM,
	    cmd_reg | PCI_COMM_INTX_DISABLE);

	DDI_INTR_NEXDBG((CE_CONT, "pci_msi_enable_mode: "
	    "Before CmdReg = 0x%x, After CmdReg = 0x%x\n",
	    cmd_reg, pci_config_get16(cfg_hdle, PCI_CONF_COMM)));

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

		msi_ctrl |= PCI_MSI_ENABLE_BIT;
		pci_config_put16(cfg_hdle,
		    caps_ptr + PCI_MSI_CTRL, msi_ctrl);

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

		if (msi_ctrl & PCI_MSIX_ENABLE_BIT)
			goto finished;

		msi_ctrl |= PCI_MSIX_ENABLE_BIT;
		pci_config_put16(cfg_hdle,
		    caps_ptr + PCI_MSIX_CTRL, msi_ctrl);

		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 - 1) *
		    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

		/* Clear the Mask bit */
		mask_bits = ddi_get32(msix_p->msix_tbl_hdl,
		    (uint32_t *)((uchar_t *)off));

		mask_bits &= ~0;

		ddi_put32(msix_p->msix_tbl_hdl,
		    (uint32_t *)((uchar_t *)off), mask_bits);
	}

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.
 */
int
pci_msi_disable_mode(dev_info_t *rdip, int type, int inum)
{
	ushort_t		caps_ptr, msi_ctrl;
	ddi_acc_handle_t	cfg_hdle;

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

	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_config_put16(cfg_hdle,
		    caps_ptr + PCI_MSI_CTRL, msi_ctrl);
	} else if (type == DDI_INTR_TYPE_MSIX) {
		uintptr_t		off;
		ddi_intr_msix_t		*msix_p;

		if (!(msi_ctrl & PCI_MSIX_ENABLE_BIT))
			goto finished;

		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 - 1) *
		    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

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

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;
	uint_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;

		mask_bits = pci_config_get32(cfg_hdle,
		    caps_ptr + offset);

		mask_bits |= (1 << inum);

		pci_config_put32(cfg_hdle,
		    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 - 1) *
		    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

		/* Set the Mask bit */
		ddi_put32(msix_p->msix_tbl_hdl,
		    (uint32_t *)((uchar_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;
	uint_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;
		mask_bits = pci_config_get32(cfg_hdle,
		    caps_ptr + offset);

		mask_bits &= ~(1 << inum);

		pci_config_put32(cfg_hdle,
		    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 - 1) *
		    PCI_MSIX_VECTOR_SIZE) + PCI_MSIX_VECTOR_CTRL_OFFSET;

		/* Clear the Mask bit */
		mask_bits = ddi_get32(msix_p->msix_tbl_hdl,
		    (uint32_t *)((uchar_t *)off));

		mask_bits &= ~0;

		ddi_put32(msix_p->msix_tbl_hdl,
		    (uint32_t *)((uchar_t *)off), mask_bits);
	}

	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;

		pending_bits = pci_config_get32(cfg_hdle,
		    caps_ptr + offset);

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

		/* Read the PBA array */
		pending_bits = ddi_get64(msix_p->msix_pba_hdl,
		    (uint64_t *)((uchar_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_config_put16(cfg_hdle, 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;
	size_t			msix_tbl_size;
	size_t			pba_tbl_size;
	ushort_t		caps_ptr, msi_ctrl;
	ddi_intr_msix_t		*msix_p;
	ddi_acc_handle_t	cfg_hdle;

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

	if (pci_get_msi_ctrl(rdip, DDI_INTR_TYPE_MSI, &msi_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_config_get32(cfg_hdle,
	    caps_ptr + PCI_MSIX_TBL_OFFSET);
	rnumber = msix_p->msix_tbl_offset & PCI_MSIX_TBL_BIR_MASK;
	msix_p->msix_tbl_offset &= ~rnumber; /* Clear BIR from the offset */

	msix_tbl_size = (msi_ctrl & PCI_MSIX_TBL_SIZE_MASK) + 1;

	if (ddi_regs_map_setup(rdip,
	    rnumber,
	    &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_initialize: MSI-X Table "
		    "ddi_regs_map_setup failed\n"));
		kmem_free(msix_p, sizeof (ddi_intr_msix_t));
		pci_config_teardown(&cfg_hdle);
		return (NULL);
	}

	/*
	 * Map in the MSI-X Pending Bit Array
	 */
	if (msi_ctrl &  PCI_MSIX_TBL_SIZE_MASK)
		pba_tbl_size = ((msi_ctrl & PCI_MSIX_TBL_SIZE_MASK) + 1)/64;

	msix_p->msix_pba_offset = pci_config_get32(cfg_hdle,
	    caps_ptr + PCI_MSIX_PBA_OFFSET);
	rnumber = msix_p->msix_pba_offset & PCI_MSIX_PBA_BIR_MASK;
	msix_p->msix_pba_offset &= ~rnumber; /* Clear offset from BIR */

	if (ddi_regs_map_setup(rdip,
	    rnumber,
	    &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_initialize: PBA "
		    "ddi_regs_map_setup failed\n"));
		ddi_regs_map_free(&msix_p->msix_tbl_hdl);
		kmem_free(msix_p, sizeof (ddi_intr_msix_t));
		pci_config_teardown(&cfg_hdle);
		return (NULL);
	}

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

	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));
}



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

	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 | DDI_INTR_FLAG_LEVEL);
	}

	/* 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_devclass_to_ipl:
 *	translate from device class to ipl
 *	NOTE: This function is  added here as pci_intx_get_ispec()
 *	calls this to figure out the priority.
 *	It is moved over from x86 pci.c
 */
int
pci_devclass_to_ipl(int class)
{
	int	base_cl;
	int	ipl;

	base_cl = (class & 0xff0000) >> 16;

	/*
	 * Use the class code values to construct an ipl for the device.
	 */
	switch (base_cl) {
	default:
	case PCI_CLASS_NONE:
		ipl = 1;
		break;
	case PCI_CLASS_MASS:
		ipl = 0x5;
		break;
	case PCI_CLASS_NET:
		ipl = 0x6;
		break;
	case PCI_CLASS_DISPLAY:
		ipl = 0x9;
		break;
	/*
	 * for high priority interrupt handlers, use level 12
	 * as the highest for device drivers
	 */
	case PCI_CLASS_MM:
		ipl = 0xc;
		break;
	case PCI_CLASS_MEM:
		ipl = 0xc;
		break;
	case PCI_CLASS_BRIDGE:
		ipl = 0xc;
		break;
	}
	return (ipl);
}


/*
 * 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				class, *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) {
			/* get 'class' property to derive the intr priority */
			class = ddi_prop_get_int(DDI_DEV_T_ANY, rdip,
			    DDI_PROP_DONTPASS, "class-code", -1);
			ispec->intrspec_pri = (class == -1) ? 1 :
			    pci_devclass_to_ipl(class);
		}
	}

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

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

	return ((ddi_intrspec_t)ispec);
}