diff --git a/.claude/skills/arch-platform-porting/SKILL.md b/.claude/skills/arch-platform-porting/SKILL.md index e38930cd11..a7710a0043 100644 --- a/.claude/skills/arch-platform-porting/SKILL.md +++ b/.claude/skills/arch-platform-porting/SKILL.md @@ -90,7 +90,8 @@ Adjust the package list to match the crates touched. 3. Use QEMU debug flags such as `-d int,cpu_reset,guest_errors` and `-S -s` when xtask exposes or can be patched to pass them. 4. Inspect symbols and generated images with `llvm-objdump`, `readelf`, and map files. Confirm runtime addresses, not only link addresses. 5. Compare with local Linux architecture code for ordering of MMU, trap, SMP, and cache/TLB barriers when uncertain. First search for a local Linux source tree, then inspect the matching `arch/` directory; do not assume a fixed path. -6. Turn the root cause into a regression test or a focused QEMU case when practical. +6. On one-shot timer platforms, verify the IRQ handler acknowledges the current timer interrupt before dispatching into code that reprograms the next event. In particular, LoongArch timer handlers must not clear `TICLR` after `_handle_irq()` / `dispatch_irq()`, because the timer tick path may already have armed a near-deadline event and a late acknowledge can clear the freshly-pending interrupt, leaving timer-based sleeps stuck. +7. Turn the root cause into a regression test or a focused QEMU case when practical. ## Common Failure Signals diff --git a/platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs index 2adefbf66a..0196e99880 100644 --- a/platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs +++ b/platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs @@ -121,34 +121,40 @@ impl IrqIf for IrqIfImpl { trace!("IRQ {irq:?}"); - if let IrqType::Ipi = irq { - let mut status = iocsr_read_w(IOCSR_IPI_STATUS); - if status != 0 { - iocsr_write_w(IOCSR_IPI_CLEAR, status); - trace!("IPI status = {:#x}", status); - - while status != 0 { - let vector = status.trailing_zeros() as usize; - status &= !(1 << vector); - if !dispatch_irq(irq.as_usize()).handled { - warn!("Unhandled IRQ {irq:?}"); + match irq { + IrqType::Timer => { + // Clear the interrupt before dispatching. The timer handler + // programs the next one-shot event; clearing afterwards can + // drop a freshly-pending event and leave sleepers blocked. + ticlr::clear_timer_interrupt(); + if !dispatch_irq(irq.as_usize()).handled { + debug!("Unhandled IRQ {irq:?}"); + } + } + IrqType::Ipi => { + let mut status = iocsr_read_w(IOCSR_IPI_STATUS); + if status != 0 { + iocsr_write_w(IOCSR_IPI_CLEAR, status); + trace!("IPI status = {:#x}", status); + + while status != 0 { + let vector = status.trailing_zeros() as usize; + status &= !(1 << vector); + if !dispatch_irq(irq.as_usize()).handled { + warn!("Unhandled IRQ {irq:?}"); + } } } } - } else { - if !dispatch_irq(irq.as_usize()).handled { - debug!("Unhandled IRQ {irq:?}"); + IrqType::Io | IrqType::Ex(_) => { + if !dispatch_irq(irq.as_usize()).handled { + debug!("Unhandled IRQ {irq:?}"); + } } } - match irq { - IrqType::Timer => { - ticlr::clear_timer_interrupt(); - } - IrqType::Ex(irq) => { - eiointc::complete_irq(irq); - } - _ => {} + if let IrqType::Ex(irq) = irq { + eiointc::complete_irq(irq); } Some(irq.as_usize()) diff --git a/platforms/somehal/src/arch/loongarch64/mod.rs b/platforms/somehal/src/arch/loongarch64/mod.rs index 0db07fc7c9..d9d034168c 100644 --- a/platforms/somehal/src/arch/loongarch64/mod.rs +++ b/platforms/somehal/src/arch/loongarch64/mod.rs @@ -80,8 +80,12 @@ impl PlatOp for Plat { let irq = match raw { raw if raw == someboot::irq::systimer_irq().raw() => { let irq = someboot::irq::IrqId::new(raw); - _handle_irq(raw.into()); + // Clear the current timer interrupt before dispatching. The + // dispatch path reprograms the next one-shot timer; clearing + // afterwards can drop a newly-arrived timer edge and strand + // timer-based sleeps. someboot::timer::ack(); + _handle_irq(raw.into()); irq } IPI_IRQ => {