The diff below improves MSI support for HyperTransport-based systems. In practice that means all systems with 64-bit AMD CPUs.
The HyperTransport bus itself does not support MSI. Devices on the HyperTransport bus may have support to map MSI messages into HyperTransport interrupt messages. This is indicated by a "MSI remapping" capability. This diff looks for those capabilities and if they're present and indicate that remapping is enabled, sets PCI_FLAGS_MSI_ENABLED for the device. If the device is a bridge, this will mean that PCI_FLAGS_MSI_ENABLES will be passed to devices behind that bridge as well, which is exactly what we want. On some machines the MSI remapping capabilities are not automatically enabled by the BIOS. My diff leaves those disabled. We probably should consider enabling them ourselves (which is what Linux seems to do), but I'd like to leave that until later. This diff will effectively enable MSI for many systems with AMD CPUs, without an easy way to turn it off. I think that is ok, since the systems where this happens clearly indicate that they support MSI. Nevertheless I'm working on a diff that gives us a global on-off switch since that is needed when ACPI indicates that MSI is not supposed to be used. Tested by dlg@ on his R815. ok? Index: pci.c =================================================================== RCS file: /cvs/src/sys/dev/pci/pci.c,v retrieving revision 1.91 diff -u -p -r1.91 pci.c --- pci.c 19 May 2011 20:14:55 -0000 1.91 +++ pci.c 29 May 2011 17:28:23 -0000 @@ -370,8 +370,10 @@ pci_probe_device(struct pci_softc *sc, p struct pci_attach_args pa; struct pci_dev *pd; struct device *dev; - pcireg_t id, class, intr, bhlcr; - int ret = 0, pin, bus, device, function; + pcireg_t id, class, intr, bhlcr, cap; + int pin, bus, device, function; + int off, ret = 0; + uint64_t addr; pci_decompose_tag(pc, tag, &bus, &device, &function); @@ -437,6 +439,29 @@ pci_probe_device(struct pci_softc *sc, p } pa.pa_intrline = PCI_INTERRUPT_LINE(intr); + if (pci_get_ht_capability(pc, tag, PCI_HT_CAP_MSI, &off, &cap)) { + /* + * XXX Should we enable MSI mapping ourselves on + * systems that have it disabled? + */ + if (cap & PCI_HT_MSI_ENABLED) { + if ((cap & PCI_HT_MSI_FIXED) == 0) { + addr = pci_conf_read(pc, tag, + off + PCI_HT_MSI_ADDR); + addr |= (uint64_t)pci_conf_read(pc, tag, + off + PCI_HT_MSI_ADDR_HI32) << 32; + } else + addr = PCI_HT_MSI_FIXED_ADDR; + + /* + * XXX This will fail to enable MSI on systems + * that don't use the canonical address. + */ + if (addr == PCI_HT_MSI_FIXED_ADDR) + pa.pa_flags |= PCI_FLAGS_MSI_ENABLED; + } + } + if (match != NULL) { ret = (*match)(&pa); if (ret != 0 && pap != NULL) @@ -545,6 +570,35 @@ pci_get_capability(pci_chipset_tag_t pc, #endif reg = pci_conf_read(pc, tag, ofs); if (PCI_CAPLIST_CAP(reg) == capid) { + if (offset) + *offset = ofs; + if (value) + *value = reg; + return (1); + } + ofs = PCI_CAPLIST_NEXT(reg); + } + + return (0); +} + +int +pci_get_ht_capability(pci_chipset_tag_t pc, pcitag_t tag, int capid, + int *offset, pcireg_t *value) +{ + pcireg_t reg; + unsigned int ofs; + + if (pci_get_capability(pc, tag, PCI_CAP_HT, &ofs, NULL) == 0) + return (0); + + while (ofs != 0) { +#ifdef DIAGNOSTIC + if ((ofs & 3) || (ofs < 0x40)) + panic("pci_get_ht_capability"); +#endif + reg = pci_conf_read(pc, tag, ofs); + if (PCI_HT_CAP(reg) == capid) { if (offset) *offset = ofs; if (value) Index: pcireg.h =================================================================== RCS file: /cvs/src/sys/dev/pci/pcireg.h,v retrieving revision 1.40 diff -u -p -r1.40 pcireg.h --- pcireg.h 14 May 2011 09:57:56 -0000 1.40 +++ pcireg.h 29 May 2011 17:28:23 -0000 @@ -479,7 +479,7 @@ typedef u_int8_t pci_revision_t; #define PCI_CAP_MSI 0x05 #define PCI_CAP_CPCI_HOTSWAP 0x06 #define PCI_CAP_PCIX 0x07 -#define PCI_CAP_LDT 0x08 +#define PCI_CAP_HT 0x08 #define PCI_CAP_VENDSPEC 0x09 #define PCI_CAP_DEBUGPORT 0x0a #define PCI_CAP_CPCI_RSRCCTL 0x0b @@ -521,6 +521,25 @@ typedef u_int8_t pci_revision_t; #define PCI_PMCSR_STATE_D1 0x01 #define PCI_PMCSR_STATE_D2 0x02 #define PCI_PMCSR_STATE_D3 0x03 + +/* + * HyperTransport; access via capability pointer. + */ +#define PCI_HT_CAP(cr) ((((cr) >> 27) < 0x08) ? \ + (((cr) >> 27) & 0x1c) : (((cr) >> 27) & 0x1f)) + +#define PCI_HT_CAP_SLAVE 0x00 +#define PCI_HT_CAP_HOST 0x04 +#define PCI_HT_CAP_INTERRUPT 0x10 +#define PCI_HT_CAP_MSI 0x15 + +#define PCI_HT_MSI_ENABLED 0x00010000 +#define PCI_HT_MSI_FIXED 0x00020000 + +#define PCI_HT_MSI_FIXED_ADDR 0xffe00000UL + +#define PCI_HT_MSI_ADDR 0x04 +#define PCI_HT_MSI_ADDR_HI32 0x08 /* * PCI Express; access via capability pointer. Index: pcivar.h =================================================================== RCS file: /cvs/src/sys/dev/pci/pcivar.h,v retrieving revision 1.65 diff -u -p -r1.65 pcivar.h --- pcivar.h 21 May 2011 10:34:53 -0000 1.65 +++ pcivar.h 29 May 2011 17:28:23 -0000 @@ -231,8 +231,10 @@ int pci_io_find(pci_chipset_tag_t, pcita int pci_mem_find(pci_chipset_tag_t, pcitag_t, int, bus_addr_t *, bus_size_t *, int *); -int pci_get_capability(pci_chipset_tag_t, pcitag_t, int, - int *, pcireg_t *); +int pci_get_capability(pci_chipset_tag_t, pcitag_t, int, + int *, pcireg_t *); +int pci_get_ht_capability(pci_chipset_tag_t, pcitag_t, int, + int *, pcireg_t *); struct pci_matchid { pci_vendor_id_t pm_vid;