embassy-rs/embassy · error
Must be called from Core 0
Error message
Must be called from Core 0
What it means
rp2040_bootsel (the SIO ROM-based BOOTSEL button read routine) uses SIO FIFO inter-core messaging that must run on core 0. embassy-rp wraps it in in_ram(|| ...), whose Result is unwrapped with expect("Must be called from Core 0"), so calling is_bootsel_pressed() from core 1 panics at runtime.
Solutions
- Call is_bootsel_pressed() only from core 0 code (main task or a core-0-executed function).
- If the button read must happen on core 1, send a request over a channel to core 0, read the button there, and send the result back.
- Restructure so boot/button logic stays in setup code that always runs on core 0 before spawning core 1 work.
Example fix
// before (runs on core 1)
core1.spawn(move |_| { if is_bootsel_pressed() { ... } });
// after
core0_task: let pressed = is_bootsel_pressed();
channel.send(pressed).await; // forward to core 1 consumer Defensive patterns
Strategy: type-guard
Validate before calling
// only read BOOTSEL on core 0
#[cfg(feature = "rp2040")]
fn safe_bootsel_check() -> bool {
debug_assert!(!cortex_m::register::mpsir::read().is_some()); // main task runs on core 0
embassy_rp::bootsel::is_bootsel_pressed()
} Try / catch
// route the result through core 0 instead of calling directly on core 1 let pressed = core0_channel.request(ButtonRead).await;
Prevention
- Never call is_bootsel_pressed() from closures spawned on CORE1.
- Keep hardware/SIO helpers on core 0 and communicate results via channels.
- Document core-0-only APIs in your project's architecture notes.
When it happens
Trigger: Calling embassy_rp::bootsel::is_bootsel_pressed() (directly or via something like `read_cs_status`) from code running on RP2040 core 1 — e.g. inside a closure spawned on CORE1, or in a task pinned to core 1.
Common situations: Spawning a task that reads the BOOTSEL button on the second core for load balancing; using the helper inside a multicore worker loop; calling it from an interrupt handled on core 1.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Boot prepare error
- CORE1 not responding
- unrecognized rx error
- UART DMA reported invalid `write_addr`
- Passphrase is too short or too long
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/db0be6ef9d4f94f7.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-rp/src/bootsel.rs:40
/// task and for any DMAs from flash to complete
pub fn is_bootsel_pressed(_p: Peri<'_, crate::peripherals::BOOTSEL>) -> bool {
unsafe {
// Compute the base address for the GPIO_QSPI_SS_STATUS/GPIO_QSPI_SS_CTRL registers.
let cs_gpio: *mut ();
if cfg!(feature = "rp2040") {
cs_gpio = IO_QSPI.gpio(1).as_ptr();
} else if cfg!(feature = "_rp235x") {
cs_gpio = IO_QSPI.gpio(3).as_ptr();
} else {
unimplemented!()
};
let mut cs_ctrl = GpioCtrl::default();
cs_ctrl.set_oeover(Oeover::Disable);
let cs_ctrl: u32 = mem::transmute(cs_ctrl);
let mut cs_status = 0;
in_ram(|| cs_status = ram_helpers::read_cs_status(cs_gpio, cs_ctrl)).expect("Must be called from Core 0");
// bootsel is active low, so invert
!mem::transmute::<u32, GpioStatus>(cs_status).infrompad()
}
}
mod ram_helpers {
/// Temporally reconfigures the CS gpio and returns the GpioStatus.
/// This function runs from RAM so it can disable flash XIP.
///
/// # Safety
///
/// The caller must ensure flash is idle and will remain idle.
/// This function must live in ram. It uses inline asm to avoid any
/// potential calls to ABI functions that might be in flash.
#[inline(never)]View on GitHub (pinned to 463a07b963)