From 7b5b69ac8d237e21497fdc804ffe0b90c3f85c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 15 Jan 2026 08:14:59 +0100 Subject: [PATCH 1/2] Do not enter wfi when SBA access would not work --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/interrupt/riscv.rs | 33 +++++++++++++++++++++++++++++++++ esp-hal/src/interrupt/xtensa.rs | 10 ++++++++++ esp-rtos/CHANGELOG.md | 1 + esp-rtos/src/task/mod.rs | 6 ++++++ esp-rtos/src/task/riscv.rs | 6 ------ esp-rtos/src/task/xtensa.rs | 6 ------ 7 files changed, 51 insertions(+), 12 deletions(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 04dd4939c2f..e33069e8128 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RsaContext`, `AesContext` now derive `Clone`. (#4709) - `ShaContext` now derive `Clone`, except on ESP32. (#4709) - Dedicated GPIO implementation (#4699) +- `esp_hal::interrupt::wait_for_interrupt`, which enters `wfi` (RISC-V) or `waiti 0` (Xtensa) when it would notprevent a debugger from reading memory (#4782) ### Changed diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index d90d1e55295..0637b229020 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -374,6 +374,39 @@ pub(crate) fn bound_cpu_interrupt_for(_cpu: Cpu, interrupt: Interrupt) -> Option unsafe { assigned_cpu_interrupt(interrupt) } } +fn cpu_wait_mode_on() -> bool { + cfg_if::cfg_if! { + if #[cfg(soc_has_pcr)] { + crate::peripherals::PCR::regs().cpu_waiti_conf().read().cpu_wait_mode_force_on().bit_is_set() + } else { + crate::peripherals::SYSTEM::regs() + .cpu_per_conf() + .read() + .cpu_wait_mode_force_on() + .bit_is_set() + } + } +} + +/// Wait for an interrupt to occur. +/// +/// This function causes the current CPU core to execute its Wait For Interrupt +/// (WFI or equivalent) instruction. After executing this function, the CPU core +/// will stop execution until an interrupt occurs. +/// +/// This function will return immediately when a debugger is attached, so it is intended to be +/// called in a loop. +#[inline(always)] +pub fn wait_for_interrupt() { + if crate::debugger::debugger_connected() && !cpu_wait_mode_on() { + // when SYSTEM_CPU_WAIT_MODE_FORCE_ON is disabled in WFI mode SBA access to memory does not + // work for debugger, so do not enter that mode when debugger is connected. + // https://github.com/espressif/esp-idf/blob/b9a308a47ca4128d018495662b009a7c461b6780/components/esp_hw_support/cpu.c#L57-L60 + return; + } + unsafe { core::arch::asm!("wfi") }; +} + mod vectored { use super::*; use crate::interrupt::IsrCallback; diff --git a/esp-hal/src/interrupt/xtensa.rs b/esp-hal/src/interrupt/xtensa.rs index 2e8b9b28c99..4d658138e2a 100644 --- a/esp-hal/src/interrupt/xtensa.rs +++ b/esp-hal/src/interrupt/xtensa.rs @@ -291,6 +291,16 @@ pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority { unwrap!(Priority::try_from(prev_interrupt_priority)) } +/// Wait for an interrupt to occur. +/// +/// This function causes the current CPU core to execute its Wait For Interrupt +/// (WFI or equivalent) instruction. After executing this function, the CPU core +/// will stop execution until an interrupt occurs. +#[inline(always)] +pub fn wait_for_interrupt() { + unsafe { core::arch::asm!("waiti 0") }; +} + mod vectored { use super::*; diff --git a/esp-rtos/CHANGELOG.md b/esp-rtos/CHANGELOG.md index e9ea42bc0cf..2e6fea6c844 100644 --- a/esp-rtos/CHANGELOG.md +++ b/esp-rtos/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a possible deadlock on multi-core chips (#4478) - Fixed a memory leak of 48 bytes when deleting esp-radio timers (#4541) - Fixed a rare crash on Xtensa MCUs (#4580, #4591) +- RISC-V: the idle hook no longer prevents a debugger from reading memory (#4782) ### Removed diff --git a/esp-rtos/src/task/mod.rs b/esp-rtos/src/task/mod.rs index 6ad3e433e00..f95d9e405f0 100644 --- a/esp-rtos/src/task/mod.rs +++ b/esp-rtos/src/task/mod.rs @@ -33,6 +33,12 @@ use crate::{ pub type IdleFn = extern "C" fn() -> !; +pub(crate) extern "C" fn idle_hook() -> ! { + loop { + esp_hal::interrupt::wait_for_interrupt(); + } +} + #[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(crate) enum TaskState { diff --git a/esp-rtos/src/task/riscv.rs b/esp-rtos/src/task/riscv.rs index 82f7d3eff09..0964ff535a2 100644 --- a/esp-rtos/src/task/riscv.rs +++ b/esp-rtos/src/task/riscv.rs @@ -112,12 +112,6 @@ impl CpuContext { } } -pub(crate) extern "C" fn idle_hook() -> ! { - loop { - unsafe { core::arch::asm!("wfi") }; - } -} - pub(crate) fn set_idle_hook_entry(idle_context: &mut CpuContext, hook_fn: IdleFn) { // Point idle context PC at the assembly that calls the idle hook. We need a new stack // frame for the idle task on the main stack. diff --git a/esp-rtos/src/task/xtensa.rs b/esp-rtos/src/task/xtensa.rs index 6a67b5e25c7..f75c0be8a36 100644 --- a/esp-rtos/src/task/xtensa.rs +++ b/esp-rtos/src/task/xtensa.rs @@ -36,12 +36,6 @@ use crate::{ static IDLE_HOOK: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); -pub(crate) extern "C" fn idle_hook() -> ! { - loop { - unsafe { core::arch::asm!("waiti 0") }; - } -} - #[unsafe(naked)] extern "C" fn idle_entry() -> ! { core::arch::naked_asm!(" From 1e1abde874f06efddd5a9daeb47f0b2bdc30d0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 15 Jan 2026 08:33:09 +0100 Subject: [PATCH 2/2] Update esp-hal/CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esp-hal/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index e33069e8128..5071868cd56 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RsaContext`, `AesContext` now derive `Clone`. (#4709) - `ShaContext` now derive `Clone`, except on ESP32. (#4709) - Dedicated GPIO implementation (#4699) -- `esp_hal::interrupt::wait_for_interrupt`, which enters `wfi` (RISC-V) or `waiti 0` (Xtensa) when it would notprevent a debugger from reading memory (#4782) +- `esp_hal::interrupt::wait_for_interrupt`, which enters `wfi` (RISC-V) or `waiti 0` (Xtensa) when it would not prevent a debugger from reading memory (#4782) ### Changed