|
@@ -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;
|
|
|
}
|
|
|
|
|
|
/**
|