Skip to content

feat(kernel,hal): preemptive kernel-thread scheduling on aarch64 - #144

Merged
kernalix7 merged 1 commit into
mainfrom
feat/aarch64-preemptive-scheduling
Jul 15, 2026
Merged

feat(kernel,hal): preemptive kernel-thread scheduling on aarch64#144
kernalix7 merged 1 commit into
mainfrom
feat/aarch64-preemptive-scheduling

Conversation

@kernalix7

Copy link
Copy Markdown
Owner

The timer-IRQ path (#143) delivered interrupts but couldn't 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 only enabled PPI 30 (GICR_ISENABLER0) — never set its group/priority/trigger, so fresh delivery never reached the CPU interface (the earlier 3-tick path only worked off a pre-latched pending IRQ). In the SGI_base frame (GICR_BASE + 0x10000): GICR_IGROUPR0 = 0xFFFF_FFFF (+ IGRPMODR0 = 0 → NS Group 1), GICR_IPRIORITYR[PPI30] = 0x00 (must be < ICC_PMR_EL1=0xFF), GICR_ICFGR1[PPI30] = 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 → deadlock in wfi. Each demo thread now msr daifclr's once it runs under preemption.

Demo

Two busy kernel threads (C, D) that never yield; the generic timer alone rotates between them.

Verified (-M virt,gic-version=3):

preemptive: thread D scheduled   (×3 each, interleaved — no yields)
preemptive: thread C scheduled
...
preemptive: C and D both ran — timer preemption verified.
Entering halt loop.

No faults under qemu -d int. GIC fix diagnosed via an opus agent; DAIF fix + demo hand-written & QEMU-verified. x86_64/riscv64 unaffected.

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.
@kernalix7
kernalix7 merged commit 1f6cfe6 into main Jul 15, 2026
2 checks passed
@kernalix7
kernalix7 deleted the feat/aarch64-preemptive-scheduling branch July 15, 2026 05:27
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