The Root Port can be enabled to generate interrupt in response of PME (Power Management Event), Hot-Plug Events and change of error status of AER (Advanced Error Reporting), using MSI or MSI-X. If a Root Port is configured to use multiple messages on MSI/MSI-X, which vector is associated to the event is indicated in some typical registers. However current code of portdrv_core.c doesn't check the registers, through it allows the Root Port to use multiple messages on MSI-X. And there is a clear bug that the portdrv on MSI-X mode deals different vector to the PME service and the hot-plug service, since according to the PCI Express Base Specification it notes that PME and Hot-Plug Events always share the same MSI/MSI-X vector. This patch modifies the code to check the registers indicating the number of vector, and tie the vector number to the proper service. Refer PCI Express Base Specification 2.0 for the detail of the interrupts for PME, Hot-Plug Events and AER: 6.1.6. Native PME Software Model 6.7.3.4. Software Notification of Hot-Plug Events 6.2.4.1.2. Interrupt Generation Signed-off-by: Hidetoshi Seto <seto.hidetoshi@xxxxxxxxxxxxxx> --- drivers/pci/pcie/portdrv_core.c | 80 +++++++++++++++++++++++++++++++++++---- 1 files changed, 72 insertions(+), 8 deletions(-) diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 890f0d2..0454fc3 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -14,6 +14,7 @@ #include <linux/string.h> #include <linux/slab.h> #include <linux/pcieport_if.h> +#include <linux/aer.h> #include "portdrv.h" @@ -128,7 +129,72 @@ static int is_msi_quirked(struct pci_dev *dev) } return quirk; } - + +static int fixup_msix(struct pci_dev *dev, int *vectors, + struct msix_entry *msix_entries , int mask) +{ + int i, j = 0, pos; + u16 reg16; + u32 reg32; + int pme_hp_mvec = 0, aer_mvec = 0; + + /* + * Interrupt Message Number: + * PME and Hot-Plug Event interrupts (when both are implemented) + * always share the same MSI/MSI-X vector, as indicated by this + * register in the PCI Express Capability register. + */ + if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) { + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®16); + pme_hp_mvec = (reg16 >> 9) & 0x1f; + } + + /* + * Advanced Error Interrupt Message Number: + * This number in the Root Error Status Register indicates which + * MSI/MSI-X vector is used for the interrupt message on error + * when any of error reporting of this Root Port is enabled. + */ + if (mask & PCIE_PORT_SERVICE_AER) { + pos = pci_find_aer_capability(dev); + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, ®32); + aer_mvec = (reg32 >> 27) & 0x1f; + } + + /* + * FIXME: We have initialized some anterior entries in the MSI-X Table + * without thinking much about which entry will be used for the events. + * Note that if both MSI and MSI-X are implemented, we need to enable + * MSI-X before reading above registers to get the numbers for MSI-X, + * otherwise the resisters may indicate the numbers for MSI. + */ + if ((pme_hp_mvec >= PCIE_PORT_DEVICE_MAXSERVICES) + || (aer_mvec >= PCIE_PORT_DEVICE_MAXSERVICES)) + return 1; + + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) { + switch (1 << i) { + case PCIE_PORT_SERVICE_PME: + case PCIE_PORT_SERVICE_HP: + vectors[i] = msix_entries[pme_hp_mvec].vector; + break; + case PCIE_PORT_SERVICE_AER: + vectors[i] = msix_entries[aer_mvec].vector; + break; + default: + while (j == pme_hp_mvec || j == aer_mvec) + j++; + BUG_ON(j >= PCIE_PORT_DEVICE_MAXSERVICES); + vectors[i] = msix_entries[j++].vector; + break; + } + } + } + return 0; +} + static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) { int i, pos, nvec, status = -EINVAL; @@ -153,13 +219,11 @@ static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) dev_info(&dev->dev, "found MSI-X capability\n"); status = pci_enable_msix(dev, msix_entries, nvec); if (!status) { - int j = 0; - - interrupt_mode = PCIE_PORT_MSIX_MODE; - for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { - if (mask & (1 << i)) - vectors[i] = msix_entries[j++].vector; - } + status = fixup_msix(dev, vectors, msix_entries, mask); + if (status) + pci_disable_msix(dev); + else + interrupt_mode = PCIE_PORT_MSIX_MODE; } } if (status) { -- -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html