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
* 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(__i386) || defined(__amd64)
{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, NULL, *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, NULL, *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, NULL, caps_ptr, PCI_MSI_CTRL, msi_ctrl);
DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_ctrl = %x\n",
PCI_CAP_GET16(h, NULL, caps_ptr, PCI_MSI_CTRL)));
/* Set the "data" and "addr" bits */
PCI_CAP_PUT32(h, NULL, caps_ptr, PCI_MSI_ADDR_OFFSET, addr);
DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_addr = %x\n",
PCI_CAP_GET32(h, NULL, caps_ptr, PCI_MSI_ADDR_OFFSET)));
if (msi_ctrl & PCI_MSI_64BIT_MASK) {
PCI_CAP_PUT32(h, NULL, 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, NULL,
caps_ptr, PCI_MSI_ADDR_OFFSET + 4)));
PCI_CAP_PUT16(h, NULL, caps_ptr, PCI_MSI_64BIT_DATA,
data);
DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_data "
"= %x\n", PCI_CAP_GET16(h, NULL, caps_ptr,
PCI_MSI_64BIT_DATA)));
} else {
PCI_CAP_PUT16(h, NULL, caps_ptr, PCI_MSI_32BIT_DATA,
data);
DDI_INTR_NEXDBG((CE_CONT, "pci_msi_configure: msi_data "
"= %x\n", PCI_CAP_GET16(h, NULL, 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, NULL, caps_ptr, PCI_MSI_CTRL, msi_ctrl);
PCI_CAP_PUT32(h, NULL, caps_ptr, PCI_MSI_ADDR_OFFSET, 0);
if (msi_ctrl & PCI_MSI_64BIT_MASK) {
PCI_CAP_PUT16(h, NULL, caps_ptr, PCI_MSI_64BIT_DATA,
0);
PCI_CAP_PUT32(h, NULL, caps_ptr, PCI_MSI_ADDR_OFFSET
+ 4, 0);
} else {
PCI_CAP_PUT16(h, NULL, caps_ptr, PCI_MSI_32BIT_DATA,
0);
}
DDI_INTR_NEXDBG((CE_CONT, "pci_msi_unconfigure: msi_ctrl "
"= %x\n", PCI_CAP_GET16(h, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, caps_ptr,
offset)) == PCI_CAP_EINVAL32)
goto done;
mask_bits |= (1 << inum);
PCI_CAP_PUT32(cfg_hdle, NULL, 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, NULL, caps_ptr,
offset)) == PCI_CAP_EINVAL32)
goto done;
mask_bits &= ~(1 << inum);
PCI_CAP_PUT32(cfg_hdle, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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);
}