diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a615547..6c67a5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,8 @@ jobs: logger: - log - defmt + features: + - rand env: # Peripheral Feature flags FLAGS: rt @@ -45,6 +47,6 @@ jobs: - name: Install thumbv8m rust target run: rustup target add thumbv8m.main-none-eabihf - name: Build - run: cargo build --verbose --release --examples --target thumbv8m.main-none-eabihf --features ${{ matrix.mcu }},${{ matrix.logger }},${{ env.FLAGS }} + run: cargo build --verbose --release --examples --target thumbv8m.main-none-eabihf --features ${{ matrix.mcu }},${{ matrix.features }},${{ matrix.logger }},${{ env.FLAGS }} - name: Test - run: cargo test --lib --target x86_64-unknown-linux-gnu --features ${{ matrix.mcu }},${{ matrix.logger }},${{ env.FLAGS }} + run: cargo test --lib --target x86_64-unknown-linux-gnu --features ${{ matrix.mcu }},${{ matrix.features }},${{ matrix.logger }},${{ env.FLAGS }} diff --git a/Cargo.toml b/Cargo.toml index 24c3e0c..011caa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ ethernet = [] # Only STM32H563/73 have ethernet otfdec = [] # Only STM32H573/33 have OTFDEC sdmmc2 = [] # Only STM32H563/73 have SDMMC2 +rand = ["dep:rand_core"] + rt = ["stm32h5/rt"] stm32h503 = ["stm32h5/stm32h503", "device-selected", "rm0492"] stm32h523 = ["stm32h5/stm32h523", "device-selected", "rm0481", "h523_h533"] @@ -64,6 +66,7 @@ embedded-hal = "1.0.0" defmt = { version = "1.0.0", optional = true } paste = "1.0.15" log = { version = "0.4.20", optional = true} +rand_core = { version = "0.6", default-features = false, optional = true } [dev-dependencies] log = { version = "0.4.20"} diff --git a/examples/blinky_random.rs b/examples/blinky_random.rs new file mode 100644 index 0000000..706582d --- /dev/null +++ b/examples/blinky_random.rs @@ -0,0 +1,72 @@ +// Note: Code taken from stm32h7xx-hal +//! This example toggles the Pin PA5 with a randomly generated period between 0 +//! and 200 milliseconds. The random period is generated by the internal +//! hardware random number generator. + +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_hal::delay::DelayNs; +use stm32h5xx_hal::{delay::Delay, pac, prelude::*}; +#[macro_use] +mod utilities; + +use log::info; + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().expect("cannot take peripherals"); + + // Constrain and Freeze power + info!("Setup PWR... "); + let pwr = dp.PWR.constrain(); + let pwrcfg = pwr.vos0().freeze(); + + // Constrain and Freeze clock + info!("Setup RCC... "); + let rcc = dp.RCC.constrain(); + //let ccdr = rcc.sys_ck(100.MHz()).freeze(pwrcfg, &dp.SYSCFG); + let ccdr = rcc.sys_ck(100.MHz()).freeze(pwrcfg, &dp.SBS); + + info!(""); + info!("stm32h5xx-hal example - Random Blinky"); + info!(""); + + let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + + // Configure PA5 as output. + let mut led = gpioa.pa5.into_push_pull_output(); + + // Get the delay provider. + let mut delay = Delay::new(cp.SYST, &ccdr.clocks); + + // Get true random number generator + let mut rng = dp.RNG.rng(ccdr.peripheral.RNG, &ccdr.clocks); + let mut random_bytes = [0u16; 3]; + match rng.fill(&mut random_bytes) { + Ok(()) => info!("random bytes: {:?}", random_bytes), + Err(err) => info!("RNG error: {:?}", err), + } + + loop { + let random_element: Result = rng.gen(); + + match random_element { + Ok(random) => { + // NOTE: the result of the expression `random % 200` + // is biased. This bias is called "modulo-bias". It is + // acceptable here for simplicity, but may not be + // acceptable for your application. + let period = random % 200_u32; + + led.toggle(); + delay.delay_ms(period); + } + Err(err) => info!("RNG error: {:?}", err), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ece8ce4..a086c91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,9 @@ pub mod icache; #[cfg(feature = "device-selected")] pub mod delay; +#[cfg(feature = "device-selected")] +pub mod rng; + #[cfg(feature = "device-selected")] mod sealed { pub trait Sealed {} diff --git a/src/prelude.rs b/src/prelude.rs index d0d2d40..962878c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -7,5 +7,6 @@ pub use crate::icache::ICacheExt as _stm32h5xx_hal_icache_ICacheExt; pub use crate::pwr::PwrExt as _stm32h5xx_hal_pwr_PwrExt; pub use crate::rcc::RccExt as _stm32h5xx_hal_rcc_RccExt; +pub use crate::rng::{RngCore as _, RngExt as _}; pub use crate::time::U32Ext as _; pub use fugit::{ExtU32 as _, RateExtU32 as _}; diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..d30cc9f --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,450 @@ +// Note: Code taken from stm32h7xx-hal +//! Random Number Generator +//! +//! # Examples +//! +//! - [Random Blinky](https://github.com/stm32-rs/stm32h5xx-hal/blob/master/examples/blinky_random.rs) + +use core::cmp; +use core::marker::PhantomData; +use core::mem; + +use crate::rcc::{rec, rec::RngClkSel}; +use crate::rcc::{CoreClocks, ResetEnable}; +use crate::stm32::RNG; +use crate::time::Hertz; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SeedError; + +/// Return the kernel clock for the Random Number Generator +/// +/// # Panics +/// +/// Panics if the kernel clock is not running +fn kernel_clk_unwrap(prec: rec::Rng, clocks: &CoreClocks) -> Hertz { + match prec.get_kernel_clk_mux() { + //RngClkSel::Hsi48 => { + RngClkSel::Hsi48Ker => { + clocks.hsi48_ck().expect("RNG: HSI48 must be enabled") + } + RngClkSel::Pll1Q => { + clocks.pll1().q_ck().expect("RNG: PLL1_Q must be enabled") + } + RngClkSel::Lse => unimplemented!(), + RngClkSel::Lsi => clocks.lsi_ck().expect("RNG: LSI must be enabled"), + } +} + +fn setup_clocks(prec: rec::Rng, clocks: &CoreClocks) -> Hertz { + let prec = prec.enable().reset(); + + let hclk = clocks.hclk(); + let rng_clk = kernel_clk_unwrap(prec, clocks); + + // Otherwise clock checker will always flag an error + // See RM0481 Rev 2 Section 32.3.6 + assert!(rng_clk > (hclk / 32), "RNG: Clock too slow"); + + rng_clk +} + +#[cfg(any( + feature = "stm32h562", + feature = "stm32h563", + feature = "stm32h573", +))] + +/// Note: +/// This uses the register values specified in AN4230 but verification +/// using this HAL has not been performed. Users can/should do their +/// own verification or request documentation from ST directly. +/// Requires RNG to be disabled since some register values can only be written when RNGEN = 0 +pub trait RngNist { + fn rng_nist_st_an4230( + self, + prec: rec::Rng, + clocks: &CoreClocks, + ) -> Rng; +} + +#[cfg(any( + feature = "stm32h562", + feature = "stm32h563", + feature = "stm32h573" +))] +impl RngNist for RNG { + /// Note: + /// This uses the register values specified in AN4230 but verification + /// using this HAL has not been performed. Users can/should do their + /// own verification or request documentation from ST directly. + /// Requires RNG to be disabled since some register values can only be written when RNGEN = 0 + fn rng_nist_st_an4230( + self, + prec: rec::Rng, + clocks: &CoreClocks, + ) -> Rng { + let rng_clk = setup_clocks(prec, clocks); + + // ST has tested this configuration only with a RNG clock of 48MHz + assert_eq!(rng_clk, Hertz::MHz(48), "RNG: Clock not 48 MHz"); + + // Set control register values, also need to write 1 to CONDRST to be able to set the other values + self.cr() + .write(|w| unsafe { w.bits(0x00F00E00).condrst().set_bit() }); + + // Set health test control register values + self.htcr().write(|w| unsafe { w.bits(0x6A91) }); + + // Set noise source control register + self.nscr().write(|w| unsafe { w.bits(0x3AF66) }); + + // Configuration done, reset CONDRST, its value goes to 0 when the reset process is + // done. It takes about 2 AHB clock cycles + 2 RNG clock cycles. + self.cr().write(|w| w.condrst().clear_bit()); + + // It should take about 2 AHB clock cycles + 2 RNG clock cycles + while self.cr().read().condrst().bit_is_set() {} + + // Enable RNG + self.cr().modify(|_, w| w.rngen().set_bit()); + + Rng::new(self) + } +} + +pub trait RngExt { + fn rng(self, prec: rec::Rng, clocks: &CoreClocks) -> Rng; + fn rng_fast(self, prec: rec::Rng, clocks: &CoreClocks) -> Rng; +} + +impl RngExt for RNG { + /// This uses the register values specified in RM0481 Rev 2 section 32.6.2 RNG configuration C + fn rng(self, prec: rec::Rng, clocks: &CoreClocks) -> Rng { + setup_clocks(prec, clocks); + + // Set control register values, also need to write 1 to CONDRST to be able to set the other values + self.cr().write(|w| unsafe { + w.nistc() + .clear_bit() + .rng_config1() + .bits(0x0F) + .clkdiv() + .bits(0x0) + .rng_config2() + .bits(0x0) + .rng_config3() + .bits(0xD) + .ced() + .clear_bit() + .condrst() + .set_bit() + }); + + // Set health test control register values + self.htcr().write(|w| unsafe { w.bits(0xAAC7) }); + + // Set noise source control register + // Note: + // This is currently not available in the PAC or SVD for H503 but is planned to be added + #[cfg(not(feature = "stm32h503"))] + self.nscr().write(|w| unsafe { w.bits(0x0003FFFF) }); + + // Configuration done, reset CONDRST, its value goes to 0 when the reset process is + // done. It takes about 2 AHB clock cycles + 2 RNG clock cycles. + self.cr().write(|w| w.condrst().clear_bit()); + + // It should take about 2 AHB clock cycles + 2 RNG clock cycles + while self.cr().read().condrst().bit_is_set() {} + + // Enable RNG + self.cr().modify(|_, w| w.rngen().set_bit()); + + Rng::new(self) + } + + /// This uses the register values specified in RM0481 Rev 2 section 32.6.2 RNG configuration B + fn rng_fast(self, prec: rec::Rng, clocks: &CoreClocks) -> Rng { + setup_clocks(prec, clocks); + + // Set control register values, also need to write 1 to CONDRST to be able to set the other values + self.cr().write(|w| unsafe { + w.nistc() + .set_bit() + .rng_config1() + .bits(0x18) + .clkdiv() + .bits(0x0) + .rng_config2() + .bits(0x0) + .rng_config3() + .bits(0x0) + .ced() + .clear_bit() + .condrst() + .set_bit() + }); + + // Set health test control register values + self.htcr().write(|w| unsafe { w.bits(0xAAC7) }); + + // Set noise source control register + #[cfg(not(feature = "stm32h503"))] // Not available on H503 + self.nscr().write(|w| unsafe { w.bits(0x0003FFFF) }); + + // Configuration done, reset CONDRST, its value goes to 0 when the reset process is + // done. It takes about 2 AHB clock cycles + 2 RNG clock cycles. + self.cr().write(|w| w.condrst().clear_bit()); + + // It should take about 2 AHB clock cycles + 2 RNG clock cycles + while self.cr().read().condrst().bit_is_set() {} + + // Enable RNG + self.cr().modify(|_, w| w.rngen().set_bit()); + + Rng::new(self) + } +} + +pub trait RngCore { + fn gen(&mut self) -> Result; + fn fill(&mut self, dest: &mut [W]) -> Result<(), SeedError>; +} + +#[cfg(any( + feature = "stm32h562", + feature = "stm32h563", + feature = "stm32h573" +))] +/// NIST mode (type state) +/// Use [RngNist::rng_nist_st_an4230] to generate [Rng] in this mode +pub struct NIST; +/// FAST mode (type state) +/// Use [RngExt::rng_fast] to generate [Rng] in this mode +pub struct FAST; +/// NORMAL mode (type state) +/// Use [RngExt::rng] to generate [Rng] in this mode +pub struct NORMAL; + +pub struct Rng { + rb: RNG, + _mode: PhantomData, +} + +impl Rng { + fn new(rb: RNG) -> Self { + Self { + rb, + _mode: PhantomData, + } + } + /// Returns 32 bits of randomness, or error + /// Automatically resets the seed error flag upon SeedError but will still return SeedError + /// Upon receiving SeedError the user is expected to keep polling this function until a valid value is returned + pub fn value(&mut self) -> Result { + 'outer: loop { + let status = self.rb.sr().read(); + + if status.cecs().bit() { + #[cfg(feature = "log")] + log::warn!("RNG Clock error detected, retrying"); + + #[cfg(feature = "defmt")] + defmt::warn!("RNG Clock error detected, retrying"); + + let sr = self.rb.sr(); + // Give rng some time to recover from clock disturbance, this time seems to be about a handful of milliseconds + for _ in 0..100_000 { + if sr.read().cecs().bit_is_clear() { + continue 'outer; + } + } + panic!("Failed to automatically recover from Rng Clock Error"); + } else if status.secs().bit() { + #[cfg(feature = "log")] + log::warn!("RNG Seed error detected, retrying"); + + #[cfg(feature = "defmt")] + defmt::warn!("RNG Seed error detected, retrying"); + // Reset seed error flag so as to leave the peripheral in a valid state ready for use + self.rb.sr().modify(|_, w| w.seis().clear_bit()); + return Err(SeedError); + } else if status.drdy().bit() { + return Ok(self.rb.dr().read().rndata().bits()); + } + } + } + + pub fn release(self) -> RNG { + self.rb + } + + /// Returns a reference to the inner peripheral + pub fn inner(&self) -> &RNG { + &self.rb + } + + /// Returns a mutable reference to the inner peripheral + pub fn inner_mut(&mut self) -> &mut RNG { + &mut self.rb + } +} + +impl core::iter::Iterator for Rng { + type Item = u32; + + fn next(&mut self) -> Option { + loop { + // We recover automatically from a seed error, so try again + if let Ok(x) = self.value() { + return Some(x); + } + } + } +} + +macro_rules! rng_core { + ($($type:ty),+) => { + $( + impl RngCore<$type> for Rng { + /// Returns a single element with random value, or error + fn gen(&mut self) -> Result<$type, SeedError> { + let val = self.value()?; + Ok(val as $type) + } + + /// Fills buffer with random values, or return error + fn fill(&mut self, buffer: &mut [$type]) -> Result<(), SeedError> { + const BATCH_SIZE: usize = mem::size_of::() / mem::size_of::<$type>(); + let mut i = 0_usize; + while i < buffer.len() { + let random_word = self.value()?; + + // using to_ne_bytes does not work for u8 and would make the macro + // implementation more complicated + #[allow(clippy::transmute_num_to_bytes)] + let bytes: [$type; BATCH_SIZE] = unsafe { mem::transmute(random_word) }; + let n = cmp::min(BATCH_SIZE, buffer.len() - i); + buffer[i..i + n].copy_from_slice(&bytes[..n]); + i += n; + } + Ok(()) + } + } + )+ + }; +} + +// Only for types larger than 32 bits +macro_rules! rng_core_large { + ($($type:ty),+) => { + $( + impl RngCore<$type> for Rng { + fn gen(&mut self) -> Result<$type, SeedError> { + const WORDS: usize = mem::size_of::<$type>() / mem::size_of::(); + let mut res: $type = 0; + + for i in 0..WORDS { + res |= (self.value()? as $type) << (i * (u32::BITS as usize)) + } + + Ok(res) + } + + fn fill(&mut self, dest: &mut [$type]) -> Result<(), SeedError> { + let len = dest.len() * (mem::size_of::<$type>() / mem::size_of::()); + let ptr = dest.as_mut_ptr() as *mut u32; + let slice_u32 = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; + self.fill(slice_u32) + } + } + )+ + }; +} + +macro_rules! rng_core_transmute { + ($($type:ty = $from:ty),+) => { + $( + impl RngCore<$type> for Rng { + fn gen(&mut self) -> Result<$type, SeedError> { + let num = >::gen(self)?; + Ok(unsafe { mem::transmute::<$from, $type>(num) }) + } + + fn fill(&mut self, dest: &mut [$type]) -> Result<(), SeedError> { + let unsigned_slice = unsafe { mem::transmute::<&mut [$type], &mut [$from]>(dest) }; + >::fill(self, unsigned_slice) + } + } + )+ + }; +} + +rng_core!(u8, u16, u32); + +// Alignment of these types must be a multiple of mem::align_of::<32>() +rng_core_large!(u64, u128); + +// A and B must have the same alignment +// rng_core_transmute!(A = B) +// assert!(mem::align_of::() == mem::align_of::()) +rng_core_transmute!( + i8 = u8, + i16 = u16, + i32 = u32, + i64 = u64, + i128 = u128, + isize = usize +); + +// If usize is 32 bits, use the rng_core! impl +#[cfg(target_pointer_width = "32")] +rng_core!(usize); + +// If usize is 64 bits, use the rng_core_large! impl +#[cfg(target_pointer_width = "64")] +rng_core_large!(usize); + +// rand_core +#[cfg(feature = "rand")] +#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] +impl rand_core::RngCore for Rng { + /// Generate a random u32 + /// Panics if RNG fails. + fn next_u32(&mut self) -> u32 { + self.gen().unwrap() + } + + /// Generate a random u64 + /// Panics if RNG fails. + fn next_u64(&mut self) -> u64 { + self.gen().unwrap() + } + + /// Fill a slice with random data. + /// Panics if RNG fails. + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill(dest).unwrap() + } + + /// Try to fill a slice with random data. Return an error if RNG fails. + fn try_fill_bytes( + &mut self, + dest: &mut [u8], + ) -> Result<(), rand_core::Error> { + self.fill(dest).map_err(|_e| { + core::num::NonZeroU32::new(rand_core::Error::CUSTOM_START) + // This should never fail as long as no enum variant is equal to 0 + .expect("Internal hal error") + .into() + }) + } +} + +#[cfg(all( + feature = "rand", + any(feature = "stm32h562", feature = "stm32h563", feature = "stm32h573") +))] +#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] +impl rand_core::CryptoRng for Rng {}