MSI API Introduction This page describes the FreeBSD device driver API for message-signalled interrupts (MSI) on PCI-Express and PCI-X busses. It is assumed the reader is familiar with the FreeBSD API for device interrupts. The MSI API is a slightly extended version of that interface. - The SYS_RES_MSI Bus Resource If a PCI device advertises MSI capability in it's configuration header and the parent PCI bridge supports MSI, the o/s will attach a SYS_RES_MSI resource to the device_t structure. The presence/absence of this resource can be used to determine if MSI support exists. Drivers that support MSI should test for this resource and fallback to using the SYS_RES_IRQ API if it isn't there. if (bus_get_resource(dev, SYS_RES_MSI, 0, NULL, NULL) != 0) { /* * No MSI, fall back to the legacy IRQ */ } The maximum number of MSI interrupt vectors supported by the device can be determined supplying a non-NULL countp parameter e.g. u_long count; if (bus_get_resource(dev, SYS_RES_MSI, 0, NULL, &count) == 0) { /* * 'count' now contains the max number of MSI vectors */ } - Allocating the Resource The MSI resource should be allocated by the driver using bus_alloc_resource. The start and end parameters should be set to 0ul and ~0ul respectively. The count parameter should be set to the desired number of MSI interrupt vectors. If the count isn't known, a value of ~0ul should be used to indicate that there is no limit. If the count is set to larger than the number available, the routine will return NULL. If the count is set to less than the number available, the number of interrupt vectors will be set to that requested. For this case, the driver must make sure that interrupts are only generated for the number of vectors requested. The flags parameter is generally set to RF_ACTIVE unless the driver has a special requirement to defer activating the resource at a later time with bus_activate_resource. Note that setting the RF_SHAREABLE will result in an error since MSI interrupts cannot be shared. struct resource *msires; int count; int msiid; msiid = 0; count = ~0ul; msires = bus_alloc_resource(dev, SYS_RES_MSI, &msiid, 0ul, ~0ul, count, RF_ACTIVE); if (msires == NULL) { /* * Could not allocate MSI resource */ } If only a single MSI interrupt vector is required, the bus_alloc_resource_any routine can be used, since it sets the count parameter to 1. The return value from the resource allocation call is an opaque struct resource pointer, that in turn points to an array of struct resource pointers. The returned pointer indicates the number of interrupt resources present in the array, with the array elements containing the actual system IRQ values. This slightly complicated arrangement is required because the resource structure is opaque, and because calls to activate an interrupt require a unique resource structure. The array can be obtained from the returned struct using the rman_get_msivecs call. struct resource **msivecs; ... msivecs = rman_get_msivec(msires); - Determining Vectors While the device itself should have no need to know the allocated vector numbers, it is often useful for debugging purposes to access them. The vectors are available as the resource start and end values in the returned resource. printf("%d MSI vectors, start %ld, end %ld\n", rman_get_size(msires), rman_get_start(msires), rman_get_start(msires) + rman_get_size(msires) - 1); Each individual interrupt resource in the msivecs array also has it's value, but with a size of 1. These can be accessed in a similar way: for (i = 0; i < rman_get_size(msires); i++ { printf("MSI irq %d, value %d\n", i, rman_get_start(msivecs[i])); } - Activating Interrupts Once the resource has been successfully allocated, the interrupt vectors associated with the resource can be activated. Code for this should loop through the resource array. The flags parameter to bus_setup_intr will ignore INTR_EXCL since MSI interrupts are by definition exclusive. An example of setting up interrupts, assuming the returned resource value from the above code fragment, and an external array of interrupt function pointers/args/void cookies: int i, flags; extern struct { driver_intr_t func; void *arg; void *cookie; } xintr; flags = INTR_FAST | INTR_MPSAFE; for (i = 0; i <= rman_get_size(msires); i++) { if (bus_setup_intr(dev, msivecs[i], flags, xintr[i].func, xintr[i].arg, &xintr[i].cookie) != 0) goto err; } ... err: - Freeing the Resource The MSI resource can be de-activated and freed in a single call. This also implicitly tears down all interrupts associated with the resource, simplifying error handling in drivers. err: bus_release_resource(dev, SYS_RES_MSI, msiid, msires);