FreeBSD's PCI interrupt routing code attempts to provide a machine independent framework that machine dependent code can hook into where necessary. First, FreeBSD uses cookie values defined by machine dependent code for SYS_RES_IRQ resources. This provides a way to handle interrupts in machine independent code and interfaces. Second, when the PCI bus needs to route an interrupt it passes the request up the device tree until it reaches a level where the request can be handled.
All interrupt resources in FreeBSD drivers are managed as SYS_RES_IRQ resources. When a driver wants to use an interrupt, it allocates a SYS_RES_IRQ resource in much the same way it allocates memory or I/O space. The driver can then attach an interrupt handler to that resource. When a PCI device attempts to allocate a INTx interrupt, the PCI bus first routes it to an IRQ value that is used to create the SYS_RES_IRQ resource. It does this by asking its parent device, which is either a Host-PCI or PCI-PCI bridge, to look up the IRQ for the given PCI interrupt. The different interrupt routing algorithms are then implemented in different drivers for Host-PCI and PCI-PCI bridge drivers.
The simplest PCI bridge driver is the PCI-PCI bridge driver. This driver's interrupt routing routine implements the swizzle defined in Section 5.1 by calculating the corresponding slot and pin on the upstream side of the bridge and passing the request up to the PCI bridge driver for the upstream PCI bus. Thus, routing requests for interrupts on busses that are not part of the main chassis will bubble up through the device tree until they hit a bridge for a PCI bus that is part of the main chassis.
Interrupt routing for PCI busses that are part of the main chassis is handled by machine dependent PCI bridge drivers. For example, if ACPI is enabled, then ACPI will probe and attach to all the PCI bridges in the ACPI namespace. When an interrupt routing request reaches a PCI bridge with an ACPI driver, it will use the _PRT for the corresponding PCI bus to determine the GSI for the PCI interrupt. It then maps the GSI to a SYS_RES_IRQ cookie value which it returns. Thus, the machine dependent code is responsible for mapping platform-specific interrupts to SYS_RES_IRQ cookies in the PCI bridge drivers. Then in the top-level root, or nexus, devices in the device tree, the machine dependent code is responsible for mapping the SYS_RES_IRQ resources back to the platform-specific interrupts.
FreeBSD does allow the user to override the IRQ for any given PCI interrupt via a tunable. The format for this tunable is hw.pcibus.slot.INTpin.irq where bus is the PCI bus number, slot is the PCI slot number, and pin is the intpin (A, B, C, or D). The value of the tunable is the IRQ to use for the specified PCI interrupt. This tunable should only be used as a last resort when there aren't more specific tunables (such as the PCI link tunables) available. One instance in which this tunable is useful is correcting hard-wired routing to I/O APIC intpins due to a broken MP Table or _PRT entry. For example, to route the PCI interrupt for bus 0, slot 16, INTA# to IRQ 24, set the loader tunable hw.pci0.16.INTA.irq=24.
For the x86 platforms, FreeBSD models the mapping of IRQ values to platform interrupts on the Global System Interrupts approach from ACPI. In fact, when using ACPI FreeBSD uses the GSI values directly as IRQs. FreeBSD also always maps IRQ values 0 through 15 to the sixteen ISA IRQs. The only remaining case is when using the MP Table to enumerate APICs and route interrupts. For this case, the MP Table code simulates the GSI approach by assigning suitable base IRQ values to each I/O APIC similar to th base GSI values used by ACPI. The MP Table code calculates the base IRQs by adding the number of input pins on each I/O APIC to the base IRQ of the current I/O APIC to determine the base IRQ of the next I/O APIC. Thus, if you have a system with three I/O APICs where the first two I/O APICs have 24 pins and the third I/O APIC has 16 pins, the first I/O APIC would be assigned IRQs 0-23, the second I/O APIC would be assigned IRQs 24-47, and the last I/O APIC would be assigned IRQs 48-63.
The x86 platforms use a global array indexed by the IRQ value to map the IRQs to platform interrupts. Each entry in the array is a pointer to an interrupt source object. Interrupt source objects consist of a struct intsrc which contains a pointer to a group of function pointers in a struct pic. One can think of struct intsrc and struct pic as abstract base classes. Each interrupt controller driver provides its own extended versions of struct pic and struct intsrc. The extended versions contain the base structure as the first member and add driver-specific data after that. For example, the I/O APIC code defines a struct ioapic which extends struct pic. Each instance of struct ioapic contains functions for managing I/O APIC input pins in its method table. It also defines a struct ioapic_intsrc which extends struct intsrc to add I/O APIC-specific data such as which I/O APIC input pin an interrupt source represents. The interrupt controller drivers determine the IRQ values for each interrupt source object. Thus, they must ensure the IRQ properly matches up with the IRQ value used for any PCI interrupts routed to that interrupt source.
Once the operating system has mapped a PCI interrupt to an interrupt source, the only remaining step for x86 platforms is mapping the interrupt source to an IDT vector. IDT vectors range from 0 to 255, and IDT vectors 0-31 are reserved for CPU faults and exceptions and NMIs. In addition, FreeBSD uses vectors 240-255 for IPIs, vector 239 for the local APIC timer interrupt, and vector 128 for system calls. That leaves vectors 32-127 and 129-238 for device interrupts.
The 8259As each require 8 contiguous IDT vectors. They each can also interrupt the CPU even when all input pins are masked if a spurious interrupt occurs. Thus, vectors 32-47 are reserved for the 8259As, even when APICs are used instead of the 8259As.
The rest of the device interrupts are allocated on an as-needed basis to active interrupt sources. For example, I/O APIC input pins allocate an IDT vector the first time an interrupt handler is registered. Most I/O APIC input pins are never used, so this strategy avoids reserving IDT vectors for interrupt sources that will never trigger.