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

  1. Call is_bootsel_pressed() only from core 0 code (main task or a core-0-executed function).
  2. 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.
  3. 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

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


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)