ADR-040 — Lower-EL IRQ while standing EL0
- Status: Accepted (taken lower-EL IRQ while standing is a supported/probed path when the serial / tests pass; FIQ/SError lower-EL still park; umbrella isolation Planned)
- Date: 2026-09-13
Context
Track A / A5 isolation leftovers. Lower-EL AArch64 sync has long been live for SVC / IABORT / DABORT. The lower-EL IRQ / FIQ / SError vector slots historically parked (ADR-004). Standing EL0 (ADR-013 / ADR-024) entered with DAIF IRQ masked in SPSR, so a timer IRQ from EL0 was never taken.
After ADR-039, the standing/EL0 trampoline restores kernel TTBR0 with MSR TTBR0 + ISB only. The same restore is required on a lower-EL IRQ path before GIC / .data access (user TTBR0 omits MMIO and kernel data).
PAN enable and umbrella EL0 isolation are not this cut. Do not switch default -cpu cortex-a57. Keep _start at 0x4008_0000.
Hypotheses
| ID | Option | Notes |
|---|---|---|
| H1 (chosen) | Wire lower-EL IRQ (irq_lower_el): restore kernel TTBR0, gic::handle_irq, if el0::is_active() put user TTBR0 back and ERET to EL0. Probe: standing dual-SVC with WFI and SPSR IRQ-unmasked; serial el0: irq. Default ERET to EL0 stays IRQ-masked. FIQ/SError stay parked. | Largest honest first cut that does not race existing EL0 probes. |
| H2 (rejected) | Unmask IRQ on every standing/task ERET and claim “IRQ while standing is normal for all tasks.” | Larger surface; timer mid-task may interact with A4/A1–A3 probes before this mile is proven. |
| H3 (rejected) | Claim “EL0 isolated” / PAN / “secure OS” because IRQ returns to EL0. | Isolation umbrella stays Planned. |
| H4 (fallback) | If H1 regresses smoke, ship a scaffold documenting the park and why — do not regress smoke. | Honesty over the mile. |
Decision
- Lower-EL IRQ live while standing. Vector slot
0x480callsirq_lower_el(same frame + TTBR0 restore assync_lower_el).handle_irq_lower_elrunsgic::handle_irq, thenstay_at_el0whenis_active(). Non-standing lower-EL IRQ still fails closed (park). - Probe. Hello path: arm standing + expect IRQ,
timer::arm_soon,ERETwith SPSR0x340(EL0t, I clear), payloadSVC #1/WFI/MOVZ/SVC #2. Serialel0: irqwhen the IRQ was taken while standing. Existingel0: standing/el0: restoredstill print on that trip. - Default ERET stays masked.
eret_to_el0keeps SPSR0x3c0. Only the IRQ probe useseret_to_el0_irq_enabled. Enabling IRQ for all standing tasks is a later cut. - Fail-closed sensors.
scripts/qemu-smoke.shgrepsel0: irq.#[test_case]lower_el_irq_while_standing. - Still Planned. Lower-EL FIQ/SError; IRQ-unmasked default standing/task
ERET; PAN enable on-cpu cortex-a57; remaining identity RAM /_startteardown; umbrella EL0 isolation. - NFR-10 text is revised in place (ID unchanged). Threat-model v1.20. Do not mint NFR-15+.
Honesty
Say “taken lower-EL IRQ while standing returned to EL0” only when the serial / tests pass. Do not say:
- “EL0 isolated”
- PAN (see ADR-026)
- lower-EL FIQ/SError are handled
- every standing task takes IRQs by default (default
ERETstill masks I) - “secure OS” / “hardened”
Consequences
src/exception.rsownsirq_lower_el/handle_irq_lower_el.src/el0.rsowns theWFIstanding probe.src/timer.rsownsarm_soon.- ADR-013 remains the isolation direction; this ADR is the lower-EL IRQ-while-standing cut.
- A later ADR may unmask IRQ for standing tasks by default, or take FIQ/SError. That work is not this cut.