embassy-rs/embassy · critical

CRACEN RNG health test failed

Error message

CRACEN RNG health test failed (rep={} prop={} startup={}); it needs a reset to produce entropy again

What it means

The CRACEN hardware true-random-number-generator on nRF54-series reports a health-test failure (repetitive-count, adaptive-proportion, or startup test) through the RNGCONTROL status register. embassy-nrf's CsRng checks this in blocking_fill_bytes and panics because once the health test fails, the entropy source cannot produce trustworthy randomness until the whole chip is reset. Any RNG consumer (TLS keys, UUIDs, etc.) must never receive data from a failed RNG.

Solutions

  1. Reset the chip (soft or hard reset) — the CRACEN RNG requires a reset to recover and produce entropy again
  2. Delay first RNG use until the CRACEN RNG has fully started, and check status before use
  3. Investigate power supply stability and operating conditions if startup failures recur on specific boards
  4. Check for known silicon errata for your nRF54 revision and apply the recommended workaround
  5. Retry with a fresh boot and, if reproducible, report to Nordic support with the rep/prop/startup fail bits

Example fix

null
Defensive patterns

Strategy: fallback

Validate before calling

// Probe RNG health before deriving keys where the HAL exposes try_ APIs:
// let mut probe = [0u8; 4];
// match rng.try_fill_bytes(&mut probe) { Ok(_) => {}, Err(_) => schedule_reset() }

Try / catch

// The HAL panics on failure; there is no catchable error. Use a fallback:
// match rng.try_fill_bytes(&mut buf) {
//     Ok(()) => buf,
//     Err(_) => { defmt::error!("CRACEN RNG failed, scheduling reset"); board_reset(); unreachable!() }
// }

Prevention

When it happens

Trigger: Calling `CsRng::fill_bytes`, `try_fill_bytes`, `blocking_fill_bytes`, `blocking_next_u32`, or `blocking_next_u64` when `rngcontrol.status().state()` is `Error` — i.e. repfail, propfail, or startupfail bits are set after power-up or during operation.

Common situations: Startup failure right after boot due to marginal analog conditions/power supply noise; very early reads before the startup health test completes; rare silicon/environmental health-test failures in the field; unusual operating temperature or voltage conditions on nRF54L15 targets.

Related errors


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

Appendix: source

Thrown at embassy-nrf/src/cracen.rs:128

        self.start_rng();

        let r = Self::core();
        for chunk in dest.chunks_mut(4) {
            // A failed health test parks the FSM in `Error`, where the FIFO never fills
            // again — so without this check the poll never returns, and being a blocking
            // loop in a sync fn it takes the whole executor with it. There is nothing to
            // do but fail loudly: measured on an nRF54LM20A, neither a SoftRst nor
            // reprogramming the cut-offs revives a TRNG that has already reached `Error`,
            // only a reset of the part does, and an infallible API cannot report. The
            // cut-offs programmed in `start_rng` are what keeps this unreached.
            let word = loop {
                if r.rngcontrol().fifolevel().read() != 0 {
                    break r.rngcontrol().fifo(0).read();
                }
                let status = r.rngcontrol().status().read();
                if status.state() == pac::cracencore::vals::State::Error {
                    panic!(
                        "CRACEN RNG health test failed (rep={} prop={} startup={}); it needs a reset to produce entropy again",
                        status.repfail(),
                        status.propfail(),
                        status.startupfail()
                    );
                }
            };

            let word = word.to_ne_bytes();
            let to_copy = word.len().min(chunk.len());
            chunk[..to_copy].copy_from_slice(&word[..to_copy]);
        }

        self.stop_rng();
    }

    /// Generate a random u32
    pub fn blocking_next_u32(&mut self) -> u32 {

View on GitHub (pinned to 463a07b963)