aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/pcie/portdrv_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/pcie/portdrv_core.c')
-rw-r--r--drivers/pci/pcie/portdrv_core.c171
1 files changed, 75 insertions, 96 deletions
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c
index ebc9d45bd731..a59210350c44 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -44,134 +44,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, &reg16);
- 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, &reg32);
- 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,
+ &reg32);
+ *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, &reg16);
- entry = reg16 & 0x1f;
- if (entry >= nr_entries)
- goto out_free_irqs;
+ if (pos) {
+ pci_read_config_word(dev, pos + PCI_EXP_DPC_CAP,
+ &reg16);
+ *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;
}
/**