Skip to main content

ws63_hal/
trng.rs

1//! True Random Number Generator (TRNG) driver for WS63.
2//!
3//! The WS63 TRNG generates true random numbers using physical entropy
4//! sources (FRO — Free-Running Oscillator). Random data is read from
5//! a FIFO register.
6//!
7//! # Usage
8//!
9//! ```ignore
10//! let trng = Trng::new(peripherals.TRNG);
11//! let random_word: u32 = trng.read_blocking().unwrap();
12//! ```
13
14use crate::peripherals::Trng;
15
16/// TRNG error type.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum TrngError {
19    /// No data available in the FIFO.
20    NoData,
21    /// Timeout waiting for entropy generation.
22    Timeout,
23}
24
25/// True Random Number Generator driver.
26pub struct TrngDriver<'d> {
27    _trng: Trng<'d>,
28}
29
30impl<'d> TrngDriver<'d> {
31    /// Create a new TRNG driver.
32    pub fn new(trng: Trng<'d>) -> Self {
33        Self { _trng: trng }
34    }
35
36    fn regs(&self) -> &'static ws63_pac::trng::RegisterBlock {
37        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid
38        unsafe { &*Trng::ptr() }
39    }
40
41    /// Check if random data is ready in the FIFO.
42    pub fn data_ready(&self) -> bool {
43        self.regs().trng_fifo_ready().read().bits() & 0x01 != 0
44    }
45
46    /// Check if the TRNG generation is complete.
47    pub fn done(&self) -> bool {
48        self.regs().trng_fifo_ready().read().bits() & 0x02 != 0
49    }
50
51    /// Read a 32-bit random word from the TRNG FIFO.
52    ///
53    /// Returns `Err(NoData)` if no data is available.
54    pub fn read(&self) -> Result<u32, TrngError> {
55        if !self.data_ready() {
56            return Err(TrngError::NoData);
57        }
58        Ok(self.regs().trng_fifo_data().read().bits())
59    }
60
61    /// Read a random word, blocking until data is available.
62    ///
63    /// Returns `Err(Timeout)` if the TRNG fails to produce entropy after
64    /// ~4ms (1,000,000 spin-loop iterations at 240MHz). On cold start,
65    /// the FRO-based entropy source may need several attempts to stabilize;
66    /// retry the call if the first attempt times out.
67    pub fn read_blocking(&self) -> Result<u32, TrngError> {
68        for _ in 0..1_000_000 {
69            if self.data_ready() {
70                return Ok(self.regs().trng_fifo_data().read().bits());
71            }
72            core::hint::spin_loop();
73        }
74        Err(TrngError::Timeout)
75    }
76
77    /// Fill a buffer with random bytes.
78    ///
79    /// Each 32-bit word produces 4 bytes. Returns `Err(Timeout)` if the TRNG
80    /// hardware fails to produce entropy.
81    pub fn fill_bytes(&self, buf: &mut [u8]) -> Result<(), TrngError> {
82        let mut i = 0;
83        while i < buf.len() {
84            let word = self.read_blocking()?;
85            let bytes = word.to_le_bytes();
86            for &b in &bytes {
87                if i < buf.len() {
88                    buf[i] = b;
89                    i += 1;
90                }
91            }
92        }
93        Ok(())
94    }
95
96    /// Fill a buffer with random 32-bit words.
97    ///
98    /// Returns `Err(Timeout)` if the TRNG hardware fails to produce entropy.
99    pub fn fill_words(&self, buf: &mut [u32]) -> Result<(), TrngError> {
100        for word in buf.iter_mut() {
101            *word = self.read_blocking()?;
102        }
103        Ok(())
104    }
105
106    /// Select the FRO sample clock source.
107    ///
108    /// * `external` — `true` for external clock, `false` for internal clock.
109    pub fn set_sample_clock(&mut self, external: bool) {
110        unsafe {
111            self.regs().trng_fro_sample_clk_sel().write(|w| w.bits(if external { 1 } else { 0 }));
112        }
113    }
114
115    /// Set the FRO divider count.
116    ///
117    /// Controls the sampling rate of the FRO entropy source.
118    /// Default is 0x1b (27).
119    pub fn set_divider(&mut self, div: u8) {
120        unsafe {
121            self.regs().trng_fro_div_cnt().write(|w| w.bits(div as u32));
122        }
123    }
124
125    /// Get the data status register value (for debugging).
126    pub fn data_status(&self) -> u32 {
127        self.regs().trng_data_st().read().bits()
128    }
129}
130
131// ── Tests ──────────────────────────────────────────────────────
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_trng_error_type_variants() {
139        assert_ne!(TrngError::NoData as u8, TrngError::Timeout as u8);
140    }
141
142    #[test]
143    fn test_trng_data_ready_bit() {
144        // data_ready checks fifo_ready bit 0
145        let ready: u32 = 0x01;
146        assert!((ready & 0x01) != 0); // data ready
147        let not_ready: u32 = 0x00;
148        assert!((not_ready & 0x01) == 0); // not ready
149    }
150
151    #[test]
152    fn test_trng_done_bit() {
153        // done checks fifo_ready bit 1
154        let done: u32 = 0x02;
155        assert!((done & 0x02) != 0); // generation done
156        let not_done: u32 = 0x00;
157        assert!((not_done & 0x02) == 0); // not done
158    }
159
160    #[test]
161    fn test_trng_read_blocking_timeout_logic() {
162        // Simulate the timeout loop: should return Err after retries exhausted
163        let max_retries = 10u32;
164        let mut data_ready = false;
165        let mut retries = 0;
166        let result = loop {
167            if data_ready {
168                break Ok(42u32);
169            }
170            if retries >= max_retries {
171                break Err(TrngError::Timeout);
172            }
173            retries += 1;
174        };
175        assert_eq!(result, Err(TrngError::Timeout));
176        assert_eq!(retries, 10);
177    }
178
179    #[test]
180    fn test_trng_read_blocking_success_first_try() {
181        let data_ready = true;
182        let result = if data_ready { Ok(0xDEAD_BEEFu32) } else { Err(TrngError::Timeout) };
183        assert_eq!(result.unwrap(), 0xDEAD_BEEF);
184    }
185}