From a29a793a01ca92b3b30e7dfbac0f8bc47f6adfb2 Mon Sep 17 00:00:00 2001 From: Matt Johnston <matt@ucc.asn.au> Date: Mon, 3 Oct 2022 23:46:07 +0800 Subject: [PATCH] Add source code --- .cargo/config.toml | 10 ++++++ Cargo.toml | 31 ++++++++++------ build.rs | 36 +++++++++++++++++++ examples/rand.rs | 40 +++++++++++++++++++++ examples/raw.rs | 30 ++++++++++++++++ memory.x | 5 +++ src/cap.rs | 88 ++++++++++++++++++++++++++++++---------------- src/lib.rs | 2 ++ src/rng.rs | 12 ++++--- 9 files changed, 207 insertions(+), 47 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 build.rs create mode 100644 examples/rand.rs create mode 100644 examples/raw.rs create mode 100644 memory.x diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a095784 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] + runner = "probe-run --chip RP2040" +#runner = "elf2uf2-rs -d" + + +[build] +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ + +[env] +DEFMT_LOG = "trace" diff --git a/Cargo.toml b/Cargo.toml index 25be236..3ea912a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,25 +5,34 @@ edition = "2021" [dependencies] -cortex-m = { version = "0.7", features = ["critical-section-single-core"]} +cortex-m = "0.7" +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +defmt = { version = "0.3", optional = true } -getrandom = { version = "0.2", default-features = false, features = ["custom"]} +getrandom = { version = "0.2", default-features = false } critical-section = "1.1" sha2 = { version = "0.10", default-features = false } -rand_chacha = "0.3" - -log = { version = "0.4", optional = true } - -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +rand_chacha = { version = "0.3", default-features = false } -# TODO: make a feature -defmt = "0.3" -defmt-rtt = "0.3" +log = { version = "0.4", default-features = false } [dev-dependencies] +cortex-m-rt = "0.7.0" +cortex-m = { version = "0.7", features = ["critical-section-single-core"]} +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } +panic-probe = { version = "0.3", features = ["print-defmt"] } +defmt-rtt = { version = "0.3" } +getrandom = { version = "0.2", default-features = false, features = ["custom"]} [features] -default = [ "log" ] +defmt = [ "dep:defmt" ] + +[profile.release] +debug = 2 [patch.crates-io] +# embassy isn't released to crates.io yet embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..afca69b --- /dev/null +++ b/build.rs @@ -0,0 +1,36 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-examples=--nmagic"); + println!("cargo:rustc-link-arg-examples=-Tlink.x"); + println!("cargo:rustc-link-arg-examples=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-examples=-Tdefmt.x"); +} diff --git a/examples/rand.rs b/examples/rand.rs new file mode 100644 index 0000000..f5698a4 --- /dev/null +++ b/examples/rand.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[cfg(not(feature = "defmt"))] +use log::{debug, info, warn, error}; + +#[cfg(feature = "defmt")] +use defmt::{debug, info, warn, panic, error}; +#[cfg(feature = "defmt")] +use {defmt_rtt as _, panic_probe as _}; + +use embassy_rp::gpio::Flex; +use embassy_executor::Spawner; +use embassy_time::{Timer, Duration}; + +use getrandom::register_custom_getrandom; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut cp = cortex_m::peripheral::Peripherals::take().unwrap(); + + let mut gpio = (Flex::new(p.PIN_10), 10); + + caprand::setup(&mut gpio.0, gpio.1, &mut cp.SYST).unwrap(); + + register_custom_getrandom!(caprand::random); + + loop { + let mut mystery = [0u8; 10]; + getrandom::getrandom(mystery.as_mut_slice()).unwrap(); + + info!("mystery bytes!"); + for m in mystery.iter() { + info!("{:x}", *m); + } + Timer::after(Duration::from_millis(333)).await; + } +} diff --git a/examples/raw.rs b/examples/raw.rs new file mode 100644 index 0000000..7478e53 --- /dev/null +++ b/examples/raw.rs @@ -0,0 +1,30 @@ +//! Prints raw samples from the capacitor random number generator. +//! These would not usually be used directly, instead use `CapRng`. +//! Raw samples are useful to analyse the entropy. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[cfg(not(feature = "defmt"))] +use log::{debug, info, warn, error}; + +#[cfg(feature = "defmt")] +use defmt::{debug, info, warn, panic, error}; +#[cfg(feature = "defmt")] +use {defmt_rtt as _, panic_probe as _}; + +use embassy_rp::gpio::Flex; +use embassy_executor::Spawner; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut cp = cortex_m::peripheral::Peripherals::take().unwrap(); + + let mut gpio = (Flex::new(p.PIN_10), 10); + loop { + caprand::cap_rand(&mut gpio.0, gpio.1, &mut cp.SYST, 10_000, + |v| info!("{}", v)).unwrap(); + } +} diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..aba861a --- /dev/null +++ b/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/src/cap.rs b/src/cap.rs index 88fd0aa..4976de2 100644 --- a/src/cap.rs +++ b/src/cap.rs @@ -1,4 +1,8 @@ -use defmt::{debug, info, panic}; +#[cfg(not(feature = "defmt"))] +use log::{debug, info, warn, error}; + +#[cfg(feature = "defmt")] +use defmt::{error, debug, info, panic}; use cortex_m::peripheral::SYST; use embassy_rp::gpio::{Flex, Pin, Pull}; @@ -30,8 +34,8 @@ use embassy_rp::pac; /// The random output is t_down and t_up time. /* -Experimental values: -0.1uF "monolithic" perhaps from futurlec? +Experimental capacitor values: +0.1uF "monolithic" through hole, perhaps from futurlec? GPIO6 0.003717 INFO initial pulldown del is 346730 0.003710 INFO initial pulldown del is 345770 @@ -49,8 +53,6 @@ GPIO13 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. @@ -61,12 +63,22 @@ const LOW_OVER: u32 = 1000; // Power of two for faster modulo const HIGH_OVER: u32 = LOW_OVER + 16384; -struct Timer<'t> { +// Assume worst case from rp2040 datasheet. +// 3.3v vdd, 2v logical high voltage, 50kohm pullup, 0.01uF capacitor, 125Mhz clock. +// 0.5 * 50e3 * 0.01e-6 * 125e6 = 31250.0 +// Then allow 50% leeway for tolerances. +const MIN_CAPACITOR_DEL: u32 = 15000; + +/// Extra iterations prior to taking output, so that `overshoot` is nice and noisy. +const WARMUP: usize = 16; + +/// Wraps timing with SYST. The clock source must already be configured. +struct SyTi<'t> { syst: &'t mut SYST, t1: u32, } -impl<'t> Timer<'t> { +impl<'t> SyTi<'t> { fn new(syst: &'t mut SYST) -> Self { syst.clear_current(); syst.enable_counter(); @@ -80,6 +92,7 @@ impl<'t> Timer<'t> { fn done(self) -> Result<u32, ()> { let t2 = SYST::get_current(); if self.syst.has_wrapped() { + error!("SYST wrapped"); return Err(()); } self.syst.disable_counter(); @@ -99,7 +112,7 @@ where F: FnMut(u32), { syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core); - // prescribed sequence + // prescribed sequence for setup syst.set_reload(10_000_000 - 1); syst.clear_current(); syst.enable_counter(); @@ -108,8 +121,8 @@ where // 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. + // Disabling the Schmitt Trigger gives a clearer correlation between "overshoot" + // and measured values. unsafe { pac::PADS_BANK0 .gpio(pin_num) @@ -122,35 +135,48 @@ where // 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); + let del = critical_section::with(|_cs| { + pin.set_pull(Pull::Down); + let t = SyTi::new(syst); + // Get it near the threshold to begin. + while pin.is_high() {} + t.done() + })?; + info!("Initial pulldown del is {}", del); + + if del < MIN_CAPACITOR_DEL { + error!("Capacitor seems small or missing?"); + return Err(()) + } // The main loop let mut overshoot = 1u32; + + // After warmup we sample twice at each "overshoot" value. + // One sample is returned as random output, the other is mixed + // in to the overshoot value. 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 + let meas = critical_section::with(|_cs| { + 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 = SyTi::new(syst); + while pin.is_high() {} + t.done() + })?; + + if i > WARMUP && (i - WARMUP) % 2 == 0 { + // real output f(meas) } else { - // don't produce output, mix measured in + // don't produce output, mix measured sample in overshoot = overshoot * 2 + meas; // modulo to sensible range if overshoot > HIGH_OVER { diff --git a/src/lib.rs b/src/lib.rs index 5266569..44e5bfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![no_std] + #[cfg(feature="log")] use log::error; diff --git a/src/rng.rs b/src/rng.rs index ed8f9fc..02bab82 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,7 +1,8 @@ -#[cfg(feature = "log")] -use log::error; +#[cfg(not(feature = "defmt"))] +use log::{debug, info, warn, error}; -use defmt::{debug, info, warn, panic}; +#[cfg(feature = "defmt")] +use defmt::{debug, info, warn, panic, error}; use core::cell::RefCell; use core::num::NonZeroU32; @@ -62,8 +63,9 @@ pub fn setup<P: Pin>( // TODO: have some kind of fast erasure RNG instead? struct CapRng(ChaCha20Rng); - impl CapRng { + const SEED_SAMPLES: usize = 1024; + /// Call this at early startup. If noisy interrupts or time slicing is happening the caller /// should disable interrupts. /// `syst` will be modified. @@ -73,7 +75,7 @@ impl CapRng { 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( + crate::cap::cap_rand(pin, pin_num, syst, Self::SEED_SAMPLES, |v| h.update(v.to_be_bytes())).map_err( |_| { warn!("Random generation failed"); error() -- GitLab