pci_intr_lib.c revision a763904894d1c7d4593dc27d5f0c8e03c6c1936f
/*
* 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
* 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 2007 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
*/
/*
* MSI-X BIR Index Table:
*
* BAR indicator register (BIR) to Base Address register.
*/
0x20, 0x24, 0xff, 0xff};
/*
* 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
{
"%s%d can't get config handle",
return (DDI_FAILURE);
}
(type == DDI_INTR_TYPE_MSI)) {
PCI_MSI_CTRL)) == PCI_CAP_EINVAL16)
goto done;
return (DDI_SUCCESS);
}
(type == DDI_INTR_TYPE_MSIX)) {
goto done;
return (DDI_SUCCESS);
}
done:
return (DDI_FAILURE);
}
/*
* pci_msi_get_cap:
*
* Get the capabilities of the MSI/X interrupt
*/
int
{
(void *)rdip));
*flagsp = 0;
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
if (msi_ctrl & PCI_MSI_64BIT_MASK)
if (msi_ctrl & PCI_MSI_PVM_MASK)
*flagsp |= (DDI_INTR_FLAG_MASKABLE |
else
} else if (type == DDI_INTR_TYPE_MSIX) {
/* MSI-X supports PVM, 64bit by default */
}
*flagsp |= DDI_INTR_FLAG_EDGE;
return (DDI_SUCCESS);
}
/*
* pci_msi_configure:
*
* capability structure.
*/
/* ARGSUSED */
int
{
&caps_ptr, &h) != DDI_SUCCESS)
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
/* Set the bits to inform how many MSIs are enabled */
/* Set the "data" and "addr" bits */
if (msi_ctrl & PCI_MSI_64BIT_MASK) {
data);
} else {
data);
}
} else if (type == DDI_INTR_TYPE_MSIX) {
/* Offset into the "inum"th entry in the MSI-X table */
(inum * PCI_MSIX_VECTOR_SIZE);
/* Set the "data" and "addr" bits */
}
pci_config_teardown(&h);
return (DDI_SUCCESS);
}
/*
* pci_msi_unconfigure:
*
* capability structure.
*/
/* ARGSUSED */
int
{
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
msi_ctrl &= (~PCI_MSI_MME_MASK);
if (msi_ctrl & PCI_MSI_64BIT_MASK) {
0);
+ 4, 0);
} else {
0);
}
} else if (type == DDI_INTR_TYPE_MSIX) {
/* Offset into the "inum"th entry in the MSI-X table */
(inum * PCI_MSIX_VECTOR_SIZE);
/* Reset the "data" and "addr" bits */
}
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
{
int ret = DDI_FAILURE;
return (DDI_FAILURE);
ret = DDI_SUCCESS;
ret = DDI_SUCCESS;
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
{
(void *)rdip));
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
if (msi_ctrl & PCI_MSI_ENABLE_BIT)
goto finished;
} else if (type == DDI_INTR_TYPE_MSIX) {
if (msi_ctrl & PCI_MSIX_ENABLE_BIT)
goto finished;
msi_ctrl);
}
msi_ctrl));
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
{
/*
* Do not turn off the master enable bit if other interrupts are
* still active.
*/
if ((flags != DDI_INTR_FLAG_BLOCK) &&
return (DDI_SUCCESS);
return (DDI_FAILURE);
/* Reset the "enable" bit */
if (type == DDI_INTR_TYPE_MSI) {
if (!(msi_ctrl & PCI_MSI_ENABLE_BIT))
goto finished;
} else if (type == DDI_INTR_TYPE_MSIX) {
if (!(msi_ctrl & PCI_MSIX_ENABLE_BIT))
goto finished;
msi_ctrl);
}
msi_ctrl));
return (DDI_SUCCESS);
}
/*
* pci_msi_set_mask:
*
* Set the mask bit in the MSI/X capability structure
*/
/* ARGSUSED */
int
{
int offset;
int ret = DDI_FAILURE;
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
if (!(msi_ctrl & PCI_MSI_PVM_MASK))
goto done;
offset)) == PCI_CAP_EINVAL32)
goto done;
} else if (type == DDI_INTR_TYPE_MSIX) {
/* Set function mask */
if (msi_ctrl & PCI_MSIX_FUNCTION_MASK) {
ret = DDI_SUCCESS;
goto done;
}
/* Offset into the "inum"th entry in the MSI-X table */
/* Set the Mask bit */
}
ret = DDI_SUCCESS;
done:
return (ret);
}
/*
* pci_msi_clr_mask:
*
* Clear the mask bit in the MSI/X capability structure
*/
/* ARGSUSED */
int
{
int offset;
int ret = DDI_FAILURE;
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
if (!(msi_ctrl & PCI_MSI_PVM_MASK))
goto done;
offset)) == PCI_CAP_EINVAL32)
goto done;
} else if (type == DDI_INTR_TYPE_MSIX) {
if (msi_ctrl & PCI_MSIX_FUNCTION_MASK) {
ret = DDI_SUCCESS;
goto done;
}
/* Offset into the "inum"th entry in the MSI-X table */
/* Clear the Mask bit */
}
ret = DDI_SUCCESS;
done:
return (ret);
}
/*
* pci_msi_get_pending:
*
* Get the pending bit from the MSI/X capability structure
*/
/* ARGSUSED */
int
{
int offset;
int ret = DDI_FAILURE;
(void *)rdip));
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
if (!(msi_ctrl & PCI_MSI_PVM_MASK)) {
"PVM is not supported\n"));
goto done;
}
offset)) == PCI_CAP_EINVAL32)
goto done;
} else if (type == DDI_INTR_TYPE_MSIX) {
/* Offset into the PBA array which has entry for "inum" */
/* Read the PBA array */
}
ret = DDI_SUCCESS;
done:
return (ret);
}
/*
* pci_msi_get_nintrs:
*
* For a given type (MSI/X) returns the number of interrupts supported
*/
int
{
(void *)rdip));
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
} else if (type == DDI_INTR_TYPE_MSIX) {
if (msi_ctrl & PCI_MSIX_TBL_SIZE_MASK)
}
"nintr = 0x%x\n", *nintrs));
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
{
/* Check for valid input argument */
return (DDI_EINVAL);
return (DDI_FAILURE);
if (type == DDI_INTR_TYPE_MSI) {
} else if (type == DDI_INTR_TYPE_MSIX) {
}
return (DDI_SUCCESS);
}
/*
* pci_msi_get_supported_type:
*
* types if device supports them. A DDI_FAILURE is returned otherwise.
*/
int
{
"rdip = 0x%p\n", (void *)rdip));
*typesp = 0;
*typesp |= DDI_INTR_TYPE_MSI;
}
*typesp |= DDI_INTR_TYPE_MSIX;
}
}
/*
* pci_msix_init:
* needed for MSI-X support. It also allocates a private
* structure to keep track of these.
*/
{
int i, ret;
return (NULL);
/*
* Initialize the devacc structure
*/
/* Map the entire MSI-X vector table */
PCI_MSIX_TBL_BIR_MASK]) == 0xff)
goto fail1;
!= DDI_PROP_SUCCESS) {
"ddi_prop_lookup_int_array failed %d\n", ret));
goto fail1;
}
reg_size = sizeof (pci_regspec_t) / sizeof (int);
(addr_space == PCI_ADDR_MEM64))) {
rnumber = i;
break;
}
}
if (rnumber == 0) {
"no mtaching reg number for offset 0x%x\n", breg));
goto fail2;
}
"ddi_regs_map_setup failed %d\n", ret));
goto fail2;
}
/*
* Map in the MSI-X Pending Bit Array
*/
PCI_MSIX_PBA_BIR_MASK]) == 0xff)
goto fail3;
pba_tbl_size));
(addr_space == PCI_ADDR_MEM64))) {
rnumber = i;
break;
}
}
if (rnumber == 0) {
"no matching reg number for offset 0x%x\n", breg));
goto fail3;
}
"ddi_regs_map_setup failed %d\n", ret));
goto fail3;
}
(void *)msix_p));
goto done;
done:
return (msix_p);
}
/*
* pci_msix_fini:
* It is only called if no more MSI-X interrupts are being used.
*/
void
{
(void *)msix_p));
}
/*
* pci_msix_dup:
* This function duplicates the address and data pair of one msi-x
* vector to another msi-x vector.
*/
int
{
/* Offset into the original inum's entry in the MSI-X table */
/* For the MSI-X number passed in, get the "data" and "addr" fields */
/* Program new vector with these existing values */
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
{
#ifdef DEBUG
#endif /* DEBUG */
*flagsp = 0;
"config handle\n"));
return (DDI_FAILURE);
}
"command register was 0x%x\n", savereg));
/* Disable the interrupts */
#ifdef DEBUG
"status register is 0x%x\n", statreg));
#endif /* DEBUG */
/* Read the bit back */
"command register is now 0x%x\n", cmdreg));
if (cmdreg & PCI_COMM_INTX_DISABLE) {
"masking supported\n"));
*flagsp |= (DDI_INTR_FLAG_MASKABLE |
}
/* Restore the device back to the original state and return */
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
{
"config handle\n"));
return (DDI_FAILURE);
}
"command register was 0x%x\n", cmdreg));
/* Enable the interrupts */
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
{
"config handle\n"));
return (DDI_FAILURE);
}
"command register was 0x%x\n", cmdreg));
/* Disable the interrupts */
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
{
*pendingp = 0;
"config handle\n"));
return (DDI_FAILURE);
}
if (statreg & PCI_STAT_INTR) {
"interrupt is pending\n"));
*pendingp = 1;
}
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 ipl;
/*
* Use the class code values to construct an ipl for the device.
*/
switch (base_class) {
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;
case PCI_CLASS_SERIALBUS:
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*/
{
int class, *intpriorities;
struct ddi_parent_private_data *pdptr;
return (NULL);
/* check if the intrspec_pri has been initialized */
if (!ispec->intrspec_pri) {
DDI_PROP_DONTPASS, "interrupt-priorities",
if (inum < num_intpriorities)
}
/* If still no priority, guess based on the class code */
if (ispec->intrspec_pri == 0) {
/* get 'class' property to derive the intr priority */
}
}
/* Get interrupt line value */
if (!ispec->intrspec_vec) {
"can't get config handle\n"));
return ((ddi_intrspec_t)ispec);
}
}
return ((ddi_intrspec_t)ispec);
}