[PATCH] pcie/portdrv: fix dealing of MSI-X vectors

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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, &reg16);
+		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, &reg32);
+		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

[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux