From fee793cbf8a0bdd020636ce433cc0883105dd2db Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 8 Dec 2025 10:16:42 -0800 Subject: MCXA TRNG driver --- embassy-mcxa/Cargo.toml | 5 +- embassy-mcxa/src/clocks/mod.rs | 2 + embassy-mcxa/src/interrupt.rs | 1 + embassy-mcxa/src/lib.rs | 1 + embassy-mcxa/src/trng.rs | 620 +++++++++++++++++++++++++++++++++++++++++ examples/mcxa/Cargo.toml | 1 + examples/mcxa/src/bin/trng.rs | 88 ++++++ 7 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 embassy-mcxa/src/trng.rs create mode 100644 examples/mcxa/src/bin/trng.rs diff --git a/embassy-mcxa/Cargo.toml b/embassy-mcxa/Cargo.toml index 8ed842aec..51e4c6f53 100644 --- a/embassy-mcxa/Cargo.toml +++ b/embassy-mcxa/Cargo.toml @@ -39,7 +39,7 @@ embedded-hal-nb = { version = "1.0" } embedded-io = "0.6" embedded-io-async = { version = "0.6.1" } heapless = "0.8" -mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt", "critical-section"], version = "0.1.0", rev = "e7dfed8740b449b6ac646bab8ac6776a3c099267" } +mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt", "critical-section"], version = "0.1.0", rev = "02bd04a21ef8f8f67f88239ff5df765bb7e60fd8" } nb = "1.1.0" paste = "1.0.15" maitake-sync = { version = "0.2.2", default-features = false, features = ["critical-section", "no-cache-pad"] } @@ -48,6 +48,9 @@ maitake-sync = { version = "0.2.2", default-features = false, features = ["criti embassy-time = { version = "0.5.0", optional = true } embassy-time-driver = { version = "0.2.1", optional = true } +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } + [features] default = ["rt"] diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index 014a12519..9288f5dc1 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs @@ -949,4 +949,6 @@ pub(crate) mod gate { // DMA0 peripheral - uses NoConfig since it has no selectable clock source impl_cc_gate!(DMA0, mrcc_glb_cc0, mrcc_glb_rst0, dma0, NoConfig); + // TRNG peripheral - uses NoConfig since it has no selectaple clock source + impl_cc_gate!(TRNG0, mrcc_glb_cc1, mrcc_glb_rst1, trng0, NoConfig); } diff --git a/embassy-mcxa/src/interrupt.rs b/embassy-mcxa/src/interrupt.rs index c960af7a2..be2704454 100644 --- a/embassy-mcxa/src/interrupt.rs +++ b/embassy-mcxa/src/interrupt.rs @@ -35,6 +35,7 @@ mod generated { LPUART5, OS_EVENT, RTC, + TRNG0 ); } diff --git a/embassy-mcxa/src/lib.rs b/embassy-mcxa/src/lib.rs index 1bbdffa06..12c2708de 100644 --- a/embassy-mcxa/src/lib.rs +++ b/embassy-mcxa/src/lib.rs @@ -19,6 +19,7 @@ pub mod interrupt; pub mod lpuart; pub mod ostimer; pub mod rtc; +pub mod trng; pub use crate::pac::NVIC_PRIO_BITS; diff --git a/embassy-mcxa/src/trng.rs b/embassy-mcxa/src/trng.rs new file mode 100644 index 000000000..6adcd0f23 --- /dev/null +++ b/embassy-mcxa/src/trng.rs @@ -0,0 +1,620 @@ +//! True Random Number Generator + +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_hal_internal::Peri; +use maitake_sync::WaitCell; +use mcxa_pac::trng0::osc2_ctl::TrngEntCtl; + +use crate::clocks::enable_and_reset; +use crate::clocks::periph_helpers::NoConfig; +use crate::interrupt::typelevel; +use crate::interrupt::typelevel::Handler; +use crate::peripherals::TRNG0; + +static WAIT_CELL: WaitCell = WaitCell::new(); + +/// TRNG Driver +pub struct Trng<'d> { + _peri: Peri<'d, TRNG0>, +} + +impl<'d> Trng<'d> { + /// Instantiates a new TRNG peripheral driver. + pub fn new(_peri: Peri<'d, TRNG0>, config: Config) -> Self { + _ = unsafe { enable_and_reset::(&NoConfig) }; + + Self::configure(config); + Self { _peri } + } + + fn configure(config: Config) { + regs() + .mctl() + .modify(|_, w| w.rst_def().set_bit().prgm().enable().err().clear_bit_by_one()); + + regs().scml().write(|w| unsafe { + w.mono_max() + .bits(config.monobit_limit_max) + .mono_rng() + .bits(config.monobit_limit_max - config.monobit_limit_min) + }); + + regs().scr1l().write(|w| unsafe { + w.run1_max() + .bits(config.run_length1_limit_max) + .run1_rng() + .bits(config.run_length1_limit_max - config.run_length1_limit_min) + }); + + regs().scr2l().write(|w| unsafe { + w.run2_max() + .bits(config.run_length2_limit_max) + .run2_rng() + .bits(config.run_length2_limit_max - config.run_length2_limit_min) + }); + + regs().scr3l().write(|w| unsafe { + w.run3_max() + .bits(config.run_length3_limit_max) + .run3_rng() + .bits(config.run_length3_limit_max - config.run_length3_limit_min) + }); + + regs().scr4l().write(|w| unsafe { + w.run4_max() + .bits(config.run_length4_limit_max) + .run4_rng() + .bits(config.run_length4_limit_max - config.run_length4_limit_min) + }); + + regs().scr5l().write(|w| unsafe { + w.run5_max() + .bits(config.run_length5_limit_max) + .run5_rng() + .bits(config.run_length5_limit_max - config.run_length5_limit_min) + }); + + regs().scr6pl().write(|w| unsafe { + w.run6p_max() + .bits(config.run_length6_limit_max) + .run6p_rng() + .bits(config.run_length6_limit_max - config.run_length6_limit_min) + }); + + regs() + .pkrmax() + .write(|w| unsafe { w.pkr_max().bits(config.poker_limit_max) }); + + regs() + .frqmax() + .write(|w| unsafe { w.frq_max().bits(config.freq_counter_max) }); + + regs() + .frqmin() + .write(|w| unsafe { w.frq_min().bits(config.freq_counter_min) }); + + regs() + .sblim() + .write(|w| unsafe { w.sb_lim().bits(config.sparse_bit_limit) }); + + regs().scmisc().write(|w| unsafe { + w.lrun_max() + .bits(config.long_run_limit_max) + .rty_ct() + .bits(config.retry_count) + }); + + regs() + .mctl() + .modify(|_, w| w.dis_slf_tst().variant(config.self_test.into())); + + regs().sdctl().write(|w| unsafe { + w.samp_size() + .bits(config.sample_size) + .ent_dly() + .bits(config.entropy_delay) + }); + + regs() + .osc2_ctl() + .modify(|_, w| w.trng_ent_ctl().variant(config.osc_mode.into())); + + regs().mctl().modify(|_, w| w.prgm().disable()); + + let _ = regs().ent(7).read().bits(); + } + + fn start() { + regs().mctl().modify(|_, w| w.trng_acc().set_bit()); + } + + fn stop() { + regs().mctl().modify(|_, w| w.trng_acc().clear_bit()); + } + + fn blocking_wait_for_generation() { + while regs().mctl().read().ent_val().bit_is_clear() {} + } + + fn fill_chunk(chunk: &mut [u8]) { + let mut entropy = [0u32; 8]; + + for (i, item) in entropy.iter_mut().enumerate() { + *item = regs().ent(i).read().bits(); + } + + let entropy: [u8; 32] = unsafe { core::mem::transmute(entropy) }; + + chunk.copy_from_slice(&entropy[..chunk.len()]); + } + + // Blocking API + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, buf: &mut [u8]) { + if buf.is_empty() { + return; // nothing to fill + } + + Self::start(); + for chunk in buf.chunks_mut(32) { + Self::blocking_wait_for_generation(); + Self::fill_chunk(chunk); + } + Self::stop(); + } + + /// Return a random u32, blocking version. + pub fn blocking_next_u32(&mut self) -> u32 { + Self::start(); + Self::blocking_wait_for_generation(); + let result = regs().ent(0).read().bits(); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Self::stop(); + + result + } + + /// Return a random u64, blocking version. + pub fn blocking_next_u64(&mut self) -> u64 { + Self::start(); + Self::blocking_wait_for_generation(); + + let mut result = u64::from(regs().ent(0).read().bits()) << 32; + result |= u64::from(regs().ent(1).read().bits()); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Self::stop(); + + result + } +} + +impl Drop for Trng<'_> { + fn drop(&mut self) { + // reset the TRNG + regs().mctl().write(|w| w.rst_def().set_bit()); + } +} + +/// TRNG Async Driver +pub struct AsyncTrng<'d> { + _peri: Peri<'d, TRNG0>, +} + +impl<'d> AsyncTrng<'d> { + /// Instantiates a new TRNG peripheral driver. + pub fn new( + _peri: Peri<'d, TRNG0>, + _irq: impl crate::interrupt::typelevel::Binding + 'd, + config: Config, + ) -> Self { + _ = unsafe { enable_and_reset::(&NoConfig) }; + + Trng::configure(config); + + crate::pac::Interrupt::TRNG0.unpend(); + unsafe { + crate::pac::Interrupt::TRNG0.enable(); + } + + Self { _peri } + } + + fn enable_ints() { + regs().int_mask().write(|w| { + w.hw_err() + .set_bit() + .ent_val() + .set_bit() + .frq_ct_fail() + .set_bit() + .intg_flt() + .set_bit() + }); + } + + async fn wait_for_generation() -> Result<(), Error> { + WAIT_CELL + .wait_for(|| { + Self::enable_ints(); + regs().mctl().read().ent_val().bit_is_set() + }) + .await + .map_err(|_| Error::ErrorStatus) + } + + // Async API + + /// Fill the buffer with random bytes, async version. + pub async fn async_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), Error> { + if buf.is_empty() { + return Ok(()); // nothing to fill + } + + Trng::start(); + for chunk in buf.chunks_mut(32) { + Self::wait_for_generation().await?; + Trng::fill_chunk(chunk); + } + Trng::stop(); + + Ok(()) + } + + /// Return a random u32, async version. + pub async fn async_next_u32(&mut self) -> Result { + Trng::start(); + Self::wait_for_generation().await?; + let result = regs().ent(0).read().bits(); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Trng::stop(); + + Ok(result) + } + + /// Return a random u64, async version. + pub async fn async_next_u64(&mut self) -> Result { + Trng::start(); + Self::wait_for_generation().await?; + + let mut result = u64::from(regs().ent(0).read().bits()) << 32; + result |= u64::from(regs().ent(1).read().bits()); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Trng::stop(); + + Ok(result) + } + + // Blocking API + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, buf: &mut [u8]) { + if buf.is_empty() { + return; // nothing to fill + } + + Trng::start(); + for chunk in buf.chunks_mut(32) { + Trng::blocking_wait_for_generation(); + Trng::fill_chunk(chunk); + } + Trng::stop(); + } + + /// Return a random u32, blocking version. + pub fn blocking_next_u32(&mut self) -> u32 { + Trng::start(); + Trng::blocking_wait_for_generation(); + let result = regs().ent(0).read().bits(); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Trng::stop(); + + result + } + + /// Return a random u64, blocking version. + pub fn blocking_next_u64(&mut self) -> u64 { + Trng::start(); + Trng::blocking_wait_for_generation(); + + let mut result = u64::from(regs().ent(0).read().bits()) << 32; + result |= u64::from(regs().ent(1).read().bits()); + + // New random bytes are generated only after reading ENT7 + let _ = regs().ent(7).read().bits(); + Trng::stop(); + + result + } +} + +impl Drop for AsyncTrng<'_> { + fn drop(&mut self) { + // reset the TRNG + regs().mctl().write(|w| w.rst_def().set_bit()); + } +} + +fn regs() -> &'static crate::pac::trng0::RegisterBlock { + unsafe { &*crate::pac::Trng0::ptr() } +} + +/// Trng errors +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Integrity error. + IntegrityError, + + /// Frequency counter fail + FrequencyCountFail, + + /// Error status + ErrorStatus, + + /// Buffer argument is invalid + InvalidBuffer, +} + +/// I2C interrupt handler. +pub struct InterruptHandler; + +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + if regs().int_status().read().bits() != 0 { + regs().int_ctrl().write(|w| { + w.hw_err() + .clear_bit() + .ent_val() + .clear_bit() + .frq_ct_fail() + .clear_bit() + .intg_flt() + .clear_bit() + }); + + WAIT_CELL.wake(); + } + } +} + +/// True random number generator configuration parameters. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// Total number of Entropy samples that will be taken during + /// Entropy generation. + pub sample_size: u16, + + /// Length (in system clocks) of each Entropy sample taken. + pub entropy_delay: u16, + + /// Enable or disable internal self-tests. + pub self_test: SelfTest, + + /// Frequency Counter Maximum Limit + pub freq_counter_max: u32, + + /// Frequency Counter Minimum Limit + pub freq_counter_min: u32, + + /// Statistical check monobit max limit + pub monobit_limit_max: u16, + + /// Statistical check monobit min limit + pub monobit_limit_min: u16, + + /// Statistical check run length 1 limit max + pub run_length1_limit_max: u16, + + /// Statistical check run length 1 limit min + pub run_length1_limit_min: u16, + + /// Statistical check run length 2 limit max + pub run_length2_limit_max: u16, + + /// Statistical check run length 2 limit min + pub run_length2_limit_min: u16, + + /// Statistical check run length 3 limit max + pub run_length3_limit_max: u16, + + /// Statistical check run length 3 limit min + pub run_length3_limit_min: u16, + + /// Statistical check run length 4 limit max + pub run_length4_limit_max: u16, + + /// Statistical check run length 4 limit min + pub run_length4_limit_min: u16, + + /// Statistical check run length 5 limit max + pub run_length5_limit_max: u16, + + /// Statistical check run length 5 limit min + pub run_length5_limit_min: u16, + + /// Statistical check run length 6 limit max + pub run_length6_limit_max: u16, + + /// Statistical check run length 6 limit min + pub run_length6_limit_min: u16, + + /// Retry count + pub retry_count: u8, + + /// Long run limit max + pub long_run_limit_max: u8, + + /// Sparse bit limit + pub sparse_bit_limit: u16, + + /// Poker limit max + pub poker_limit_max: u32, + + /// Oscillator mode + pub osc_mode: OscMode, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_size: 1024, + entropy_delay: 32_000, + self_test: SelfTest::Enabled, + freq_counter_max: 75_000, + freq_counter_min: 30_000, + monobit_limit_max: 596, + monobit_limit_min: 427, + run_length1_limit_max: 187, + run_length1_limit_min: 57, + run_length2_limit_max: 105, + run_length2_limit_min: 28, + run_length3_limit_max: 97, + run_length3_limit_min: 33, + run_length4_limit_max: 0, + run_length4_limit_min: 0, + run_length5_limit_max: 0, + run_length5_limit_min: 0, + run_length6_limit_max: 0, + run_length6_limit_min: 0, + retry_count: 2, + long_run_limit_max: 32, + sparse_bit_limit: 0, + poker_limit_max: 0, + osc_mode: OscMode::DualOscs, + } + } +} + +/// Enable or disable internal self-tests. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum SelfTest { + /// Disabled. + Disabled, + + /// Enabled. + Enabled, +} + +impl From for bool { + fn from(value: SelfTest) -> Self { + match value { + SelfTest::Disabled => true, + SelfTest::Enabled => false, + } + } +} + +/// Oscillator mode. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum OscMode { + /// Single oscillator using OSC1. + SingleOsc1, + + /// Dual oscillator. + DualOscs, + + /// Single oscillator using OSC2. + SingleOsc2, +} + +impl From for TrngEntCtl { + fn from(value: OscMode) -> Self { + match value { + OscMode::SingleOsc1 => Self::TrngEntCtlSingleOsc1, + OscMode::DualOscs => Self::TrngEntCtlDualOscs, + OscMode::SingleOsc2 => Self::TrngEntCtlSingleOsc2, + } + } +} + +impl<'d> rand_core_06::RngCore for Trng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d> rand_core_06::CryptoRng for Trng<'d> {} + +impl<'d> rand_core_09::RngCore for Trng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } +} + +impl<'d> rand_core_09::CryptoRng for Trng<'d> {} + +impl<'d> rand_core_06::RngCore for AsyncTrng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d> rand_core_06::CryptoRng for AsyncTrng<'d> {} + +impl<'d> rand_core_09::RngCore for AsyncTrng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } +} + +impl<'d> rand_core_09::CryptoRng for AsyncTrng<'d> {} diff --git a/examples/mcxa/Cargo.toml b/examples/mcxa/Cargo.toml index d07cc4272..347659f5b 100644 --- a/examples/mcxa/Cargo.toml +++ b/examples/mcxa/Cargo.toml @@ -21,6 +21,7 @@ embassy-time-driver = "0.2.1" embedded-io-async = "0.6.1" heapless = "0.9.2" panic-probe = { version = "1.0", features = ["print-defmt"] } +rand_core = "0.9" static_cell = "2.1.1" tmp108 = "0.4.0" diff --git a/examples/mcxa/src/bin/trng.rs b/examples/mcxa/src/bin/trng.rs new file mode 100644 index 000000000..95b4d0a88 --- /dev/null +++ b/examples/mcxa/src/bin/trng.rs @@ -0,0 +1,88 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use hal::bind_interrupts; +use hal::config::Config; +use hal::trng::{self, AsyncTrng, InterruptHandler, Trng}; +use rand_core::RngCore; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +bind_interrupts!( + struct Irqs { + TRNG0 => InterruptHandler; + } +); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let mut p = hal::init(config); + + defmt::info!("TRNG example"); + + let config = trng::Config::default(); + let mut trng = Trng::new(p.TRNG0.reborrow(), config); + + defmt::info!("========== BLOCKING =========="); + + defmt::info!("Generate 10 u32"); + for _ in 0..10 { + let rand = trng.blocking_next_u32(); + defmt::info!("{}", rand); + } + + defmt::info!("Generate 10 u64"); + for _ in 0..10 { + let rand = trng.blocking_next_u64(); + defmt::info!("{}", rand); + } + + let mut buf = [0_u8; 256]; + + defmt::info!("Generate 10 256-byte buffers"); + for _ in 0..10 { + trng.blocking_fill_bytes(&mut buf); + defmt::info!("{:02x}", buf); + } + + defmt::info!("RngCore"); + + for _ in 0..10 { + defmt::info!("u32: {}", trng.next_u32()); + defmt::info!("u64: {}", trng.next_u64()); + } + + drop(trng); + + defmt::info!("========== ASYNC =========="); + + let mut trng = AsyncTrng::new(p.TRNG0.reborrow(), Irqs, config); + + defmt::info!("Generate 10 u32"); + for _ in 0..10 { + let rand = trng.async_next_u32().await.unwrap(); + defmt::info!("{}", rand); + } + + defmt::info!("Generate 10 u64"); + for _ in 0..10 { + let rand = trng.async_next_u64().await.unwrap(); + defmt::info!("{}", rand); + } + + let mut buf = [0_u8; 256]; + + defmt::info!("Generate 10 256-byte buffers"); + for _ in 0..10 { + trng.async_fill_bytes(&mut buf).await.unwrap(); + defmt::info!("{:02x}", buf); + } + + defmt::info!("RngCore"); + + for _ in 0..10 { + defmt::info!("u32: {}", trng.next_u32()); + defmt::info!("u64: {}", trng.next_u64()); + } +} -- cgit