diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2017-11-14 12:11:23 -0600 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2017-11-14 12:11:23 -0600 |
commit | 6018182d3158505f11103adaee8ffb53424df986 (patch) | |
tree | bf2421e0bf69b3a3b74d26acd706bdf264e3ada4 /drivers/pci | |
parent | 65a129d7847e4f5b24c6e56ab49090b1f6d0f3dc (diff) | |
parent | a579ba49a9e27bfc1d5cb69b0ea3781d8df46b5b (diff) | |
download | linux-6018182d3158505f11103adaee8ffb53424df986.tar.bz2 |
Merge branch 'pci/msi' into next
* pci/msi:
PCI/portdrv: Compute MSI/MSI-X IRQ vectors after final allocation
PCI/portdrv: Factor out Interrupt Message Number lookup
PCI/portdrv: Consolidate comments
PCI/portdrv: Add #defines for AER and DPC Interrupt Message Number masks
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/pcie/portdrv_core.c | 171 |
1 files changed, 75 insertions, 96 deletions
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 313a21df1692..3cd5eb48644a 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -43,134 +43,113 @@ static void release_pcie_device(struct device *dev) kfree(to_pcie_device(dev)); } -/** - * pcie_port_enable_irq_vec - try to set up MSI-X or MSI as interrupt mode - * for given port - * @dev: PCI Express port to handle - * @irqs: Array of interrupt vectors to populate - * @mask: Bitmask of port capabilities returned by get_port_device_capability() - * - * Return value: 0 on success, error code on failure +/* + * Fill in *pme, *aer, *dpc with the relevant Interrupt Message Numbers if + * services are enabled in "mask". Return the number of MSI/MSI-X vectors + * required to accommodate the largest Message Number. */ -static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask) +static int pcie_message_numbers(struct pci_dev *dev, int mask, + u32 *pme, u32 *aer, u32 *dpc) { - int nr_entries, entry, nvec = 0; + u32 nvec = 0, pos, reg32; + u16 reg16; /* - * Allocate as many entries as the port wants, so that we can check - * which of them will be useful. Moreover, if nr_entries is correctly - * equal to the number of entries this port actually uses, we'll happily - * go through without any tricks. + * The Interrupt Message Number indicates which vector is used, i.e., + * the MSI-X table entry or the MSI offset between the base Message + * Data and the generated interrupt message. See PCIe r3.1, sec + * 7.8.2, 7.10.10, 7.31.2. */ - nr_entries = pci_alloc_irq_vectors(dev, 1, PCIE_PORT_MAX_MSI_ENTRIES, - PCI_IRQ_MSIX | PCI_IRQ_MSI); - if (nr_entries < 0) - return nr_entries; if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) { - u16 reg16; - - /* - * Per PCIe r3.1, sec 6.1.6, "PME and Hot-Plug Event - * interrupts (when both are implemented) always share the - * same MSI or MSI-X vector, as indicated by the Interrupt - * Message Number field in the PCI Express Capabilities - * register". - * - * Per sec 7.8.2, "For MSI, the [Interrupt Message Number] - * indicates the offset between the base Message Data and - * the interrupt message that is generated." - * - * "For MSI-X, the [Interrupt Message Number] indicates - * which MSI-X Table entry is used to generate the - * interrupt message." - */ pcie_capability_read_word(dev, PCI_EXP_FLAGS, ®16); - entry = (reg16 & PCI_EXP_FLAGS_IRQ) >> 9; - if (entry >= nr_entries) - goto out_free_irqs; - - irqs[PCIE_PORT_SERVICE_PME_SHIFT] = pci_irq_vector(dev, entry); - irqs[PCIE_PORT_SERVICE_HP_SHIFT] = pci_irq_vector(dev, entry); - - nvec = max(nvec, entry + 1); + *pme = (reg16 & PCI_EXP_FLAGS_IRQ) >> 9; + nvec = *pme + 1; } if (mask & PCIE_PORT_SERVICE_AER) { - u32 reg32, pos; - - /* - * Per PCIe r3.1, sec 7.10.10, the Advanced Error Interrupt - * Message Number in the Root Error Status register - * indicates which MSI/MSI-X vector is used for AER. - * - * "For MSI, the [Advanced Error Interrupt Message Number] - * indicates the offset between the base Message Data and - * the interrupt message that is generated." - * - * "For MSI-X, the [Advanced Error Interrupt Message - * Number] indicates which MSI-X Table entry is used to - * generate the interrupt message." - */ pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); - pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, ®32); - entry = reg32 >> 27; - if (entry >= nr_entries) - goto out_free_irqs; - - irqs[PCIE_PORT_SERVICE_AER_SHIFT] = pci_irq_vector(dev, entry); - - nvec = max(nvec, entry + 1); + if (pos) { + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, + ®32); + *aer = (reg32 & PCI_ERR_ROOT_AER_IRQ) >> 27; + nvec = max(nvec, *aer + 1); + } } if (mask & PCIE_PORT_SERVICE_DPC) { - u16 reg16, pos; - - /* - * Per PCIe r4.0 (v0.9), sec 7.9.15.2, the DPC Interrupt - * Message Number in the DPC Capability register indicates - * which MSI/MSI-X vector is used for DPC. - * - * "For MSI, the [DPC Interrupt Message Number] indicates - * the offset between the base Message Data and the - * interrupt message that is generated." - * - * "For MSI-X, the [DPC Interrupt Message Number] indicates - * which MSI-X Table entry is used to generate the - * interrupt message." - */ pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DPC); - pci_read_config_word(dev, pos + PCI_EXP_DPC_CAP, ®16); - entry = reg16 & 0x1f; - if (entry >= nr_entries) - goto out_free_irqs; + if (pos) { + pci_read_config_word(dev, pos + PCI_EXP_DPC_CAP, + ®16); + *dpc = reg16 & PCI_EXP_DPC_IRQ; + nvec = max(nvec, *dpc + 1); + } + } + + return nvec; +} - irqs[PCIE_PORT_SERVICE_DPC_SHIFT] = pci_irq_vector(dev, entry); +/** + * pcie_port_enable_irq_vec - try to set up MSI-X or MSI as interrupt mode + * for given port + * @dev: PCI Express port to handle + * @irqs: Array of interrupt vectors to populate + * @mask: Bitmask of port capabilities returned by get_port_device_capability() + * + * Return value: 0 on success, error code on failure + */ +static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask) +{ + int nr_entries, nvec; + u32 pme = 0, aer = 0, dpc = 0; - nvec = max(nvec, entry + 1); + /* Allocate the maximum possible number of MSI/MSI-X vectors */ + nr_entries = pci_alloc_irq_vectors(dev, 1, PCIE_PORT_MAX_MSI_ENTRIES, + PCI_IRQ_MSIX | PCI_IRQ_MSI); + if (nr_entries < 0) + return nr_entries; + + /* See how many and which Interrupt Message Numbers we actually use */ + nvec = pcie_message_numbers(dev, mask, &pme, &aer, &dpc); + if (nvec > nr_entries) { + pci_free_irq_vectors(dev); + return -EIO; } /* - * If nvec is equal to the allocated number of entries, we can just use - * what we have. Otherwise, the port has some extra entries not for the - * services we know and we need to work around that. + * If we allocated more than we need, free them and reallocate fewer. + * + * Reallocating may change the specific vectors we get, so + * pci_irq_vector() must be done *after* the reallocation. + * + * If we're using MSI, hardware is *allowed* to change the Interrupt + * Message Numbers when we free and reallocate the vectors, but we + * assume it won't because we allocate enough vectors for the + * biggest Message Number we found. */ if (nvec != nr_entries) { - /* Drop the temporary MSI-X setup */ pci_free_irq_vectors(dev); - /* Now allocate the MSI-X vectors for real */ nr_entries = pci_alloc_irq_vectors(dev, nvec, nvec, PCI_IRQ_MSIX | PCI_IRQ_MSI); if (nr_entries < 0) return nr_entries; } - return 0; + /* PME and hotplug share an MSI/MSI-X vector */ + if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) { + irqs[PCIE_PORT_SERVICE_PME_SHIFT] = pci_irq_vector(dev, pme); + irqs[PCIE_PORT_SERVICE_HP_SHIFT] = pci_irq_vector(dev, pme); + } -out_free_irqs: - pci_free_irq_vectors(dev); - return -EIO; + if (mask & PCIE_PORT_SERVICE_AER) + irqs[PCIE_PORT_SERVICE_AER_SHIFT] = pci_irq_vector(dev, aer); + + if (mask & PCIE_PORT_SERVICE_DPC) + irqs[PCIE_PORT_SERVICE_DPC_SHIFT] = pci_irq_vector(dev, dpc); + + return 0; } /** |