diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index eeba1f54..7b8e3641 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 f3ceb98e..2b02c1af 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 dae26054..4c93d504 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 6f9edff3..f840214f 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 8bc50b6d..180861f7 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 6f3f7fc7..1c38a775 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 a0240b19..bcb0fffc 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 d4dcfd06..ee8b5a89 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 f3952159..c2e461cf 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 00000000..40778c64 --- /dev/null +++ b/embassy-nrf/src/rng.rs @@ -0,0 +1,154 @@ +use core::convert::Infallible; +use core::marker::PhantomData; + +use embassy::interrupt::InterruptExt; +use embassy::traits; +use embassy::util::OnDrop; +use embassy::util::Signal; +use embassy::util::Unborrow; +use embassy_extras::unborrow; +use futures::Future; +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 NEXT_BYTE: Signal = Signal::new(); + +/// 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>, +} + +impl<'d> Rng<'d> { + pub 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 ()) { + NEXT_BYTE.signal(RNG::regs().value.read().value().bits()); + RNG::regs().events_valrdy.reset(); + } + + 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> { + self.enable_irq(); + self.start(); + + async move { + let on_drop = OnDrop::new(|| { + self.stop(); + self.disable_irq(); + }); + + for byte in dest.iter_mut() { + *byte = NEXT_BYTE.wait().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 fa1dab36..8db0ad53 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 00000000..a4c204c2 --- /dev/null +++ b/examples/nrf/src/bin/rng.rs @@ -0,0 +1,30 @@ +#![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_nrf::Peripherals; +use embassy_nrf::rng::Rng; +use embassy_nrf::interrupt; +use embassy::traits::rng::Rng as _; +use rand::Rng as _; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let mut rng = 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)); +}