embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
5.32k stars 733 forks source link

stm32h7: RNG locks up #3047

Open darkwater opened 4 months ago

darkwater commented 4 months ago

On an STM32H723ZG, Rng seems to lock up after the first call.

Tested using the rng example, by wrapping the call in a loop:

    loop {
        let mut buf = [0u8; 16];
        unwrap!(rng.async_fill_bytes(&mut buf).await);
        info!("random bytes: {:02x}", buf);

        Timer::after_secs(1).await;
    }

Output:

...
INFO  Hello World!
└─ rng::____embassy_main_task::{async_fn#0} @ src/bin/rng.rs:20  
INFO  random bytes: [d7, c3, 2a, 33, 71, 6a, 87, 4f, 6f, 55, 37, b9, 52, 9b, b8, cc]
└─ rng::____embassy_main_task::{async_fn#0} @ src/bin/rng.rs:27  
<nothing happens anymore>

Tested on a Nucleo-H723ZG, with some stuff connected, but that shouldn't affect the RNG I believe.

darkwater commented 4 months ago

Interestingly, I can make the buffer larger, and the first call will still work, unless I make it too large; it breaks somewhere between 850 and 900 bytes. But the second call never works.

Actually, if it breaks from large buffer, the only log output I see (defmt level trace) is "calling", no hello world or flash/rcc debug info. Maybe some memory corruption is going on?

8cichy8 commented 3 months ago

Hello. Maybe stupid question, but did you set right chip feature for crate embassy-stm32 in Cargo.toml? Something similar happened to me, when I copied local project a forgot to change feature from stm32f412re to stm32f411ce. Then it did hang on Timer::after_secs(1).await ...

darkwater commented 3 months ago

Yeah, everything's configured properly for my chip. The only thing I'm not sure about is the RCC configuration, but it seems like all the RNG needs it the HSI48 input.

Something I noticed in Rng::reset():

    /// Reset the RNG.
    #[cfg(rng_v1)]
    pub fn reset(&mut self) {
        T::regs().cr().write(|reg| {
            reg.set_rngen(false);
        });
        T::regs().sr().modify(|reg| {
            reg.set_seis(false);
            reg.set_ceis(false);
        });
        T::regs().cr().modify(|reg| {
            reg.set_rngen(true);
        });
        // Reference manual says to discard the first.
        let _ = self.next_u32();
    }

The reference manual for the STM32H723/733, STM32H725/735 and STM32H730 doesn't mention anything about discarding the first sample after a reset. If I comment out that line, it doesn't immediately break after the first async_fill_bytes.

However, after a while, a seed error will still occur, and it gets stuck at waiting for SECS to be cleared:

    /// Try to recover from a seed error.
    pub fn recover_seed_error(&mut self) {
        self.reset();
        // reset should also clear the SEIS flag
        if T::regs().sr().read().seis() {
            warn!("recovering from seed error failed");
            return;
        }
        // wait for SECS to be cleared by RNG
        while T::regs().sr().read().secs() {}
    }
darkwater commented 3 months ago

Looks like the sequence to recover from a seed error is different too:

The following sequence must be used to fully recover from a seed error:

  1. Software reset by writing CONDRST at 1 and at 0 (see bitfield description for details).
  2. wait for CONDRST to be cleared in the RNG_CR register, then confirm that SEIS is cleared in the RNG_SR register.
  3. wait for SECS to be cleared by RNG. The random number generation is now back to normal.

The following implementation seems to work:

    /// Try to recover from a seed error.
    #[cfg(feature = "stm32h723zg")]
    pub fn recover_seed_error(&mut self) {
        // set CONDRST = 1
        T::regs().cr().write(|r| r.0 |= 1 << 30);
        // set CONDRST = 0
        T::regs().cr().write(|r| r.0 &= !(1 << 30));

        // wait for CONDRST to be reset
        while T::regs().cr().read().0 & (1 << 30) != 0 {}

        T::regs().sr().modify(|r| {
            if r.seis() {
                // clear SEIS
                r.set_seis(false)
            }
        });

        // wait for SECS to be cleared
        while T::regs().sr().read().secs() {}

        // EDIT: a reset is also needed
        self.reset();
    }

Looks like it's just a different version of the RNG? I'm not sure how to implement that properly.

PegasisForever commented 1 month ago

I'm using STM32H723VG on a custom board and the RNG works normally. Can you check your clock config for RNG is correct? (config.rcc.mux.rngsel when you run embassy_stm32::init)

darkwater commented 1 month ago

I dont understand clock configs very well, this is what I've got:

    let p = embassy_stm32::init({
        let mut hal_config = embassy_stm32::Config::default();
        hal_config.enable_debug_during_sleep = true;

        hal_config.rcc.hsi = Some(HSIPrescaler::DIV1);
        hal_config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: false });
        hal_config.rcc.mux.rngsel = Rngsel::HSI48;
        hal_config.rcc.csi = true;
        hal_config.rcc.pll1 = Some(Pll {
            source: PllSource::HSI,
            prediv: PllPreDiv::DIV4,
            mul: PllMul::MUL50,
            divp: Some(PllDiv::DIV2),
            divq: Some(PllDiv::DIV8),
            divr: None,
        });
        hal_config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz
        hal_config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz
        hal_config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz
        hal_config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz
        hal_config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz
        hal_config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz
        hal_config.rcc.voltage_scale = VoltageScale::Scale1;
        hal_config
    });

But either way, the logic I'm seeing in the current Embassy implementation doesn't match what I'm reading in the reference manual, and the function I provided does work. Maybe the current implementation happens to works under some circumstances?

hansihe commented 2 weeks ago

I have a STM32H723VG on a custom board, and I am seeing the same behaviour as @darkwater.

PegasisForever commented 2 weeks ago

This is my clock config:

    let mut config = Config::default();
    {
        use embassy_stm32::rcc::mux::*;
        use embassy_stm32::rcc::*;

        config.rcc.hsi = Some(HSIPrescaler::DIV1);
        config.rcc.hse = Some(Hse {
            freq: mhz(16),
            mode: HseMode::Oscillator,
        });
        config.rcc.csi = false;
        config.rcc.hsi48 = Some(Hsi48Config {
            sync_from_usb: false,
        });
        config.rcc.ls = LsConfig::default_lsi();

        config.rcc.pll1 = Some(Pll {
            source: PllSource::HSE,
            prediv: PllPreDiv::DIV1,
            mul: PllMul::MUL32,
            divp: Some(PllDiv::DIV1),
            divq: Some(PllDiv::DIV4),
            divr: Some(PllDiv::DIV2),
        });

        config.rcc.pll2 = Some(Pll {
            source: PllSource::HSE,
            prediv: PllPreDiv::DIV1,
            mul: PllMul::MUL20,
            divp: Some(PllDiv::DIV8),
            divq: Some(PllDiv::DIV2),
            divr: Some(PllDiv::DIV2),
        });
        config.rcc.pll3 = Some(Pll {
            source: PllSource::HSE,
            prediv: PllPreDiv::DIV1,
            mul: PllMul::MUL24,
            divp: Some(PllDiv::DIV2),
            divq: Some(PllDiv::DIV8),
            divr: Some(PllDiv::DIV2),
        });

        config.rcc.sys = Sysclk::PLL1_P;
        config.rcc.d1c_pre = AHBPrescaler::DIV1;

        config.rcc.apb1_pre = APBPrescaler::DIV2;
        config.rcc.apb2_pre = APBPrescaler::DIV2;
        config.rcc.apb3_pre = APBPrescaler::DIV2;
        config.rcc.apb4_pre = APBPrescaler::DIV2;
        config.rcc.ahb_pre = AHBPrescaler::DIV2;

        config.rcc.voltage_scale = VoltageScale::Scale0;

        config.rcc.mux.spi123sel = Saisel::PLL1_Q;
        config.rcc.mux.usart234578sel = Usart234578sel::PCLK1;
        config.rcc.mux.rngsel = Rngsel::HSI48;
        config.rcc.mux.i2c4sel = I2c4sel::PCLK4;
        config.rcc.mux.i2c1235sel = I2c1235sel::PCLK1;
        config.rcc.mux.spi6sel = Spi6sel::PCLK4;
        config.rcc.mux.spi45sel = Spi45sel::PCLK2;
        config.rcc.mux.adcsel = Adcsel::PLL2_P;
        config.rcc.mux.fdcansel = Fdcansel::PLL1_Q;
        config.rcc.mux.usbsel = Usbsel::PLL3_Q;
    }
    let p = embassy_stm32::init(config);