stm32f4_prng/
lib.rs

1#![no_std]
2
3use rand::RngCore;
4use stm32f4xx_hal::{
5    stm32::ADC1,
6    adc::{Adc, Temperature, Vref, config::Resolution},
7    signature::{Uid, VrefCal, VtempCal30},
8};
9use rand_chacha::{
10    ChaCha8Rng,
11    rand_core::SeedableRng,
12};
13pub use stm32f4xx_hal::adc::config::SampleTime;
14
15/// The sources of entropy to use
16#[derive(Debug, Copy, Clone)]
17pub enum EntropySources {
18    TempOnly(SampleTime),
19    VrefOnly(SampleTime),
20    DevInfoAndTemp(SampleTime),
21    DevInfoAndVref(SampleTime),
22    TempAndVref(SampleTime),
23    AllSources(SampleTime),
24}
25
26/// Configuration values for seeding the PRNG
27#[derive(Debug, Copy, Clone)]
28pub struct RngConfig {
29    /// Minimum number of randomly generated words to discard
30    ///
31    /// Note: If this is less than 1024, 1024 numbers will be discarded
32    pub min_init_cycles: u32,
33
34    /// Maximum number of randomly generated words to discard
35    ///
36    /// Note: This must be greater than `min_init_cycles`, or the
37    /// values will be swapped
38    pub max_init_cycles: u32,
39
40    /// The sources to use for runtime entropy gathering for the initial
41    /// seed value. Options include:
42    ///
43    /// * Device Info, such as serial number and die position
44    /// * Internal Temperature Sensor
45    /// * Internal VRef measurement
46    pub entropy_sources: EntropySources,
47}
48
49impl Default for RngConfig {
50    fn default() -> Self {
51        RngConfig {
52            min_init_cycles: 8192,
53            max_init_cycles: 16384,
54            entropy_sources: EntropySources::AllSources(SampleTime::Cycles_480),
55        }
56    }
57}
58
59// Fill the device info into 16 bytes
60fn fill_device_info(slice: &mut [u8; 16]) {
61    let uid = Uid::get();
62    let vtc30 = VtempCal30::get();
63    let vrc = VrefCal::get();
64
65    // 7 bytes (7/16)
66    let lot = uid.lot_num().as_bytes();
67    slice[..7].copy_from_slice(lot);
68
69    // 1 byte (8/16)
70    let waf = uid.waf_num();
71    slice[7] = waf;
72
73    // 2 bytes (10/16)
74    let xpos = uid.x().to_ne_bytes();
75    slice[8..10].copy_from_slice(&xpos);
76
77    // 2 bytes (12/16)
78    let ypos = uid.y().to_ne_bytes();
79    slice[10..12].copy_from_slice(&ypos);
80
81    // 2 bytes (14/16)
82    let cal30 = vtc30.read().to_ne_bytes();
83    slice[12..14].copy_from_slice(&cal30);
84
85    // 2 bytes (16/16)
86    let vrcal = vrc.read().to_ne_bytes();
87    slice[14..16].copy_from_slice(&vrcal);
88}
89
90enum Source {
91    Temp,
92    Vref,
93}
94
95fn fill_adc_readings(adc: &mut Adc<ADC1>, source: Source, samples: SampleTime, bytes: &mut [u8]) {
96    // TODO: Ensure all ADC reading aren't exactly the same?
97
98    for byte in bytes.iter_mut() {
99        // take the LSB of the 12 bit ADC reading for maximum noise
100        *byte = match source {
101            Source::Temp => adc.convert(&Temperature, samples),
102            Source::Vref => adc.convert(&Vref, samples),
103        } as u8;
104    }
105}
106
107/// Create a new RNG, seeded with ADC data
108pub fn seed_rng(adc: &mut Adc<ADC1>, config: RngConfig) -> ChaCha8Rng {
109    adc.enable_temperature_and_vref();
110    adc.set_resolution(Resolution::Twelve);
111
112    let mut key = [0u8; 32];
113    let mut bytes = [0u8; 16];
114
115    match config.entropy_sources {
116        EntropySources::TempOnly(sample_time) => {
117            fill_adc_readings(adc, Source::Temp, sample_time, &mut key);
118        }
119        EntropySources::VrefOnly(sample_time) => {
120            fill_adc_readings(adc, Source::Vref, sample_time, &mut key);
121        }
122        EntropySources::DevInfoAndTemp(sample_time) => {
123            fill_device_info(&mut bytes);
124            key[..16].copy_from_slice(&bytes);
125            fill_adc_readings(adc, Source::Temp, sample_time, &mut key[16..]);
126        }
127        EntropySources::DevInfoAndVref(sample_time) => {
128            fill_device_info(&mut bytes);
129            key[..16].copy_from_slice(&bytes);
130            fill_adc_readings(adc, Source::Vref, sample_time, &mut key[16..]);
131        }
132        EntropySources::TempAndVref(sample_time) => {
133            fill_adc_readings(adc, Source::Temp, sample_time, &mut key[..16]);
134            fill_adc_readings(adc, Source::Vref, sample_time, &mut key[16..]);
135        }
136        EntropySources::AllSources(sample_time) => {
137            fill_device_info(&mut bytes);
138            key[..16].copy_from_slice(&bytes);
139            fill_adc_readings(adc, Source::Temp, sample_time, &mut key[16..24]);
140            fill_adc_readings(adc, Source::Vref, sample_time, &mut key[24..]);
141        }
142    }
143
144    let mut rng = ChaCha8Rng::from_seed(key);
145
146    // Ensure we have min and max in the right order, and that we
147    // have SOME amount of random entropy
148    let min = config.min_init_cycles.min(config.max_init_cycles);
149    let max = config.min_init_cycles.max(config.max_init_cycles);
150    let end = (max - min).min(1024);
151
152    let nop_cycles = min + (rng.next_u32() % end);
153
154    for _ in 0..nop_cycles {
155        let _ = rng.next_u32();
156    }
157
158    rng
159}