embassy-rs/embassy · critical

EVENT_CHANNEL not initialized

Error message

EVENT_CHANNEL not initialized

What it means

The WPAN (BLE) stack on STM32WBA stores the outgoing event channel sender in a static OptionalCell (EVENT_CHANNEL), populated during stack initialization. get_channel() unwraps it with expect(); if the BLE link layer callback fires before the embassy-stm32-wpan stack has been initialized (the static is still None), this panic fires. It is an internal invariant: the hardware CPU2 firmware raised a BLE event but the Rust-side sink was never set up.

Solutions

  1. Initialize the WPAN stack (create the BLE stack so EVENT_CHANNEL is populated) before enabling the IPCC/BLE interrupts or before CPU2 can send events.
  2. Ensure CPU2 wireless firmware is started only after the Rust-side stack initialization completes.
  3. Do not manually enable BLECB/IPCC channel interrupts; let the embassy-wpan stack set them up.
  4. If the panic appears after a version upgrade, update embassy-stm32-wpan and its docs-following init sequence, since the init API may have changed.

Example fix

// before
unsafe { CPU2.start_firmware() };
T::Interrupt::enable(); // IRQ may fire before stack init
let mut ble = Ble::new(...);

// after
let mut ble = Ble::new(...); // initializes EVENT_CHANNEL
unsafe { CPU2.start_firmware() };
T::Interrupt::enable();
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling BLE IRQs / starting CPU2, assert stack init happened:
// initialize the WPAN stack first; the embassy API guarantees EVENT_CHANNEL is set after Ble/Stack::new.
let mut ble = Ble::new(p.IPCC, p.RADIO, Irqs);
let mut stack = embassy_stm32_wpan::ble::Stack::new(&mut ble);
// only now:
// stack.run(...) / enable interrupts / start CPU2

Prevention

When it happens

Trigger: Calling BLECB_Indication (i.e., CPU2 delivering a BLE event over IPCC) before the application has constructed and run the WPAN stack (e.g. before Ble::new / stack init registers the zerocopy channel sender). Also occurs if stack init was skipped or ran on a different execution path than the interrupt that services IPCC channel 1.

Common situations: Enabling the BLE IRQ or starting CPU2 wireless firmware too early in main; forgetting to call the WPAN stack initialization before enabling interrupts; copying example code that enables the radio IRQ but drops the stack builder; running the BLE interrupt handler in tests/benchmarks without a full stack.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/41c9f29ad2e5bea1. Report an issue: GitHub.

Appendix: source

Thrown at embassy-stm32-wpan/src/wba/linklayer_plat.rs:165

// Critical-section restore token for IRQ enable/disable pairing.
// Only written when the IRQ disable counter transitions 0->1, and consumed when it transitions 1->0.
static mut CS_RESTORE_STATE: Option<critical_section::RestoreState> = None;

// Optional hardware RNG instance for true random number generation.
// The RNG peripheral pointer is stored here to be used by LINKLAYER_PLAT_GetRNG.
// This must be set by the application using `set_rng_instance` before the link layer requests random numbers.
pub(crate) static mut PLATFORM: Option<&'static Platform> = None;

pub(crate) static mut EVENT_CHANNEL: Option<zerocopy_channel::Sender<'static, CriticalSectionRawMutex, ChannelPacket>> =
    None;

const fn get_platform() -> &'static Platform {
    unsafe { PLATFORM.as_ref().expect("PLATFORM not initialized") }
}

const fn get_channel() -> &'static mut zerocopy_channel::Sender<'static, CriticalSectionRawMutex, ChannelPacket> {
    unsafe { EVENT_CHANNEL.as_mut().expect("EVENT_CHANNEL not initialized") }
}

// ============================================================================
// AES-128 ECB Hardware Acceleration (Embassy driver)
// ============================================================================

/// Perform AES-128 ECB encryption using the Embassy AES driver.
fn aes_ecb_encrypt(key: &[u8; 16], input: &[u8; 16], output: &mut [u8; 16]) {
    get_platform().borrow_aes(|aes| {
        let cipher = AesEcb::new(key);
        let mut ctx = aes.start(&cipher, Direction::Encrypt);
        aes.payload_blocking(&mut ctx, input, output, true).unwrap();
        aes.finish_blocking(ctx).unwrap();
    });
}

// ============================================================================
// AES-CMAC (RFC 4493) Implementation

View on GitHub (pinned to 463a07b963)