diff --git a/Cargo.toml b/Cargo.toml index 816036ea181eecd8de9628a1de38c3652c56a427..25be236c6780f16ad0b05a79d017d8cac92277a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,12 @@ log = { version = "0.4", optional = true } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +# TODO: make a feature +defmt = "0.3" +defmt-rtt = "0.3" + +[dev-dependencies] + [features] default = [ "log" ] diff --git a/src/cap.rs b/src/cap.rs new file mode 100644 index 0000000000000000000000000000000000000000..88fd0aaa679032a7613567bb710ca4b89b060f60 --- /dev/null +++ b/src/cap.rs @@ -0,0 +1,162 @@ +use defmt::{debug, info, panic}; + +use cortex_m::peripheral::SYST; +use embassy_rp::gpio::{Flex, Pin, Pull}; +use embassy_rp::pac; + +/// _ still pullup, =up_x +/// t3 t4 t1 t2 / +/// |prev |pulldn |still | pullup| / +/// |iter |active |pulldn,| active| / | pulldn active, etc +/// | |=t_down|=down_x| =t_up | o | measuring next t_down, etc... +/// | . | | | | +/// . . | | | . +/// . . | | | . . +/// . . | | | . . +/// .---------------.---------------.-----------.------ GPIO threshold. Probably isn't really flat. +/// . . . +/// . . +/// . . +/// . +/// The GPIO pin is attached to a capacitor to ground. 0.1uF used for experiment. +/// The GPIO pin is left as input and is driven low/high with pulldown/pullup +/// resistors. The time taken to reach logical low/high is measured. The pulldown/pullup +/// resistor is left on for a further time down_x/up_x, which intends to drive +/// the voltage further so that a useful time can be measured for the next t_up/t_down +/// cycle. +/// The down_x/up_x overshoot cycle times are doubled each iteration and add in the measured time, +/// amplifying noise captured in t_down/t_up measurements. +/// down_x/up_x time is kept within a sensible range with modulo. +/// The random output is t_down and t_up time. + +/* +Experimental values: +0.1uF "monolithic" perhaps from futurlec? +GPIO6 +0.003717 INFO initial pulldown del is 346730 +0.003710 INFO initial pulldown del is 345770 +less than ~13000 overshoot had lots of no-delays + +Altronics R8617 • 0.01uF 50V Y5V 0805 SMD Chip Capacitor PK 10 +GPIO10 +0.001292 INFO initial pulldown del is 43234 +0.001294 INFO initial pulldown del is 43598 + + +Altronics R8629 • 0.047uF 50V X7R 0805 SMD Chip Capacitor PK 10 +GPIO13 +0.002497 INFO initial pulldown del is 193878 +0.002547 INFO initial pulldown del is 200170 +*/ + +/// Extra iterations prior to taking output, so that `overshoot` is nice and noisy. +const WARMUP: usize = 8; + +// Range of cycle counts for overshoot. These values are somewhat dependent on the +// cpu frequency and capacitor values. + +// Lower limit is necessary because we don't want to hit the threshold immediately +// on reading. +const LOW_OVER: u32 = 1000; +// Power of two for faster modulo +const HIGH_OVER: u32 = LOW_OVER + 16384; + +struct Timer<'t> { + syst: &'t mut SYST, + t1: u32, +} + +impl<'t> Timer<'t> { + fn new(syst: &'t mut SYST) -> Self { + syst.clear_current(); + syst.enable_counter(); + Self { + syst, + t1: SYST::get_current(), + } + } + + /// returns the duration, or failure on overflow + fn done(self) -> Result<u32, ()> { + let t2 = SYST::get_current(); + if self.syst.has_wrapped() { + return Err(()); + } + self.syst.disable_counter(); + Ok(self.t1 - t2) + } +} + +// `f()` is called on each output `u32`. +pub fn cap_rand<'d, P: Pin, F>( + pin: &mut Flex<'d, P>, + pin_num: usize, + syst: &mut SYST, + n_out: usize, + mut f: F, +) -> Result<(), ()> +where + F: FnMut(u32), +{ + syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core); + // prescribed sequence + syst.set_reload(10_000_000 - 1); + syst.clear_current(); + syst.enable_counter(); + + // Seemed to reduce noise (for investigating thermal noise vs other interference). + // XXX Somehow disabling ROSC breaks SWD, perhaps signal integrity issues? + // unsafe{ pac::ROSC.ctrl().modify(|s| s.set_enable(pac::rosc::vals::Enable::DISABLE)) }; + + // Not necessary, but output has a cleaner more understandable linear response + // with Schmitt Trigger disabled. + unsafe { + pac::PADS_BANK0 + .gpio(pin_num) + .modify(|s| s.set_schmitt(false)) + }; + + // Measure pulldown time from vcc as a sanity check + pin.set_as_output(); + pin.set_high(); + // Long enough to drive the capacitor high + cortex_m::asm::delay(10000); + pin.set_as_input(); + pin.set_pull(Pull::Down); + syst.clear_current(); + let t = Timer::new(syst); + // Get it near the threshold to begin. + while pin.is_high() {} + let del = t.done()?; + info!("initial pulldown del is {}", del); + + // The main loop + let mut overshoot = 1u32; + let n_iter = WARMUP + 2 * n_out; + for i in 0..n_iter { + // Pull up until hit logical high + pin.set_pull(Pull::Up); + while pin.is_low() {} + // Keep pulling up for `overshoot` cycles + cortex_m::asm::delay(overshoot); + + // Pull down, time how long to reach threshold + pin.set_pull(Pull::Down); + let t = Timer::new(syst); + while pin.is_high() {} + let meas = t.done()?; + + if i > WARMUP && (i - WARMUP) % 2 == 1 { + // produce output, don't use internally + f(meas) + } else { + // don't produce output, mix measured in + overshoot = overshoot * 2 + meas; + // modulo to sensible range + if overshoot > HIGH_OVER { + overshoot = LOW_OVER + (overshoot % (HIGH_OVER - LOW_OVER + 1)) + } + } + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 5bf73b7b508816a029c075f24cfb0bb186f80537..526656999122d012609921e29cd215a17b27f039 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,127 +1,9 @@ -use core::num::NonZeroU32; -use core::cell::RefCell; -use core::ops::DerefMut; - #[cfg(feature="log")] use log::error; -use embassy_rp::gpio::{Flex, Pull, Pin}; -use cortex_m::peripheral::SYST; - -use critical_section::Mutex; -use sha2::{Sha256, Digest}; -use rand_chacha::ChaCha20Rng; - -use rand_chacha::rand_core::{RngCore, SeedableRng}; - -const CHARGE_CYCLES: u32 = 600; -// 1 bit per iteration is a rough assumption, give a factor of 4 leeway -const NUM_LOOPS: usize = 1024; -const MIN_DEL: u32 = 300_000; -// Assuming at least a 125mhz clock. A slower clock could possibly be used, though -// we are assuming that the clock is fast enough to pick up variations due to -// thermal noise. -// Other constants are also assuming a 125mhz clock. -const MIN_10MS_CYCLES: u32 = 1_250_000; - -// arbitrary constant -const CAPRAND_ERR: u32 = getrandom::Error::CUSTOM_START + 510132368; - -pub fn error() -> getrandom::Error { - // OK unwrap: const value is nonzero - let c: NonZeroU32 = CAPRAND_ERR.try_into().unwrap(); - c.into() -} - - -static RNG: Mutex<RefCell<Option<CapRng>>> = Mutex::new(RefCell::new(None)); - -pub fn random(buf: &mut [u8]) -> Result<(), getrandom::Error> { - critical_section::with(|cs| { - let mut rng = RNG.borrow_ref_mut(cs); - let rng = rng.deref_mut(); - if let Some(rng) = rng { - rng.0.fill_bytes(buf); - Ok(()) - } else { - error!("setup() not called"); - Err(error()) - } - }) -} - -pub(crate) fn setup<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<(), getrandom::Error> { - let r = CapRng::new(pin, syst)?; - - critical_section::with(|cs| { - let mut rng = RNG.borrow_ref_mut(cs); - rng.insert(r); - }); - Ok(()) -} - -// TODO: this is another impl of chacha20, can it use chacha20 crate instead? Is the size much? -// TODO: have some kind of fast erasure RNG instead? -struct CapRng(ChaCha20Rng); - -impl CapRng { - /// Disables interrupts, will block. Call this at startup before any time-sensitive code - /// `syst` will be modified. - fn new<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<Self, getrandom::Error> { - let mut h = Sha256::new(); - for _ in 0..NUM_LOOPS { - let del = Self::sample(pin, syst)?; - h.update(del.to_be_bytes()); - } - let seed: [u8; 32] = h.finalize().into(); - Ok(Self(ChaCha20Rng::from_seed(seed))) - } - - /// Returns individual samples from the capacitor discharge. Not normally used, - /// can be used to analyse the random number generation. - pub fn sample<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<u32, getrandom::Error> { - syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core); - if SYST::get_ticks_per_10ms() != MIN_10MS_CYCLES { - error!("System clock not 125mhz"); - return Err(error()) - } - - // This sequence is necessary - syst.set_reload(0x00ffffff); - syst.clear_current(); - syst.enable_counter(); - - pin.set_pull(Pull::Down); - pin.set_as_output(); - pin.set_high(); - // allow capacitor to charge - cortex_m::asm::delay(CHARGE_CYCLES); - - let (t1, t2, wr) = critical_section::with(|_cs| { - syst.clear_current(); - let t1 = SYST::get_current(); - // allow to drain through pull down - pin.set_as_input(); - while pin.is_high() {} - - let t2 = SYST::get_current(); - let wr = syst.has_wrapped(); - (t1, t2, wr) - }); - // Don't leave enabled after completion. - syst.enable_counter(); +mod rng; +mod cap; - if wr { - error!("Timer wrapper, capacitor too large?"); - return Err(error()) - } +pub use rng::{setup, random}; +pub use cap::cap_rand; - let del = t1 - t2; - if del > MIN_DEL { - Ok(del) - } else { - error!("Capacitor seems too small?"); - Err(error()) - } - } -} diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed8f9fc64e7ee21c9cea1946d351ca1017418e1c --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,85 @@ +#[cfg(feature = "log")] +use log::error; + +use defmt::{debug, info, warn, panic}; + +use core::cell::RefCell; +use core::num::NonZeroU32; +use core::ops::DerefMut; + +use critical_section::Mutex; +use rand_chacha::ChaCha20Rng; +use sha2::{Digest, Sha256}; + +use cortex_m::peripheral::SYST; +use embassy_rp::gpio::{Flex, Pin}; + +use rand_chacha::rand_core::{RngCore, SeedableRng}; + +// arbitrary constant +pub const CAPRAND_ERR: u32 = getrandom::Error::CUSTOM_START + 510132368; + +pub fn error() -> getrandom::Error { + // OK unwrap: const value is nonzero + let c: NonZeroU32 = CAPRAND_ERR.try_into().unwrap(); + c.into() +} + +static RNG: Mutex<RefCell<Option<CapRng>>> = Mutex::new(RefCell::new(None)); + +pub fn random(buf: &mut [u8]) -> Result<(), getrandom::Error> { + critical_section::with(|cs| { + let mut rng = RNG.borrow_ref_mut(cs); + let rng = rng.deref_mut(); + if let Some(rng) = rng { + rng.0.fill_bytes(buf); + Ok(()) + } else { + error!("setup() not called"); + Err(error()) + } + }) +} + +/// Call this at early startup. If noisy interrupts or time slicing is happening the caller +/// should disable interrupts. +/// `syst` will be modified. +pub fn setup<P: Pin>( + pin: &mut Flex<P>, + pin_num: usize, + syst: &mut SYST, +) -> Result<(), getrandom::Error> { + let r = CapRng::new(pin, pin_num, syst)?; + + critical_section::with(|cs| { + let mut rng = RNG.borrow_ref_mut(cs); + let _ = rng.insert(r); + }); + Ok(()) +} + +// TODO: this is another impl of chacha20, can it use chacha20 crate instead? Is the size much? +// TODO: have some kind of fast erasure RNG instead? +struct CapRng(ChaCha20Rng); + + +impl CapRng { + /// Call this at early startup. If noisy interrupts or time slicing is happening the caller + /// should disable interrupts. + /// `syst` will be modified. + fn new<P: Pin>( + pin: &mut Flex<P>, + pin_num: usize, + syst: &mut SYST, + ) -> Result<Self, getrandom::Error> { + let mut h = Sha256::new(); + crate::cap::jumble(pin, pin_num, syst, 256 * 4, |v| h.update(v.to_be_bytes())).map_err( + |_| { + warn!("Random generation failed"); + error() + }, + )?; + let seed: [u8; 32] = h.finalize().into(); + Ok(Self(ChaCha20Rng::from_seed(seed))) + } +}