Skip to content

feat(kernel,hal): take timer interrupts on aarch64 (preemptive path) - #143

Merged
kernalix7 merged 1 commit into
mainfrom
feat/aarch64-preemptive-timer-irq
Jul 13, 2026
Merged

feat(kernel,hal): take timer interrupts on aarch64 (preemptive path)#143
kernalix7 merged 1 commit into
mainfrom
feat/aarch64-preemptive-timer-irq

Conversation

@kernalix7

Copy link
Copy Markdown
Owner

Wires the full EL1 IRQ path so the aarch64 kernel receives and handles physical-timer interrupts — the last piece for preemptive scheduling.

  • boot.rs: real exception vector table. EL1h IRQ slot (0x280) → el1h_irq saves 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).
  • gic.rs: GICv3 ack_irq (ICC_IAR1_EL1) / eoi_irq (ICC_EOIR1_EL1) + SPURIOUS_INTID.
  • irq.rs: aarch64_handle_irq acks, services timer PPI 30 (re-arm + charge_tick + EOI + guarded sched_yield_once), announces first ticks.
  • main.rs: re-arm timer + unmask IRQs (daifclr) before the halt loop.

Verified (-M virt,gic-version=3): after Entering halt loop, the log prints timer 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.

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
kernalix7 merged commit d91dbd9 into main Jul 13, 2026
2 checks passed
@kernalix7
kernalix7 deleted the feat/aarch64-preemptive-timer-irq branch July 13, 2026 02:58
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant