diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index eeba1f54e..7b8e36414 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -33,6 +33,7 @@ embedded-hal = { version = "0.2.4" } embedded-dma = { version = "0.1.2" } futures = { version = "0.3.5", default-features = false } critical-section = "0.2.1" +rand_core = "0.6.3" nrf52805-pac = { version = "0.1.0", optional = true, features = [ "rt" ]} nrf52810-pac = { version = "0.9.0", optional = true, features = [ "rt" ]} diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index f3ceb98e4..2b02c1afe 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -8,6 +8,9 @@ embassy_extras::peripherals! { RTC0, RTC1, + // RNG + RNG, + // UARTE UARTE0, diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index dae260545..4c93d5046 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -8,6 +8,9 @@ embassy_extras::peripherals! { RTC0, RTC1, + // RNG + RNG, + // UARTE UARTE0, diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 6f9edff35..f840214fa 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -8,6 +8,9 @@ embassy_extras::peripherals! { RTC0, RTC1, + // RNG + RNG, + // UARTE UARTE0, diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 8bc50b6d3..180861f71 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -8,6 +8,9 @@ embassy_extras::peripherals! { RTC0, RTC1, + // RNG + RNG, + // UARTE UARTE0, diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 6f3f7fc7b..1c38a7751 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -9,6 +9,9 @@ embassy_extras::peripherals! { RTC1, RTC2, + // RNG + RNG, + // UARTE UARTE0, diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index a0240b196..bcb0fffc0 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -9,6 +9,9 @@ embassy_extras::peripherals! { RTC1, RTC2, + // RNG + RNG, + // UARTE UARTE0, UARTE1, diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index d4dcfd063..ee8b5a89c 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -9,6 +9,9 @@ embassy_extras::peripherals! { RTC1, RTC2, + // RNG + RNG, + // QSPI QSPI, diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index f39521594..c2e461cf1 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -33,6 +33,7 @@ pub mod ppi; pub mod pwm; #[cfg(feature = "nrf52840")] pub mod qspi; +pub mod rng; pub mod rtc; #[cfg(not(feature = "nrf52820"))] pub mod saadc; diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs new file mode 100644 index 000000000..a444c9b3f --- /dev/null +++ b/embassy-nrf/src/rng.rs @@ -0,0 +1,244 @@ +use core::convert::Infallible; +use core::future::Future; +use core::marker::PhantomData; +use core::ptr; +use core::sync::atomic::AtomicPtr; +use core::sync::atomic::Ordering; +use core::task::Poll; + +use embassy::interrupt::InterruptExt; +use embassy::traits; +use embassy::util::AtomicWaker; +use embassy::util::OnDrop; +use embassy::util::Unborrow; +use embassy_extras::unborrow; +use futures::future::poll_fn; +use rand_core::RngCore; + +use crate::interrupt; +use crate::pac; +use crate::peripherals::RNG; + +impl RNG { + fn regs() -> &'static pac::rng::RegisterBlock { + unsafe { &*pac::RNG::ptr() } + } +} + +static STATE: State = State { + ptr: AtomicPtr::new(ptr::null_mut()), + end: AtomicPtr::new(ptr::null_mut()), + waker: AtomicWaker::new(), +}; + +struct State { + ptr: AtomicPtr, + end: AtomicPtr, + waker: AtomicWaker, +} + +/// A wrapper around an nRF RNG peripheral. +/// +/// It has a non-blocking API, through `embassy::traits::Rng`, and a blocking api through `rand`. +pub struct Rng<'d> { + irq: interrupt::RNG, + phantom: PhantomData<(&'d mut RNG, &'d mut interrupt::RNG)>, +} + +impl<'d> Rng<'d> { + /// Creates a new RNG driver from the `RNG` peripheral and interrupt. + /// + /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, + /// e.g. using `mem::forget`. + /// + /// The synchronous API is safe. + pub unsafe fn new( + _rng: impl Unborrow + 'd, + irq: impl Unborrow + 'd, + ) -> Self { + unborrow!(irq); + + let this = Self { + irq, + phantom: PhantomData, + }; + + this.stop(); + this.disable_irq(); + + this.irq.set_handler(Self::on_interrupt); + this.irq.unpend(); + this.irq.enable(); + + this + } + + fn on_interrupt(_: *mut ()) { + // Clear the event. + RNG::regs().events_valrdy.reset(); + + // Mutate the slice within a critical section, + // so that the future isn't dropped in between us loading the pointer and actually dereferencing it. + let (ptr, end) = critical_section::with(|_| { + let ptr = STATE.ptr.load(Ordering::Relaxed); + // We need to make sure we haven't already filled the whole slice, + // in case the interrupt fired again before the executor got back to the future. + let end = STATE.end.load(Ordering::Relaxed); + if !ptr.is_null() && ptr != end { + // If the future was dropped, the pointer would have been set to null, + // so we're still good to mutate the slice. + // The safety contract of `Rng::new` means that the future can't have been dropped + // without calling its destructor. + unsafe { + *ptr = RNG::regs().value.read().value().bits(); + } + } + (ptr, end) + }); + + if ptr.is_null() || ptr == end { + // If the future was dropped, there's nothing to do. + // If `ptr == end`, we were called by mistake, so return. + return; + } + + let new_ptr = unsafe { ptr.add(1) }; + match STATE + .ptr + .compare_exchange(ptr, new_ptr, Ordering::Relaxed, Ordering::Relaxed) + { + Ok(_) => { + let end = STATE.end.load(Ordering::Relaxed); + // It doesn't matter if `end` was changed under our feet, because then this will just be false. + if new_ptr == end { + STATE.waker.wake(); + } + } + Err(_) => { + // If the future was dropped or finished, there's no point trying to wake it. + // It will have already stopped the RNG, so there's no need to do that either. + } + } + } + + fn stop(&self) { + RNG::regs().tasks_stop.write(|w| unsafe { w.bits(1) }) + } + + fn start(&self) { + RNG::regs().tasks_start.write(|w| unsafe { w.bits(1) }) + } + + fn enable_irq(&self) { + RNG::regs().intenset.write(|w| w.valrdy().set()); + } + + fn disable_irq(&self) { + RNG::regs().intenclr.write(|w| w.valrdy().clear()); + } + + /// Enable or disable the RNG's bias correction. + /// + /// Bias correction removes any bias towards a '1' or a '0' in the bits generated. + /// However, this makes the generation of numbers slower. + /// + /// Defaults to disabled. + pub fn bias_correction(&self, enable: bool) { + RNG::regs().config.write(|w| w.dercen().bit(enable)) + } +} + +impl<'d> Drop for Rng<'d> { + fn drop(&mut self) { + self.irq.disable() + } +} + +impl<'d> traits::rng::Rng for Rng<'d> { + type Error = Infallible; + + #[rustfmt::skip] // For some reason rustfmt removes the where clause + type RngFuture<'a> where 'd: 'a = impl Future> + 'a; + + fn fill_bytes<'a>(&'a mut self, dest: &'a mut [u8]) -> Self::RngFuture<'a> { + async move { + if dest.len() == 0 { + return Ok(()); // Nothing to fill + } + + let range = dest.as_mut_ptr_range(); + // Even if we've preempted the interrupt, it can't preempt us again, + // so we don't need to worry about the order we write these in. + STATE.ptr.store(range.start, Ordering::Relaxed); + STATE.end.store(range.end, Ordering::Relaxed); + + self.enable_irq(); + self.start(); + + let on_drop = OnDrop::new(|| { + self.stop(); + self.disable_irq(); + + // The interrupt is now disabled and can't preempt us anymore, so the order doesn't matter here. + STATE.ptr.store(ptr::null_mut(), Ordering::Relaxed); + STATE.end.store(ptr::null_mut(), Ordering::Relaxed); + }); + + poll_fn(|cx| { + STATE.waker.register(cx.waker()); + + // The interrupt will never modify `end`, so load it first and then get the most up-to-date `ptr`. + let end = STATE.end.load(Ordering::Relaxed); + let ptr = STATE.ptr.load(Ordering::Relaxed); + + if ptr == end { + // We're done. + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Trigger the teardown + drop(on_drop); + + Ok(()) + } + } +} + +impl<'d> RngCore for Rng<'d> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.start(); + + for byte in dest.iter_mut() { + let regs = RNG::regs(); + while regs.events_valrdy.read().bits() == 0 {} + regs.events_valrdy.reset(); + *byte = regs.value.read().value().bits(); + } + + self.stop(); + } + + fn next_u32(&mut self) -> u32 { + let mut bytes = [0; 4]; + self.fill_bytes(&mut bytes); + // We don't care about the endianness, so just use the native one. + u32::from_ne_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.fill_bytes(&mut bytes); + u64::from_ne_bytes(bytes) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +// TODO: Should `Rng` implement `CryptoRng`? It's 'suitable for cryptographic purposes' according to the specification. diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index fa1dab360..8db0ad53f 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -29,3 +29,4 @@ cortex-m-rt = "0.6.13" embedded-hal = { version = "0.2.4" } panic-probe = { version = "0.2.0", features = ["print-defmt"] } futures = { version = "0.3.8", default-features = false, features = ["async-await"] } +rand = { version = "0.8.4", default-features = false } diff --git a/examples/nrf/src/bin/rng.rs b/examples/nrf/src/bin/rng.rs new file mode 100644 index 000000000..6aa43d0db --- /dev/null +++ b/examples/nrf/src/bin/rng.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; + +use defmt::panic; +use embassy::executor::Spawner; +use embassy::traits::rng::Rng as _; +use embassy_nrf::interrupt; +use embassy_nrf::rng::Rng; +use embassy_nrf::Peripherals; +use rand::Rng as _; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let mut rng = unsafe { Rng::new(p.RNG, interrupt::take!(RNG)) }; + + // Async API + let mut bytes = [0; 4]; + rng.fill_bytes(&mut bytes).await.unwrap(); // nRF RNG is infallible + defmt::info!("Some random bytes: {:?}", bytes); + + // Sync API with `rand` + defmt::info!("A random number from 1 to 10: {:?}", rng.gen_range(1..=10)); + + let mut bytes = [0; 1024]; + rng.fill_bytes(&mut bytes).await.unwrap(); + let zero_count: u32 = bytes.iter().fold(0, |acc, val| acc + val.count_zeros()); + let one_count: u32 = bytes.iter().fold(0, |acc, val| acc + val.count_ones()); + defmt::info!( + "Chance of zero: {}%", + zero_count * 100 / (bytes.len() as u32 * 8) + ); + defmt::info!( + "Chance of one: {}%", + one_count * 100 / (bytes.len() as u32 * 8) + ); +}