Interrupt controller kernel module for Aarch64 GIC architectures
The interrupt controller kernel module transfers logic from startup-provided callouts to the kernel, allowing the kernel to make better interrupt handling decisions. This module allows for less contention on concurrent interrupt handling on multiple cores. This is in part due to the ability of the kernel module to make smarter decisions (e.g., when serializing access to the interrupt controller's resources) than the stock kernel that invokes opaque callouts provided by startup. A prime example is the clock interrupt, which can easily be triggered at the same time on multiple cores.
- All interrupts are disabled (masked) unless explicitly required otherwise.
- Priority, trigger type, and polarity are configured for all interrupts.
- Interrupt IDs are partitioned into correct intrinfo entries (PPI, SPI, LPI, etc.).
Currently, the modules only exist for aarch64 processors with a GICv2 or GICv3. GICv4 is backwards compatible with GICv3 and treated as such.
GICv2
The GICv2 module supports the following interrupts as separate entries:
-
private peripheral interrupts (PPI)
-
shared peripheral interrupts (SPI)
The module depends on a startup that splits the PPIs and SPIs into different intrinfo entries. You can use the latest libstartup to build the startup.
[ module=gicv2 ]GICv3
The GICv3 module supports the following interrupts as separate entries:
-
private peripheral interrupts (PPI)
-
shared peripheral interrupts (SPI)
- extended SPIs
-
locality-specific peripheral interrupts (LPI) via a translation table (ITS). In other words, the GICv3 module doesn't support direct LPIs.
The CPU interface (GICC) is only supported via system registers
(ICC_xxx), not via memory-mapped
registers. The ICC_PMR_EL1 register is set to establish the CPU’s
default running priority such that normal interrupt delivery is permitted.
For LPIs, startup must provide the callouts for mask and unmask operations. LPI mask and unmask operation must use the ITS command queue for invalidating the configuration cache, which is a resource shared between cores.
Startup must also ensure that required registers (e.g., GITS_CBASER,
GICR_PROPBASER) are allocated.
[ module=gicv3 ]Is your GIC module loaded?
# cat /proc/config
[...]
intrctlr:gicv2Your system will display intrctlr:gicv3 if a GICv3 module is loaded,
or intrctlr:N/A if no module is loaded.
# cat /proc/ker/intr
gicv2 /* module name */
2025/01/23-14:27:09EST /* date of build */
spurious: 0 /* number of spurious interrupts */Cascades
The modules see child controllers as opaque, and the controllers rely on startup to provide the callouts and configuration. Therefore, the modules treat these controllers as a shared resource when delivering their interrupts, and masking and unmasking them. For both GICv2 and GICv3 modules, child controllers should be hooked to an SPI.
The module supports all GENFLAG flags for child controllers, except for INTR_GENFLAG_ID_LOOP.
Caveats
The GIC modules only replaces the root controller, which matches several intrinfo entries (e.g., PPIs and SPIs). Each type of interrupt must be its own intrinfo entry.
Section:intrinfo offset:0x00000e68 size:0x000000c0 elsize:0x00000060
0) vector_base:00000000, #vectors:32, cascade_vector:7fffffff <--- PPIs: base: 0, 32 vectors
cpu_intr_base:00000000, cpu_intr_stride:0, flags:0000, local_stride:32768
id => flags:0000, size:0030, rtn:ffffff80402050a8
eoi => flags:0000, size:0018, rtn:ffffff80402050d8
mask:ffffff80402050f0, unmask:ffffff8040205120, config:ffffff8040205150
1) vector_base:00000020, #vectors:128, cascade_vector:7fffffff <--- SPIs: base: 32, n vectors
cpu_intr_base:00000000, cpu_intr_stride:0, flags:0000, local_stride:0
id => flags:0000, size:0010, rtn:ffffff8040205160
eoi => flags:0000, size:0018, rtn:ffffff8040205170
mask:ffffff8040205188, unmask:ffffff80402051bc, config:0000000000000000
GICv3 startups typically split the different interrupt types.
The INTR_GENFLAG_ID_LOOP flag isn't supported for cascades and the root controller won't use the flag for itself either.
