From 48706c1b14395e818a229c0d0b8b9c72c0d89c75 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 2 Sep 2026 15:24:45 +0200 Subject: [PATCH 1/4] arch/intel64: fix self-deadlock in intel64_oneshot_start() intel64_oneshot_start() takes g_oneshot_spin and then, if the timer is already running, calls intel64_oneshot_cancel(), which takes the same spinlock again. Spinlocks are not recursive, so the CPU spins forever on its own lock while holding the critical section; the HPET timer ISR on another CPU then blocks on g_cpu_irqlock and the system hangs. This is hit as soon as the tickless scheduler re-arms a running HPET oneshot timer under SMP (ostest task_restart, LTP aio tests). Stop the running timer inline instead of calling cancel: disable the interrupt, detach the ISR so up_enable_irq() does not assert on a busy IRQ, and clear the running flag. The ISR, comparator and interrupt enable are reprogrammed by the rest of the function anyway. Assisted-by: Claude Code Signed-off-by: raiden00pl --- arch/x86_64/src/intel64/intel64_oneshot.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/arch/x86_64/src/intel64/intel64_oneshot.c b/arch/x86_64/src/intel64/intel64_oneshot.c index a98b8cea55502..eb907eaed17ee 100644 --- a/arch/x86_64/src/intel64/intel64_oneshot.c +++ b/arch/x86_64/src/intel64/intel64_oneshot.c @@ -301,10 +301,21 @@ int intel64_oneshot_start(struct intel64_oneshot_s *oneshot, flags = spin_lock_irqsave(&g_oneshot_spin); if (oneshot->running) { - /* Yes.. then cancel it */ + /* Yes.. then stop it. Do NOT call intel64_oneshot_cancel() here: + * it takes g_oneshot_spin, which we already hold, and spinlocks are + * not recursive, so that deadlocks the CPU. Everything else that + * cancel would do (ISR, comparator, interrupt enable) is + * reprogrammed below anyway. + */ tmrinfo("Already running... cancelling\n"); - intel64_oneshot_cancel(oneshot, NULL); + +#ifndef CONFIG_INTEL64_HPET_FSB + INTEL64_TIM_DISABLEINT(oneshot->tch, oneshot->chan); + INTEL64_TIM_SETISR(oneshot->tch, oneshot->chan, NULL, NULL, false); +#endif + + oneshot->running = false; } /* Save the new handler and its argument */ From befb91b619315842c887dcfe46536b1a241cc434 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Tue, 8 Sep 2026 09:28:38 +0200 Subject: [PATCH 2/4] arch/intel64: fix nxstyle issues in intel64_hpet.c fix nxstyle issues in intel64_hpet.c Assisted-by: Claude Code Signed-off-by: raiden00pl --- arch/x86_64/src/intel64/intel64_hpet.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/x86_64/src/intel64/intel64_hpet.c b/arch/x86_64/src/intel64/intel64_hpet.c index 83ff189b1eb80..4e993c278308a 100644 --- a/arch/x86_64/src/intel64/intel64_hpet.c +++ b/arch/x86_64/src/intel64/intel64_hpet.c @@ -223,6 +223,7 @@ static void intel64_hpet_cmpset(struct intel64_tim_dev_s *dev, uint8_t timer, uint64_t cmp) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + DEBUGASSERT(timer < hpet->timers); intel64_hpet_putreg(hpet, HPET_TCOMP_OFFSET(timer), cmp); } @@ -239,6 +240,7 @@ static uint64_t intel64_hpet_cmpget(struct intel64_tim_dev_s *dev, uint8_t timer) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + DEBUGASSERT(timer < hpet->timers); return intel64_hpet_getreg(hpet, HPET_TCOMP_OFFSET(timer)); } @@ -255,6 +257,7 @@ static uint64_t intel64_hpet_intget(struct intel64_tim_dev_s *dev, uint8_t timer) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + return (intel64_hpet_getreg(hpet, HPET_GISR_OFFSET) & HPET_GISR_TINT(timer)); } @@ -271,6 +274,7 @@ static void intel64_hpet_intack(struct intel64_tim_dev_s *dev, uint8_t timer) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + intel64_hpet_putreg(hpet, HPET_GISR_OFFSET, HPET_GISR_TINT(timer)); } @@ -285,6 +289,7 @@ static void intel64_hpet_intack(struct intel64_tim_dev_s *dev, static uint64_t intel64_hpet_cntget(struct intel64_tim_dev_s *dev) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + return intel64_hpet_getreg(hpet, HPET_MCNTR_OFFSET); } @@ -300,6 +305,7 @@ static void intel64_hpet_cntset(struct intel64_tim_dev_s *dev, uint64_t cntr) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + return intel64_hpet_putreg(hpet, HPET_MCNTR_OFFSET, cntr); } @@ -314,6 +320,7 @@ static void intel64_hpet_cntset(struct intel64_tim_dev_s *dev, static uint32_t intel64_hpet_perget(struct intel64_tim_dev_s *dev) { struct intel64_hpet_s *hpet = (struct intel64_hpet_s *)dev; + return hpet->clk_per_fs; } From a2e1fd017ae41eef182ee9ddcf68fb0cd5488880 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Mon, 7 Sep 2026 13:25:42 +0200 Subject: [PATCH 3/4] arch/intel64: keep the HPET ISR attached when the timer is stopped intel64_hpet_setisr() with a NULL handler detached the ISR with irq_attach(irq, NULL), which installs irq_unexpected_isr(). The oneshot driver does this every time the timer expires or is re-armed, so an HPET interrupt already in flight to another CPU lands on the unexpected ISR and panics the system: irq_unexpected_isr: ERROR irq: 34 seen under SMP with the LTP test suite. Just mask the interrupt and keep the ISR attached; intel64_oneshot_handler() already treats an interrupt that arrives while the timer is not running as spurious. Assisted-by: Claude Code Signed-off-by: raiden00pl --- arch/x86_64/src/intel64/intel64_hpet.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arch/x86_64/src/intel64/intel64_hpet.c b/arch/x86_64/src/intel64/intel64_hpet.c index 4e993c278308a..36512ac6c7175 100644 --- a/arch/x86_64/src/intel64/intel64_hpet.c +++ b/arch/x86_64/src/intel64/intel64_hpet.c @@ -390,9 +390,12 @@ static int intel64_hpet_setisr(struct intel64_tim_dev_s *dev, uint8_t timer, if (handler == NULL) { - /* Disable interrupt */ + /* Disable the interrupt but keep the ISR attached. Detaching it here + * installs irq_unexpected_isr(), and an HPET interrupt that is already + * in flight to another CPU then panics the system. A stray interrupt + * is handled as spurious by the oneshot ISR instead. + */ - irq_attach(irq, handler, arg); up_disable_irq(irq); } else From 1e2e333d856e80ba43fb330020a7def5c98635aa Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Tue, 8 Sep 2026 10:23:27 +0200 Subject: [PATCH 4/4] arch/intel64: don't clear the oneshot handler from the HPET ISR intel64_oneshot_handler() cleared oneshot->handler and oneshot->arg after picking them up, without holding g_oneshot_spin, while intel64_oneshot_start() re-arms the timer under that lock from another CPU. Now that the HPET ISR stays attached across a re-arm, a stale interrupt can interleave with start(): it reads the freshly installed handler, clears it, and start() then sets running = true again, so the genuine expiry that follows finds running == true with a NULL handler and jumps to address zero from interrupt context (page fault at RIP 0 in the CPU0 IDLE task while the LTP lio_listio tests were running), or the alarm is simply lost and the tickless system stops. The handler and its argument are owned by start() and cancel(); the ISR only needs to read them. Leave them alone in the ISR and skip the call if none is installed. The remaining effect of a stale interrupt is an early invocation of the alarm callback, which is harmless: the tickless scheduler re-evaluates its expirations and re-arms the timer. Assisted-by: Claude Code Signed-off-by: raiden00pl --- arch/x86_64/src/intel64/intel64_oneshot.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/arch/x86_64/src/intel64/intel64_oneshot.c b/arch/x86_64/src/intel64/intel64_oneshot.c index eb907eaed17ee..27168a329f88b 100644 --- a/arch/x86_64/src/intel64/intel64_oneshot.c +++ b/arch/x86_64/src/intel64/intel64_oneshot.c @@ -100,18 +100,21 @@ static int intel64_oneshot_handler(int irg_num, void * context, void *arg) INTEL64_TIM_ACKINT(oneshot->tch, oneshot->chan); #endif - /* The timer is no longer running */ + /* The timer is no longer running. Only pick up the handler here; + * it is owned by intel64_oneshot_start()/cancel(), which may be + * re-arming the timer on another CPU right now. Clearing it from + * the ISR could leave a re-armed timer without a handler, and the + * next expiry would then jump through a NULL pointer. + */ oneshot->running = false; - - /* Forward the event, clearing out any vestiges */ - oneshot_handler = (oneshot_handler_t)oneshot->handler; - oneshot->handler = NULL; oneshot_arg = (void *)oneshot->arg; - oneshot->arg = NULL; - oneshot_handler(oneshot_arg); + if (oneshot_handler != NULL) + { + oneshot_handler(oneshot_arg); + } } else {