pci_intr_lib.c revision 8181b438236881e6d31f7155101c85c011a9b6bb
/*
* 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 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/pci_intr_lib.h>
/*
* MSI-X BIR Index Table:
*
* BAR indicator register (BIR) to Base Address register.
*/
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.
*/
{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.
*/
{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
{
"%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 */
/*
* Note that the spec only requires 32-bit accesses
* to be supported. Apparently some chipsets don't
* support 64-bit accesses.
*/
addr >> 32);
"msix_addr 0x%x.%x msix_data 0x%x\n",
}
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 */
/*
* Note that the spec only requires 32-bit accesses
* to be supported. Apparently some chipsets don't
* support 64-bit accesses.
*/
}
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
{
(void *)rdip));
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.
*
* Note that the spec only requires 32-bit accesses to be supported.
* Apparently some chipsets don't support 64-bit accesses.
*/
/* 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_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 *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 interrupt line value */
if (!ispec->intrspec_vec) {
"can't get config handle\n"));
return ((ddi_intrspec_t)ispec);
}
}
return ((ddi_intrspec_t)ispec);
}
static uint32_t
{
int i;
}
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.
*/
{
int property_len;
/*
* Use the "class-code" property to get the base and sub class
* codes for the requesting device.
*/
if (class_code == -1)
return (val);
/* look up the val from the default table */
/* see if there is a more specific property specified value */
return (val);
if ((property_len % sizeof (pci_class_val_t)) == 0)
return (val);
}
/*
* pci_class_to_pil:
*
* Return the pil for a given PCI device.
*/
{
/* Default pil is 1 */
"pci-class-priorities", pci_default_pil,
/* 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.
*/
{
/* default weight is 0% */
"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);
}