Does WFI reliably exit during timeout interrupts when interrupts are masked?
0 reputation · 23 Aug 2022, 10:48 UTC
0 reputation · 23 Aug 2022, 10:48 UTC
Implementing graceful timeouts on AArch64 often involves using the WFI (Wait For Interrupt) instruction in conjunction with the ARM Generic Timer. The goal is to place the core in a low-power state and wake it precisely when a programmable compare register triggers a private peripheral interrupt (PPI).
A technical uncertainty arises when interrupts are globally masked. While the architecture allows for the generation of interrupts, the interaction between masked interrupts and the WFI instruction's exit condition is implementation-defined. This creates a challenge for ensuring deterministic timeout handling without enabling full interrupt processing.
Which specific conditions guarantee that a WFI instruction will exit solely due to a timer-generated interrupt while interrupts remain masked? Does the architecture permit a masked interrupt to wake the core without triggering a corresponding exception handler?
30775 reputation · 23 Aug 2022, 21:50 UTC
Yes. On ARMv8-A (and ARMv9-A), WFI completes when an interrupt becomes pending, regardless of whether it is masked by PSTATE.I/F/A. Masking controls whether the exception is taken, not whether the core wakes. So a Generic Timer PPI (e.g., the physical non-secure timer interrupt) will bring the core out of WFI even with IRQs globally masked, and execution simply continues at the instruction after WFI — no handler runs until you later clear the mask.
This is architectural behavior, not implementation-defined. What is implementation-defined is which other events may also complete a WFI (debug events, some system activity), so treat the wake as "timer or something else" and confirm the source.
Confirmed by the architecture:
WFI is a hint that suspends execution until a wake-up event; a pending asynchronous exception is a wake-up event even when masked.DAIF bit is cleared.CNTP_CTL_EL0.ENABLE=1 and IMASK=0. If the timer's own IMASK is set, no interrupt is raised and WFI will not wake on that timer.Likely explanation for observed flakiness: if your timeout sometimes doesn't fire, the usual causes are the timer's IMASK bit, a PPI misrouted or disabled in the GIC (check the PPI's enable and target/routing for the correct core), or the core waking early on an unrelated event and your loop not re-checking the deadline.
CNTP_TVAL_EL0 (or CVAL) and set CNTP_CTL_EL0 = ENABLE | (IMASK=0).MSR DAIFSet, #2.WFI; on return, check CNTP_CTL_EL0.ISTATUS (or read the GIC pending state) to confirm the timer fired; if not, loop back to WFI.MSR DAIFClr, #2 to take the pending exception, or just handle the timeout inline and leave interrupts masked.Set a near-future compare value, mask IRQ, execute WFI, and confirm the PC advances to the next instruction with ISTATUS set. If you have PMU/cycle-counter access, you can also confirm the core actually entered the low-power state and woke at the deadline. Behavior described here assumes ARMv8-A with a standard Generic Timer and GICv2/v3; consult the ARM ARM sections on WFI and Generic Timer interrupts for your exact revision.
One detail that would change the recommendation: are you trying to guarantee the core wakes only on the timer? If so, you must disable or account for every other interrupt source and implementation-defined wake event on that core — the architecture does not promise exclusivity.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.