diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c index c6d16e37dc4..44247b4578a 100644 --- a/sys/dev/pci/pci.c +++ b/sys/dev/pci/pci.c @@ -165,6 +165,7 @@ struct pci_quirk { uint32_t devid; /* Vendor/device of the card */ int type; #define PCI_QUIRK_MAP_REG 1 /* PCI map register in weird place */ +#define PCI_QUIRK_DISABLE_MSI 2 /* MSI/MSI-X doesn't work */ int arg1; int arg2; }; @@ -224,6 +225,11 @@ TUNABLE_INT("hw.pci.enable_msix", &pci_do_msix); SYSCTL_INT(_hw_pci, OID_AUTO, enable_msix, CTLFLAG_RW, &pci_do_msix, 1, "Enable support for MSI-X interrupts"); +static int pci_honor_msi_blacklist = 1; +TUNABLE_INT("hw.pci.honor_msi_blacklist", &pci_honor_msi_blacklist); +SYSCTL_INT(_hw_pci, OID_AUTO, honor_msi_blacklist, CTLFLAG_RD, + &pci_honor_msi_blacklist, 1, "Honor chipset blacklist for MSI"); + /* Find a device_t by bus/slot/function */ device_t @@ -1194,6 +1200,47 @@ pci_resume_msi(device_t dev) cfg->msi.msi_ctrl, 2); } +/* + * Returns true if the specified device is blacklisted because MSI + * doesn't work. + */ +int +pci_msi_device_blacklisted(device_t dev) +{ + struct pci_quirk *q; + + if (!pci_honor_msi_blacklist) + return (0); + + for (q = &pci_quirks[0]; q->devid; q++) { + if (q->devid == pci_get_devid(dev) && + q->type == PCI_QUIRK_DISABLE_MSI) + return (1); + } + return (0); +} + +/* + * Determine if MSI is blacklisted globally on this sytem. Currently, + * we just check for blacklisted chipsets as represented by the + * host-PCI bridge at device 0:0:0. In the future, it may become + * necessary to check other system attributes, such as the kenv values + * that give the motherboard manufacturer and model number. + */ +static int +pci_msi_blacklisted(void) +{ + device_t dev; + + if (!pci_honor_msi_blacklist) + return (0); + + dev = pci_find_bsf(0, 0, 0); + if (dev != NULL) + return (pci_msi_device_blacklisted(dev)); + return (0); +} + /* * Attempt to allocate *count MSI messages. The actual number allocated is * returned in *count. After this function returns, each message will be @@ -1217,6 +1264,10 @@ pci_alloc_msi_method(device_t dev, device_t child, int *count) if (rle != NULL && rle->res != NULL) return (ENXIO); + /* If MSI is blacklisted for this system, fail. */ + if (pci_msi_blacklisted()) + return (ENXIO); + /* Try MSI-X first. */ error = pci_alloc_msix(dev, child, count); if (error != ENODEV) diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h index 846f99af9b1..eca13efaeb1 100644 --- a/sys/dev/pci/pcivar.h +++ b/sys/dev/pci/pcivar.h @@ -417,6 +417,7 @@ void pci_enable_msix(device_t dev, u_int index, uint64_t address, void pci_mask_msix(device_t dev, u_int index); int pci_pending_msix(device_t dev, u_int index); void pci_unmask_msix(device_t dev, u_int index); +int pci_msi_device_blacklisted(device_t dev); #endif /* _SYS_BUS_H_ */