Click the blue "Arm Selected" in the upper left corner and select "Set as Star"
Note: This article primarily discusses the ARMV8-aarch64 architecture, gicv3, and Linux kernel 5.14 by default.
Consideration: Have you ever thought about a scenario where a high-priority interrupt preempts another interrupt that is currently being processed? This is known as interrupt nesting. Many might say, “Of course, this scenario exists,” but does it really exist in practice? Don’t be misled by blogs or chapters in books online; today we will discuss the essence by focusing on the following aspects:
-
Does the ARM CORE support interrupt nesting?
-
Does the GIC interrupt controller support interrupt nesting?
-
Does the Linux Kernel operating system use interrupt nesting?
Before we begin, let’s note a few common understandings (common knowledge that will not be repeated in this article):
-
FIQ and IRQ have the same priority; FIQ is just a type of interrupt, not a so-called fast interrupt.
1. Does the ARM CORE support interrupt nesting?
First, does the ARM CORE support interrupt nesting? The answer is Yes! However, there is a prerequisite: when entering interrupt handling, the PSTATE bits I, F, A, etc., are masked, and software needs to actively unmask them; then interrupt nesting can occur. As shown in the figure below, this is an example (or model) of ARM Core supporting interrupt nesting:
2. Does the GIC interrupt controller support interrupt nesting?
Next, does the GIC interrupt controller support interrupt nesting? The answer is Yes, it supports interrupt preemption and nesting!.
2.1. First, let’s introduce the concepts of priority and preemption
-
Each INTID (interrupt number) has a priority (represented by registers
GICD_IPRIORITYn
orGICR_IPRIORITYn
), which is an 8-bit unsigned value. 0x00 is the highest possible priority, and 0xFF is the lowest possible priority. -
Each PE has a priority mask register (
ICC_PMR_EL1
) in its CPU interface. This register sets the minimum priority required to forward interrupts to that PE. Only interrupts with a priority higher than the register value will be sent to the PE. -
The GICv3 architecture has the concept of running priority. When a PE responds to an interrupt, its running priority becomes that of the interrupt. When the PE writes to one of the EOI registers, the running priority returns to its previous value. If the PE is not processing an interrupt, the running priority is the idle priority (0xFF). Only interrupts with a priority higher than the running priority can preempt the current interrupt.
2.2. Without preemption: disabling preemption, disabling running priority
In the Without preemption case, high-priority interrupts cannot preempt active interrupts and must wait for the active interrupt to finish executing and return before a high-priority interrupt can occur “preemption” (is it appropriate to call it preemption here? This situation should belong to preemption pending interrupts). As shown in the figure below, this is an example:
-
High-priority interrupts cannot preempt active interrupts.
-
Waiting for active interrupts to complete and return, high-priority interrupts can then occur (i.e., preemption pending interrupts)
Even though you have disabled preemption, priority still exists and will still take effect.
Example of preemption pending interrupts, in this case, a high-priority interrupt can preempt a low-priority pending interrupt, not the currently executing interrupt. After preemption, the high-priority interrupt becomes pending. What most of us refer to as interrupt nesting means that one interrupt is executing and is preempted by another interrupt. When a low-priority interrupt on the CPU interface is in a pending state, a higher-priority interrupt may also become pending. The redistributor can send a Set command for a new higher-priority interrupt. This Set command will replace the previous Set command, causing the CPU interface to issue a Release for the lower-priority interrupt. The figure below shows an example of such a scenario. This example assumes that the priority of INTID Y is higher than that of INTID X.
2.3. With preemption
Next, let’s look at the example of preemption during an active interrupt, which describes the situation: one interrupt is executing, and then another higher-priority interrupt interrupts it, which is exactly what we refer to as interrupt nesting.
When considering preemption, the concept of running priority is crucial. When a high-priority interrupt is sent to a PE that is already handling a low-priority interrupt, preemption occurs. Preemption adds some extra complexity to software, but it prevents low-priority interrupts from blocking the processing of higher-priority interrupts.
2.4. For a gicv3 IP, the priority certainly exists; is it Without preemption or With preemption? How is it configured?
Please refer to the ICC_BPRn_EL1
register, which defines the priority value field divided into two parts: the group priority field and the sub-priority field. The group priority field determines group 1 interrupt preemption. To explain it simply, the interrupt priority is divided into two parts, as shown in the figure below, the BIT[2:0] of the ICC_BPRn_EL1
register defines the value of N in the figure belowFor preemption, only the Group priority bits are considered. The Subpriority bits are ignored. Then, for example:
-
INTID A has priority 0x10
-
INTID B has priority 0x20
-
INTID C has priority 0x21
As above, there are three interrupts: A can preempt B, but B cannot preempt C because B and C have the same Group priority
3. Does the Linux Kernel operating system use interrupt nesting?
Next, does the Linux Kernel operating system use interrupt nesting? The answer is No!
First, let’s check the configuration of the ICC_BPRn_EL1
register:The value written is 0, which means N=0, as shown in the first row of the figure below, indicating that preemption is enabled.
Next, look at the priority configuration for each INTID (interrupt number); as shown below, during the GIC initialization phase, each INTID (interrupt number) is configured with the same priority value of 0xA5. This means that all interrupts have the same priority.
In fact, in the GICv3 code, an interface is provided to set the priority for a specific interrupt. Looking at the function’s purpose, it is merely to set the priority for NMI interrupts (Note: Currently, there is no such interrupt in the ARMv8 architecture of the Linux Kernel; a new type of NMI interrupt was added in ARMv9), and its value is 0xA5 & 0x7f = 0x25, which is less than 0xA0, meaning that this priority is higher than that of other interrupts.



