diff options
author | jhb <jhb@FreeBSD.org> | 2007-01-22 21:48:44 +0000 |
---|---|---|
committer | jhb <jhb@FreeBSD.org> | 2007-01-22 21:48:44 +0000 |
commit | 3624354c54eb965482e4bb6a2769c0feb7248692 (patch) | |
tree | e54f3042911f50266b958edf09a9fe2525a0987a /sys/dev/pci | |
parent | 4a22f82e6c50a7bc72683c16a7b8ea6a6d5624e4 (diff) | |
download | FreeBSD-src-3624354c54eb965482e4bb6a2769c0feb7248692.zip FreeBSD-src-3624354c54eb965482e4bb6a2769c0feb7248692.tar.gz |
Expand the MSI/MSI-X API to address some deficiencies in the MSI-X support.
- First off, device drivers really do need to know if they are allocating
MSI or MSI-X messages. MSI requires allocating powerof2() messages for
example where MSI-X does not. To address this, split out the MSI-X
support from pci_msi_count() and pci_alloc_msi() into new driver-visible
functions pci_msix_count() and pci_alloc_msix(). As a result,
pci_msi_count() now just returns a count of the max supported MSI
messages for the device, and pci_alloc_msi() only tries to allocate MSI
messages. To get a count of the max supported MSI-X messages, use
pci_msix_count(). To allocate MSI-X messages, use pci_alloc_msix().
pci_release_msi() still handles both MSI and MSI-X messages, however.
As a result of this change, drivers using the existing API will only
use MSI messages and will no longer try to use MSI-X messages.
- Because MSI-X allows for each message to have its own data and address
values (and thus does not require all of the messages to have their
MD vectors allocated as a group), some devices allow for "sparse" use
of MSI-X message slots. For example, if a device supports 8 messages
but the OS is only able to allocate 2 messages, the device may make the
best use of 2 IRQs if it enables the messages at slots 1 and 4 rather
than default of using the first N slots (or indicies) at 1 and 2. To
support this, add a new pci_remap_msix() function that a driver may call
after a successful pci_alloc_msix() (but before allocating any of the
SYS_RES_IRQ resources) to allow the allocated IRQ resources to be
assigned to different message indices. For example, from the earlier
example, after pci_alloc_msix() returned a value of 2, the driver would
call pci_remap_msix() passing in array of integers { 1, 4 } as the
new message indices to use. The rid's for the SYS_RES_IRQ resources
will always match the message indices. Thus, after the call to
pci_remap_msix() the driver would be able to access the first message
in slot 1 at SYS_RES_IRQ rid 1, and the second message at slot 4 at
SYS_RES_IRQ rid 4. Note that the message slots/indices are 1-based
rather than 0-based so that they will always correspond to the rid
values (SYS_RES_IRQ rid 0 is reserved for the legacy INTx interrupt).
To support this API, a new PCIB_REMAP_MSIX() method was added to the
pcib interface to change the message index for a single IRQ.
Tested by: scottl
Diffstat (limited to 'sys/dev/pci')
-rw-r--r-- | sys/dev/pci/pci.c | 170 | ||||
-rw-r--r-- | sys/dev/pci/pci_if.m | 17 | ||||
-rw-r--r-- | sys/dev/pci/pci_pci.c | 11 | ||||
-rw-r--r-- | sys/dev/pci/pci_private.h | 4 | ||||
-rw-r--r-- | sys/dev/pci/pcib_if.m | 10 | ||||
-rw-r--r-- | sys/dev/pci/pcib_private.h | 1 | ||||
-rw-r--r-- | sys/dev/pci/pcivar.h | 18 |
7 files changed, 204 insertions, 27 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c index dc930c1..7ef07e2 100644 --- a/sys/dev/pci/pci.c +++ b/sys/dev/pci/pci.c @@ -101,6 +101,7 @@ static void pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t data); #endif static void pci_read_vpd(device_t pcib, pcicfgregs *cfg); +static int pci_msi_blacklisted(void); static device_method_t pci_methods[] = { /* Device interface */ @@ -145,8 +146,11 @@ static device_method_t pci_methods[] = { DEVMETHOD(pci_assign_interrupt, pci_assign_interrupt_method), DEVMETHOD(pci_find_extcap, pci_find_extcap_method), DEVMETHOD(pci_alloc_msi, pci_alloc_msi_method), + DEVMETHOD(pci_alloc_msix, pci_alloc_msix_method), + DEVMETHOD(pci_remap_msix, pci_remap_msix_method), DEVMETHOD(pci_release_msi, pci_release_msi_method), DEVMETHOD(pci_msi_count, pci_msi_count_method), + DEVMETHOD(pci_msix_count, pci_msix_count_method), { 0, 0 } }; @@ -1024,14 +1028,36 @@ pci_pending_msix(device_t dev, u_int index) return (bus_read_4(cfg->msix.msix_pba_res, offset) & bit); } -static int -pci_alloc_msix(device_t dev, device_t child, int *count) +/* + * Attempt to allocate *count MSI-X messages. The actual number allocated is + * returned in *count. After this function returns, each message will be + * available to the driver as SYS_RES_IRQ resources starting at rid 1. + */ +int +pci_alloc_msix_method(device_t dev, device_t child, int *count) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; struct resource_list_entry *rle; int actual, error, i, irq, max; + /* Don't let count == 0 get us into trouble. */ + if (*count == 0) + return (EINVAL); + + /* If rid 0 is allocated, then fail. */ + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0); + if (rle != NULL && rle->res != NULL) + return (ENXIO); + + /* Already have allocated messages? */ + if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) + return (ENXIO); + + /* If MSI is blacklisted for this system, fail. */ + if (pci_msi_blacklisted()) + return (ENXIO); + /* MSI-X capability present? */ if (cfg->msix.msix_location == 0 || !pci_do_msix) return (ENODEV); @@ -1052,10 +1078,6 @@ pci_alloc_msix(device_t dev, device_t child, int *count) } cfg->msix.msix_pba_res = rle->res; - /* Already have allocated messages? */ - if (cfg->msix.msix_alloc != 0) - return (ENXIO); - if (bootverbose) device_printf(child, "attempting to allocate %d MSI-X vectors (%d supported)\n", @@ -1132,24 +1154,105 @@ pci_alloc_msix(device_t dev, device_t child, int *count) return (0); } +/* + * By default, pci_alloc_msix() will assign the allocated IRQ resources to + * the first N messages in the MSI-X table. However, device drivers may + * want to use different layouts in the case that they do not allocate a + * full table. This method allows the driver to specify what layout it + * wants. It must be called after a successful pci_alloc_msix() but + * before any of the associated SYS_RES_IRQ resources are allocated via + * bus_alloc_resource(). The 'indices' array contains N (where N equals + * the 'count' returned from pci_alloc_msix()) message indices. The + * indices are 1-based (meaning the first message is at index 1). On + * successful return, each of the messages in the 'indices' array will + * have an associated SYS_RES_IRQ whose rid is equal to the index. Thus, + * if indices contains { 2, 4 }, then upon successful return, the 'child' + * device will have two SYS_RES_IRQ resources available at rids 2 and 4. + */ +int +pci_remap_msix_method(device_t dev, device_t child, u_int *indices) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + struct resource_list_entry *rle; + int count, error, i, j, *irqs; + + /* Sanity check the indices. */ + for (i = 0; i < cfg->msix.msix_alloc; i++) + if (indices[i] == 0 || indices[i] > cfg->msix.msix_msgnum) + return (EINVAL); + + /* Check for duplicates. */ + for (i = 0; i < cfg->msix.msix_alloc; i++) + for (j = i + 1; j < cfg->msix.msix_alloc; j++) + if (indices[i] == indices[j]) + return (EINVAL); + + /* Make sure none of the resources are allocated. */ + for (i = 1, count = 0; count < cfg->msix.msix_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i); + if (rle == NULL) + continue; + if (rle->res != NULL) + return (EBUSY); + count++; + } + + /* Save the IRQ values and free the existing resources. */ + irqs = malloc(sizeof(int) * cfg->msix.msix_alloc, M_TEMP, M_WAITOK); + for (i = 1, count = 0; count < cfg->msix.msix_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i); + if (rle == NULL) + continue; + irqs[count] = rle->start; + resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i); + count++; + } + + /* Map the IRQ values to the new message indices and rids. */ + for (i = 0; i < cfg->msix.msix_alloc; i++) { + resource_list_add(&dinfo->resources, SYS_RES_IRQ, indices[i], + irqs[i], irqs[i], 1); + error = PCIB_REMAP_MSIX(device_get_parent(dev), child, + indices[i], irqs[i]); + KASSERT(error == 0, ("Failed to remap MSI-X message")); + } + if (bootverbose) { + if (cfg->msix.msix_alloc == 1) + device_printf(child, + "Remapped MSI-X IRQ to index %d\n", indices[0]); + else { + device_printf(child, "Remapped MSI-X IRQs to indices"); + for (i = 0; i < cfg->msix.msix_alloc - 1; i++) + printf(" %d,", indices[i]); + printf(" %d\n", indices[cfg->msix.msix_alloc - 1]); + } + } + free(irqs, M_TEMP); + + return (0); +} + static int pci_release_msix(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; struct resource_list_entry *rle; - int i; + int count, i; /* Do we have any messages to release? */ if (cfg->msix.msix_alloc == 0) return (ENODEV); /* Make sure none of the resources are allocated. */ - for (i = 0; i < cfg->msix.msix_alloc; i++) { - rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); - KASSERT(rle != NULL, ("missing MSI resource")); + for (i = 1, count = 0; count < cfg->msix.msix_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i); + if (rle == NULL) + continue; if (rle->res != NULL) return (EBUSY); + count++; } /* Update control register with to disable MSI-X. */ @@ -1158,11 +1261,14 @@ pci_release_msix(device_t dev, device_t child) cfg->msix.msix_ctrl, 2); /* Release the messages. */ - for (i = 0; i < cfg->msix.msix_alloc; i++) { - rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); + for (i = 1, count = 0; count < cfg->msix.msix_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i); + if (rle == NULL) + continue; PCIB_RELEASE_MSIX(device_get_parent(dev), child, rle->start); - resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); + resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i); + count++; } /* Update alloc count. */ @@ -1171,6 +1277,23 @@ pci_release_msix(device_t dev, device_t child) } /* + * Return the max supported MSI-X messages this device supports. + * Basically, assuming the MD code can alloc messages, this function + * should return the maximum value that pci_alloc_msix() can return. + * Thus, it is subject to the tunables, etc. + */ +int +pci_msix_count_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + + if (pci_do_msix && cfg->msix.msix_location != 0) + return (cfg->msix.msix_msgnum); + return (0); +} + +/* * Support for MSI message signalled interrupts. */ void @@ -1294,23 +1417,18 @@ pci_alloc_msi_method(device_t dev, device_t child, int *count) if (rle != NULL && rle->res != NULL) return (ENXIO); + /* Already have allocated messages? */ + if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) + 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) - return (error); - /* MSI capability present? */ if (cfg->msi.msi_location == 0 || !pci_do_msi) return (ENODEV); - /* Already have allocated messages? */ - if (cfg->msi.msi_alloc != 0) - return (ENXIO); - if (bootverbose) device_printf(child, "attempting to allocate %d MSI vectors (%d supported)\n", @@ -1444,10 +1562,10 @@ pci_release_msi_method(device_t dev, device_t child) } /* - * Return the max supported MSI or MSI-X messages this device supports. + * Return the max supported MSI messages this device supports. * Basically, assuming the MD code can alloc messages, this function - * should return the maximum value that pci_alloc_msi() can return. Thus, - * it is subject to the tunables, etc. + * should return the maximum value that pci_alloc_msi() can return. + * Thus, it is subject to the tunables, etc. */ int pci_msi_count_method(device_t dev, device_t child) @@ -1455,8 +1573,6 @@ pci_msi_count_method(device_t dev, device_t child) struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; - if (pci_do_msix && cfg->msix.msix_location != 0) - return (cfg->msix.msix_msgnum); if (pci_do_msi && cfg->msi.msi_location != 0) return (cfg->msi.msi_msgnum); return (0); diff --git a/sys/dev/pci/pci_if.m b/sys/dev/pci/pci_if.m index ba60415..266ec34 100644 --- a/sys/dev/pci/pci_if.m +++ b/sys/dev/pci/pci_if.m @@ -118,6 +118,18 @@ METHOD int alloc_msi { int *count; }; +METHOD int alloc_msix { + device_t dev; + device_t child; + int *count; +}; + +METHOD int remap_msix { + device_t dev; + device_t child; + u_int *indices; +}; + METHOD int release_msi { device_t dev; device_t child; @@ -127,3 +139,8 @@ METHOD int msi_count { device_t dev; device_t child; } DEFAULT null_msi_count; + +METHOD int msix_count { + device_t dev; + device_t child; +} DEFAULT null_msi_count; diff --git a/sys/dev/pci/pci_pci.c b/sys/dev/pci/pci_pci.c index 8accc42..6e94798 100644 --- a/sys/dev/pci/pci_pci.c +++ b/sys/dev/pci/pci_pci.c @@ -82,6 +82,7 @@ static device_method_t pcib_methods[] = { DEVMETHOD(pcib_alloc_msi, pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, pcib_alloc_msix), + DEVMETHOD(pcib_remap_msix, pcib_remap_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), { 0, 0 } @@ -583,6 +584,16 @@ pcib_alloc_msix(device_t pcib, device_t dev, int index, int *irq) return (PCIB_ALLOC_MSIX(device_get_parent(bus), dev, index, irq)); } +/* Pass request to remap an MSI-X message up to the parent bridge. */ +int +pcib_remap_msix(device_t pcib, device_t dev, int index, int irq) +{ + device_t bus; + + bus = device_get_parent(pcib); + return (PCIB_REMAP_MSIX(device_get_parent(bus), dev, index, irq)); +} + /* Pass request to release an MSI-X message up to the parent bridge. */ int pcib_release_msix(device_t pcib, device_t dev, int irq) diff --git a/sys/dev/pci/pci_private.h b/sys/dev/pci/pci_private.h index 9aea42d..32be4aa 100644 --- a/sys/dev/pci/pci_private.h +++ b/sys/dev/pci/pci_private.h @@ -67,8 +67,12 @@ int pci_disable_io_method(device_t dev, device_t child, int space); int pci_find_extcap_method(device_t dev, device_t child, int capability, int *capreg); int pci_alloc_msi_method(device_t dev, device_t child, int *count); +int pci_alloc_msix_method(device_t dev, device_t child, int *count); +int pci_remap_msix_method(device_t dev, device_t child, + u_int *indices); int pci_release_msi_method(device_t dev, device_t child); int pci_msi_count_method(device_t dev, device_t child); +int pci_msix_count_method(device_t dev, device_t child); struct resource *pci_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags); diff --git a/sys/dev/pci/pcib_if.m b/sys/dev/pci/pcib_if.m index 0e12c00..18755fa 100644 --- a/sys/dev/pci/pcib_if.m +++ b/sys/dev/pci/pcib_if.m @@ -126,6 +126,16 @@ METHOD int alloc_msix { }; # +# Remap a single MSI-X message to a different index. +# +METHOD int remap_msix { + device_t pcib; + device_t dev; + int index; + int irq; +}; + +# # Release a single MSI-X message mapped onto 'irq'. # METHOD int release_msix { diff --git a/sys/dev/pci/pcib_private.h b/sys/dev/pci/pcib_private.h index 9575df2..a571578 100644 --- a/sys/dev/pci/pcib_private.h +++ b/sys/dev/pci/pcib_private.h @@ -78,6 +78,7 @@ int pcib_route_interrupt(device_t pcib, device_t dev, int pin); int pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs); int pcib_release_msi(device_t pcib, device_t dev, int count, int *irqs); int pcib_alloc_msix(device_t pcib, device_t dev, int index, int *irq); +int pcib_remap_msix(device_t pcib, device_t dev, int index, int irq); int pcib_release_msix(device_t pcib, device_t dev, int irq); #endif diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h index eca13ef..a075745 100644 --- a/sys/dev/pci/pcivar.h +++ b/sys/dev/pci/pcivar.h @@ -396,6 +396,18 @@ pci_alloc_msi(device_t dev, int *count) } static __inline int +pci_alloc_msix(device_t dev, int *count) +{ + return (PCI_ALLOC_MSIX(device_get_parent(dev), dev, count)); +} + +static __inline int +pci_remap_msix(device_t dev, u_int *indices) +{ + return (PCI_REMAP_MSIX(device_get_parent(dev), dev, indices)); +} + +static __inline int pci_release_msi(device_t dev) { return (PCI_RELEASE_MSI(device_get_parent(dev), dev)); @@ -407,6 +419,12 @@ pci_msi_count(device_t dev) return (PCI_MSI_COUNT(device_get_parent(dev), dev)); } +static __inline int +pci_msix_count(device_t dev) +{ + return (PCI_MSIX_COUNT(device_get_parent(dev), dev)); +} + device_t pci_find_bsf(uint8_t, uint8_t, uint8_t); device_t pci_find_device(uint16_t, uint16_t); |