embassy-rs/embassy · error

PLATFORM not initialized

Error message

PLATFORM not initialized

What it means

embassy-stm32-wpan's linklayer platform exposes PLATFORM, an Option<&'static Platform> that is populated during stack initialization (init/bring-up). get_platform() unwraps it with expect, so any platform callback invoked before the WPAN stack has been initialized panics with 'PLATFORM not initialized'.

Solutions

  1. Initialize the WPAN stack (the call that sets PLATFORM) before using any BLEPLAT/LINKLAYER crypto or RNG functions.
  2. Ensure task ordering: the init task must complete (PLATFORM stored) before radio/crypto tasks run.
  3. If a callback can fire early, defer it until after initialization or return an error instead of panicking.

Example fix

// before
let key = BLEPLAT_PkaReadP256Key(...); // stack not yet initialized
// after
let mut ble = ble::init(...).await; // stores PLATFORM first
let key = BLEPLAT_PkaReadP256Key(...);
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure stack init completed before using BLEPLAT helpers
// embassy API: hold a handle from init which proves initialization
let ble = embassy_stm32_wpan::init(config).expect("wpan init");
// only after `ble` exists, call RNG/PKA helpers

Try / catch

// wrap early radio work so a pre-init call cannot panic the app
if stack_ready.load(Ordering::Acquire) {
    let key = pka_read_key(...);
} else {
    defmt::warn!("WPAN not initialized; skipping crypto call");
}

Prevention

When it happens

Trigger: Calling get_platform()-dependent functions (LINKLAYER_PLAT_GetRNG, BLEPLAT_RngGet, BLEPLAT_PkaStartP256Key, BLEPLAT_PkaReadP256Key, BLEPLAT_PkaStartDhKey, aes_ecb_encrypt) before the Wireless/BLE stack init has stored the Platform into the static — e.g. calling crypto/RNG plats very early at boot or in a static initializer.

Common situations: Using the WPAN AES/RNG helpers before stm32wb/wba BLE init completed; calling BLEPLAT callbacks from an early interrupt that fires before stack bring-up; a task ordering issue where the radio task runs before init.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

static RADIO_SW_LOW_ISR_RUNNING_HIGH_PRIO: AtomicBool = AtomicBool::new(false);
static AHB5_SWITCHED_OFF: AtomicBool = AtomicBool::new(false);
static RADIO_SLEEP_TIMER_VAL: AtomicU32 = AtomicU32::new(0);

// 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();
    });

View on GitHub (pinned to 463a07b963)