feat(kernel,hal): take timer interrupts on aarch64 (preemptive path) - #143
Merged
Conversation
Wire the full EL1 IRQ path so the aarch64 kernel receives and handles physical-timer interrupts: - boot.rs: replace the 16 NOP exception-vector slots with real vectors. The EL1h IRQ slot (0x280) branches to el1h_irq, which saves a 288-byte trap frame (x0-x30 + ELR_EL1/SPSR_EL1 — the system regs must be stacked because a preemptive switch could otherwise clobber them), calls the Rust handler, restores, and erets. Other slots go to el1_default (spin, so an unexpected exception stops at a known PC under 'qemu -d int'). - gic.rs: add GICv3 CPU-interface ack_irq (ICC_IAR1_EL1) / eoi_irq (ICC_EOIR1_EL1) + SPURIOUS_INTID. - irq.rs: aarch64_handle_irq (extern C, called from the vector) acks at the GIC, services the timer PPI (INTID 30) — re-arm one-shot, charge a tick, EOI, then a guarded sched_yield_once — and announces the first few ticks for bring-up visibility. - main.rs: after the cooperative demo, re-arm the timer and unmask IRQs (daifclr) before the halt loop. Verified in QEMU (-M virt,gic-version=3): after 'Entering halt loop' the log prints 'timer IRQ received (preemptive)' three times — the timer PPI is delivered, acked, handled, and EOI'd end to end with no faults. x86_64/riscv64 unaffected.
kernalix7
added a commit
that referenced
this pull request
Jul 15, 2026
The timer-IRQ path (#143) delivered interrupts but could not actually preempt: two bugs surfaced when a re-armed timer had to preempt busy kernel threads. 1. GICv3 fresh Group-1 PPI delivery (gic.rs, init_redistributor): the init only enabled PPI 30 (GICR_ISENABLER0); it never assigned the PPI's group, priority, or trigger. Fresh delivery therefore never reached the CPU interface (the earlier 3-tick path only worked off a pre-latched pending IRQ). Program, in the SGI_base frame (GICR_BASE + 0x10000): GICR_IGROUPR0 = 0xFFFF_FFFF (SGIs/PPIs -> Group 1) + GICR_IGRPMODR0 = 0 (NS Group 1); GICR_IPRIORITYR byte for PPI 30 = 0x00 (must be < ICC_PMR_EL1 = 0xFF to pass the priority filter); GICR_ICFGR1 PPI-30 field = level. Written before ISENABLER0. 2. DAIF inheritance across a preemptive switch (main.rs): switch_context swaps GPRs + SP but not PSTATE, so a thread switched-to from inside a timer IRQ inherits DAIF.I = 1 (masked) and can never be preempted again — the demo threads deadlocked in wfi. Each demo thread now unmasks IRQs (msr daifclr) once it starts running under preemption. Adds a preemptive demo: two busy kernel threads (C, D) that NEVER yield; the generic timer alone rotates between them. Verified in QEMU (-M virt,gic-version=3): C and D interleave three times each, then the re-elected boot thread prints 'timer preemption verified', with no faults under 'qemu -d int'. x86_64/riscv64 unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires the full EL1 IRQ path so the aarch64 kernel receives and handles physical-timer interrupts — the last piece for preemptive scheduling.
el1h_irqsaves a 288-byte trap frame (x0-x30 + ELR_EL1/SPSR_EL1 — system regs stacked so a preemptive switch can't clobber them), calls the Rust handler, restores,erets. Other slots →el1_default(spin at a known PC).ack_irq(ICC_IAR1_EL1) /eoi_irq(ICC_EOIR1_EL1) + SPURIOUS_INTID.aarch64_handle_irqacks, services timer PPI 30 (re-arm + charge_tick + EOI + guarded sched_yield_once), announces first ticks.Verified (
-M virt,gic-version=3): afterEntering halt loop, the log printstimer IRQ received (preemptive)×3 — timer PPI delivered → GIC ack → vector → trap frame → Rust handler → EOI → eret, no faults. Split across two agents (GIC/handler) + hand-written vector asm; x86_64/riscv64 unaffected.