[RFC] Whitelist chipsets supporting MSI and check Hyper-transport capabilities

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

 



Several chipsets are known to not support MSI. Some support MSI but
disable it by default. Thus, several drivers implement their own way to
detect whether MSI works.

We introduce whitelisting of chipsets that are known to support MSI and
keep the existing backlisting to disable MSI for other chipsets. When it
is unknown whether the root chipset support MSI or not, we disable MSI
by default except if pci=forcemsi was passed.

Whitelisting is done by setting a new PCI_BUS_FLAGS_MSI in the chipset
subordinate bus. pci_enable_msi() thus starts by checking whether the
root chipset of the device has the MSI or NOMSI flag set.

Bus flags inheritance is dropped since it has been reported to be broken.

We currently whitelist all Intel chipsets, nVidia CK804 and ServerWorks
HT2000. Some others are probably missing such as nVidia chipsets for
Intel processors.

We introduces a generic code to check the MSI capability in the
HyperTransport configuration space, and use it in the generic HT MSI
quirk (only used for HT2000 so far) and in the CK804 specific quirk
(needs to check 2 HT MSI mapping).

Finally, we rename PCI_CAP_ID_HT_IRQCONF to PCI_CAP_ID_HT since its
value is the capability and not the irqconf subtype.

Signed-off-by: Brice Goglin <[email protected]>

---

I do not split this patch in multiple pieces since it is still small
and this is just a RFC. I will split it for the actual submission.

The patch adds PCI_DEVICE_ID_NVIDIA_CK804_PCIE to pciids.h and may thus
conflict with pci-nvidia-quirk-to-make-aer-pci-e-extended-capability-visible.patch
which adds it too (it has been pushed in Greg K-H's patchset today).


 Documentation/kernel-parameters.txt |    6 +
 arch/powerpc/sysdev/mpic.c          |    2
 drivers/pci/msi.c                   |   58 ++++++++++-----
 drivers/pci/pci.c                   |    2
 drivers/pci/pci.h                   |    1
 drivers/pci/probe.c                 |    2
 drivers/pci/quirks.c                |   95 +++++++++++++++++++++++++-
 include/linux/pci.h                 |    3
 include/linux/pci_ids.h             |    2
 include/linux/pci_regs.h            |    2
 10 files changed, 147 insertions(+), 26 deletions(-)

Index: linux-mm/drivers/pci/msi.c
===================================================================
--- linux-mm.orig/drivers/pci/msi.c	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/drivers/pci/msi.c	2006-06-13 14:55:36.000000000 -0400
@@ -28,6 +28,7 @@
 static kmem_cache_t* msi_cachep;
 
 static int pci_msi_enable = 1;
+static int pci_msi_force = 0;
 static int last_alloc_vector;
 static int nr_released_vectors;
 static int nr_reserved_vectors = NR_HP_RESERVED_VECTORS;
@@ -902,6 +903,34 @@
 }
 
 /**
+ * pci_msi_enabled - check whether MSI may be enabled on device
+ * @dev: pointer to the pci_dev data structure of MSI device function
+ *
+ * Check parent busses for MSI or NO_MSI flags, or disable except
+ * if forced.
+ **/
+int pci_msi_enabled(struct pci_dev * dev)
+{
+	struct pci_dev *pdev;
+
+	if (!pci_msi_enable || !dev || dev->no_msi)
+ 		return -1;
+
+	/* find root complex for our device */
+	pdev = dev;
+	while (pdev->bus && pdev->bus->self)
+		pdev = pdev->bus->self;
+
+	/* check its bus flags */
+	if (pdev->subordinate->bus_flags & PCI_BUS_FLAGS_MSI)
+		return 0;
+	if (pdev->subordinate->bus_flags & PCI_BUS_FLAGS_NO_MSI)
+		return -1;
+
+	return pci_msi_force ? 0 : -1;
+}
+
+/**
  * pci_enable_msi - configure device's MSI capability structure
  * @dev: pointer to the pci_dev data structure of MSI device function
  *
@@ -913,19 +942,11 @@
  **/
 int pci_enable_msi(struct pci_dev* dev)
 {
-	struct pci_bus *bus;
-	int pos, temp, status = -EINVAL;
+	int pos, temp, status;
 	u16 control;
 
-	if (!pci_msi_enable || !dev)
- 		return status;
-
-	if (dev->no_msi)
-		return status;
-
-	for (bus = dev->bus; bus; bus = bus->parent)
-		if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
-			return -EINVAL;
+	if (pci_msi_enabled(dev) < 0)
+		return -EINVAL;
 
 	temp = dev->irq;
 
@@ -1135,22 +1156,14 @@
  **/
 int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec)
 {
-	struct pci_bus *bus;
 	int status, pos, nr_entries, free_vectors;
 	int i, j, temp;
 	u16 control;
 	unsigned long flags;
 
-	if (!pci_msi_enable || !dev || !entries)
- 		return -EINVAL;
-
-	if (dev->no_msi)
+	if (!entries || pci_msi_enabled(dev) < 0)
 		return -EINVAL;
 
-	for (bus = dev->bus; bus; bus = bus->parent)
-		if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
-			return -EINVAL;
-
 	status = msi_init();
 	if (status < 0)
 		return status;
@@ -1350,6 +1363,11 @@
 	pci_msi_enable = 0;
 }
 
+void pci_force_msi(void)
+{
+	pci_msi_force = 1;
+}
+
 EXPORT_SYMBOL(pci_enable_msi);
 EXPORT_SYMBOL(pci_disable_msi);
 EXPORT_SYMBOL(pci_enable_msix);
Index: linux-mm/drivers/pci/pci.c
===================================================================
--- linux-mm.orig/drivers/pci/pci.c	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/drivers/pci/pci.c	2006-06-13 14:55:36.000000000 -0400
@@ -978,6 +978,8 @@
 		if (*str && (str = pcibios_setup(str)) && *str) {
 			if (!strcmp(str, "nomsi")) {
 				pci_no_msi();
+			} else if (!strcmp(str, "forcemsi")) {
+				pci_force_msi();
 			} else {
 				printk(KERN_ERR "PCI: Unknown option `%s'\n",
 						str);
Index: linux-mm/drivers/pci/pci.h
===================================================================
--- linux-mm.orig/drivers/pci/pci.h	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/drivers/pci/pci.h	2006-06-13 14:55:36.000000000 -0400
@@ -51,6 +51,7 @@
 #ifdef CONFIG_PCI_MSI
 void disable_msi_mode(struct pci_dev *dev, int pos, int type);
 void pci_no_msi(void);
+void pci_force_msi(void);
 #else
 static inline void disable_msi_mode(struct pci_dev *dev, int pos, int type) { }
 static inline void pci_no_msi(void) { }
Index: linux-mm/drivers/pci/quirks.c
===================================================================
--- linux-mm.orig/drivers/pci/quirks.c	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/drivers/pci/quirks.c	2006-06-16 22:26:42.000000000 -0400
@@ -588,7 +588,8 @@
         
 	if (dev->subordinate) {
 		printk(KERN_WARNING "PCI: MSI quirk detected. "
-		       "PCI_BUS_FLAGS_NO_MSI set for subordinate bus.\n");
+		       "PCI_BUS_FLAGS_NO_MSI set for %s subordinate bus.\n",
+		       pci_name(dev));
 		dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
 	}
 
@@ -608,7 +609,9 @@
 static void __init quirk_svw_msi(struct pci_dev *dev)
 {
 	pci_msi_quirk = 1;
-	printk(KERN_WARNING "PCI: MSI quirk detected. pci_msi_quirk set.\n");
+	printk(KERN_WARNING "PCI: MSI quirk detected for %s. "
+	       "pci_msi_quirk set.\n",
+	       pci_name(dev));
 }
 DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_svw_msi );
 #endif /* CONFIG_X86_IO_APIC */
@@ -1556,6 +1559,94 @@
 }
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL,	0x1460,		quirk_p64h2_1k_io);
 
+/* Mark MSI bus flags on chipset that are known to support it */
+static void __devinit quirk_msi_supported(struct pci_dev *dev)
+{
+	if (dev->subordinate) {
+		printk(KERN_WARNING "PCI: MSI quirk detected. "
+		       "PCI_BUS_FLAGS_MSI set for subordinate bus.\n");
+		dev->subordinate->bus_flags |= PCI_BUS_FLAGS_MSI;
+	}
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_msi_supported);
+
+/* Return MSI bus flags depending on MSI HT cap */
+static pci_bus_flags_t __devinit check_msi_ht_cap(struct pci_dev *dev)
+{
+	u8 cap_off;
+	int nbcap = 0;
+	cap_off = PCI_CAPABILITY_LIST - 1;
+
+	/* go through all caps looking for a hypertransport msi mapping */
+	while (pci_read_config_byte(dev, cap_off + 1, &cap_off) == 0 &&
+		nbcap++ <= 256 / 4) {
+		u32 cap_hdr;
+		if (cap_off == 0 || cap_off == 0xff)
+			break;
+		cap_off &= 0xfc;
+		/* msi mapping section according to hypertransport spec */
+		if (pci_read_config_dword(dev, cap_off, &cap_hdr) == 0
+		    && (cap_hdr & 0xff) == PCI_CAP_ID_HT /* hypertransport cap */
+		    && (cap_hdr & 0xf8000000) == 0xa8000000 /* msi mapping */) {
+			printk(KERN_INFO "PCI: Found MSI HT mapping on %s\n",
+			       pci_name(dev));
+			return cap_hdr & 0x10000 /* msi mapping cap enabled */
+			    ? PCI_BUS_FLAGS_MSI : PCI_BUS_FLAGS_NO_MSI;
+		}
+	}
+	return 0;
+}
+
+/* Check the hypertransport MSI mapping to know whether MSI is enabled or not */
+static void __devinit quirk_msi_ht_cap(struct pci_dev *dev)
+{
+	pci_bus_flags_t flags;
+
+	if (!dev->subordinate)
+		return;
+
+	flags = check_msi_ht_cap(dev);
+	if (flags) {
+		printk(KERN_WARNING "PCI: MSI quirk detected. "
+		       "%s set for %s subordinate bus.\n",
+		       flags == PCI_BUS_FLAGS_NO_MSI ?
+			   "PCI_BUS_FLAGS_NO_MSI" : "PCI_BUS_FLAGS_MSI",
+		       pci_name(dev));
+		dev->subordinate->bus_flags |= flags;
+	}
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE, quirk_msi_ht_cap);
+
+/* The nVidia CK804 PCI-E might have 2 hypertransport MSI mapping.
+ * MSI are supported if the MSI cap is enabled on one of them.
+ */
+static void __devinit quirk_nvidia_ck804_msi_ht_cap(struct pci_dev *dev)
+{
+	struct pci_dev *pdev;
+	pci_bus_flags_t flags;
+
+	if (!dev->subordinate)
+		return;
+
+	/* check MSI HT cap on this chipset and the root one.
+	 * a single MSI flag is enough to be sure that MSI are supported.
+	 */
+	pdev = pci_find_slot(dev->bus->number, 0);
+	flags = check_msi_ht_cap(dev) | check_msi_ht_cap(pdev);
+	if (flags & PCI_BUS_FLAGS_MSI)
+		flags = PCI_BUS_FLAGS_MSI;
+
+	if (flags) {
+		printk(KERN_WARNING "PCI: MSI quirk detected. "
+		       "%s set for %s subordinate bus.\n",
+		       flags == PCI_BUS_FLAGS_NO_MSI ?
+			   "PCI_BUS_FLAGS_NO_MSI" : "PCI_BUS_FLAGS_MSI",
+		       pci_name(dev));
+		dev->subordinate->bus_flags |= flags;
+	}
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, quirk_nvidia_ck804_msi_ht_cap);
+
 EXPORT_SYMBOL(pcie_mch_quirk);
 #ifdef CONFIG_HOTPLUG
 EXPORT_SYMBOL(pci_fixup_device);
Index: linux-mm/include/linux/pci.h
===================================================================
--- linux-mm.orig/include/linux/pci.h	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/include/linux/pci.h	2006-06-13 14:55:37.000000000 -0400
@@ -97,6 +97,7 @@
 typedef unsigned short __bitwise pci_bus_flags_t;
 enum pci_bus_flags {
 	PCI_BUS_FLAGS_NO_MSI = (__force pci_bus_flags_t) 1,
+	PCI_BUS_FLAGS_MSI = (__force pci_bus_flags_t) 2,
 };
 
 struct pci_cap_saved_state {
@@ -242,7 +243,7 @@
 	char		name[48];
 
 	unsigned short  bridge_ctl;	/* manage NO_ISA/FBB/et al behaviors */
-	pci_bus_flags_t bus_flags;	/* Inherited by child busses */
+	pci_bus_flags_t bus_flags;
 	struct device		*bridge;
 	struct class_device	class_dev;
 	struct bin_attribute	*legacy_io; /* legacy I/O for this bus */
Index: linux-mm/include/linux/pci_ids.h
===================================================================
--- linux-mm.orig/include/linux/pci_ids.h	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/include/linux/pci_ids.h	2006-06-13 14:59:58.000000000 -0400
@@ -1027,6 +1027,7 @@
 #define PCI_DEVICE_ID_NVIDIA_NVENET_8		0x0056
 #define PCI_DEVICE_ID_NVIDIA_NVENET_9		0x0057
 #define PCI_DEVICE_ID_NVIDIA_CK804_AUDIO	0x0059
+#define PCI_DEVICE_ID_NVIDIA_CK804_PCIE		0x005d
 #define PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS	0x0064
 #define PCI_DEVICE_ID_NVIDIA_NFORCE2_IDE	0x0065
 #define PCI_DEVICE_ID_NVIDIA_NVENET_2		0x0066
@@ -1405,6 +1406,7 @@
 #define PCI_DEVICE_ID_SERVERWORKS_LE	  0x0009
 #define PCI_DEVICE_ID_SERVERWORKS_GCNB_LE 0x0017
 #define PCI_DEVICE_ID_SERVERWORKS_EPB	  0x0103
+#define PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE	0x0132
 #define PCI_DEVICE_ID_SERVERWORKS_OSB4	  0x0200
 #define PCI_DEVICE_ID_SERVERWORKS_CSB5	  0x0201
 #define PCI_DEVICE_ID_SERVERWORKS_CSB6    0x0203
Index: linux-mm/Documentation/kernel-parameters.txt
===================================================================
--- linux-mm.orig/Documentation/kernel-parameters.txt	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/Documentation/kernel-parameters.txt	2006-06-13 14:55:37.000000000 -0400
@@ -1190,6 +1190,12 @@
 		nomsi		[MSI] If the PCI_MSI kernel config parameter is
 				enabled, this kernel boot option can be used to
 				disable the use of MSI interrupts system-wide.
+		forcemsi	[MSI] If the PCI_MSI kernel config parameter is
+				enabled, force MSI to be enabled when its support
+				in parent busses is unknown. By default, MSI
+				are only enabled when parent busses are known
+				to support it. This option does not force MSI
+				when parent busses are known to not support it.
 		nosort		[IA-32] Don't sort PCI devices according to
 				order given by the PCI BIOS. This sorting is
 				done to get a device order compatible with
Index: linux-mm/drivers/pci/probe.c
===================================================================
--- linux-mm.orig/drivers/pci/probe.c	2006-06-12 10:08:25.000000000 -0400
+++ linux-mm/drivers/pci/probe.c	2006-06-13 14:55:37.000000000 -0400
@@ -351,7 +351,7 @@
 	child->parent = parent;
 	child->ops = parent->ops;
 	child->sysdata = parent->sysdata;
-	child->bus_flags = parent->bus_flags;
+	child->bus_flags = 0;
 	child->bridge = get_device(&bridge->dev);
 
 	child->class_dev.class = &pcibus_class;
Index: linux-mm/arch/powerpc/sysdev/mpic.c
===================================================================
--- linux-mm.orig/arch/powerpc/sysdev/mpic.c	2006-06-16 22:20:49.000000000 -0400
+++ linux-mm/arch/powerpc/sysdev/mpic.c	2006-06-16 22:20:57.000000000 -0400
@@ -250,7 +250,7 @@
 	for (pos = readb(devbase + PCI_CAPABILITY_LIST); pos != 0;
 	     pos = readb(devbase + pos + PCI_CAP_LIST_NEXT)) {
 		u8 id = readb(devbase + pos + PCI_CAP_LIST_ID);
-		if (id == PCI_CAP_ID_HT_IRQCONF) {
+		if (id == PCI_CAP_ID_HT) {
 			id = readb(devbase + pos + 3);
 			if (id == 0x80)
 				break;
Index: linux-mm/include/linux/pci_regs.h
===================================================================
--- linux-mm.orig/include/linux/pci_regs.h	2006-06-16 22:21:33.000000000 -0400
+++ linux-mm/include/linux/pci_regs.h	2006-06-16 22:22:00.000000000 -0400
@@ -196,7 +196,7 @@
 #define  PCI_CAP_ID_MSI		0x05	/* Message Signalled Interrupts */
 #define  PCI_CAP_ID_CHSWP	0x06	/* CompactPCI HotSwap */
 #define  PCI_CAP_ID_PCIX	0x07	/* PCI-X */
-#define  PCI_CAP_ID_HT_IRQCONF	0x08	/* HyperTransport IRQ Configuration */
+#define  PCI_CAP_ID_HT		0x08	/* HyperTransport capability */
 #define  PCI_CAP_ID_VNDR	0x09	/* Vendor specific capability */
 #define  PCI_CAP_ID_SHPC 	0x0C	/* PCI Standard Hot-Plug Controller */
 #define  PCI_CAP_ID_EXP 	0x10	/* PCI Express */


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux