/* * 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 #include #include #include #include #include #include /* * 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 **)®s_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 *)®s_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 *)®s_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); }