Interrupt controller kernel module for Aarch64 GIC architectures

QNX SDP8.0System ArchitectureDeveloperUser

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.

The interrupt controller module depends on startup to initialize the controller hardware to a well-defined baseline state. At minimum, startup must ensure the following conditions before the kernel module is loaded:
  • 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.

To use the GICv2 module, add the following attribute to the procnto line in your buildfile:
[ 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.

Note:
All interrupts except PPIs are optional.

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.

To use the GICv3 module, add the following attribute to the procnto line in your buildfile:
[ module=gicv3 ]

Is your GIC module loaded?

To check if you have a GIC module loaded, enter:
# cat /proc/config
[...]
intrctlr:gicv2

Your system will display intrctlr:gicv3 if a GICv3 module is loaded, or intrctlr:N/A if no module is loaded.

For more information about your GIC module, enter:
# cat /proc/ker/intr
gicv2                       /* module name */
2025/01/23-14:27:09EST      /* date of build */
spurious: 0                 /* number of spurious interrupts */

Cascades

It's possible to have a hierarchy of interrupt controllers, as shown in the image below:
Figure 1Hierarchy of interrupt controllers: child controller hooked to SPI 100


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.

Legacy GICv2 support in startup from libstartup bundles PPIs and SPIs in a single intrinfo. Ensure that your startup uses the latest libstartup to split them. For example:
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.

Page updated: