mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-21 14:22:33 +00:00
Merge pull request #3338 from ionspin/add-rp2350-trng-support
RP2350 TRNG support
This commit is contained in:
commit
d12e98aaf4
@ -42,6 +42,8 @@ pub mod rtc;
|
||||
pub mod spi;
|
||||
#[cfg(feature = "time-driver")]
|
||||
pub mod time_driver;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub mod trng;
|
||||
pub mod uart;
|
||||
pub mod usb;
|
||||
pub mod watchdog;
|
||||
@ -402,6 +404,8 @@ embassy_hal_internal::peripherals! {
|
||||
|
||||
WATCHDOG,
|
||||
BOOTSEL,
|
||||
|
||||
TRNG
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
|
||||
|
405
embassy-rp/src/trng.rs
Normal file
405
embassy-rp/src/trng.rs
Normal file
@ -0,0 +1,405 @@
|
||||
//! True Random Number Generator (TRNG) driver.
|
||||
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Not;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_internal::Peripheral;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use rand_core::Error;
|
||||
|
||||
use crate::interrupt::typelevel::{Binding, Interrupt};
|
||||
use crate::peripherals::TRNG;
|
||||
use crate::{interrupt, pac};
|
||||
|
||||
trait SealedInstance {
|
||||
fn regs() -> pac::trng::Trng;
|
||||
fn waker() -> &'static AtomicWaker;
|
||||
}
|
||||
|
||||
/// TRNG peripheral instance.
|
||||
#[allow(private_bounds)]
|
||||
pub trait Instance: SealedInstance {
|
||||
/// Interrupt for this peripheral.
|
||||
type Interrupt: Interrupt;
|
||||
}
|
||||
|
||||
impl SealedInstance for TRNG {
|
||||
fn regs() -> rp_pac::trng::Trng {
|
||||
pac::TRNG
|
||||
}
|
||||
|
||||
fn waker() -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance for TRNG {
|
||||
type Interrupt = interrupt::typelevel::TRNG_IRQ;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
/// TRNG ROSC Inverter chain length options.
|
||||
pub enum InverterChainLength {
|
||||
None = 0,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
impl From<InverterChainLength> for u8 {
|
||||
fn from(value: InverterChainLength) -> Self {
|
||||
value as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the TRNG.
|
||||
///
|
||||
/// - Three built in entropy checks
|
||||
/// - ROSC frequency controlled by selecting one of ROSC chain lengths
|
||||
/// - Sample period in terms of system clock ticks
|
||||
///
|
||||
///
|
||||
/// Default configuration is based on the following from documentation:
|
||||
///
|
||||
/// ----
|
||||
///
|
||||
/// RP2350 Datasheet 12.12.2
|
||||
///
|
||||
/// ...
|
||||
///
|
||||
/// When configuring the TRNG block, consider the following principles:
|
||||
/// • As average generation time increases, result quality increases and failed entropy checks decrease.
|
||||
/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and
|
||||
/// failed entropy checks.
|
||||
/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or
|
||||
/// 1 and sample count settings of 20-25.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly
|
||||
/// by setting the sample period to 0. Random data collected this way is then passed through
|
||||
/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!).
|
||||
#[non_exhaustive]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Bypass TRNG autocorrelation test
|
||||
pub disable_autocorrelation_test: bool,
|
||||
/// Bypass CRNGT test
|
||||
pub disable_crngt_test: bool,
|
||||
/// When set, the Von-Neuman balancer is bypassed (including the
|
||||
/// 32 consecutive bits test)
|
||||
pub disable_von_neumann_balancer: bool,
|
||||
/// Sets the number of rng_clk cycles between two consecutive
|
||||
/// ring oscillator samples.
|
||||
/// Note: If the von Neumann decorrelator is bypassed, the minimum value for
|
||||
/// sample counter must not be less than seventeen
|
||||
pub sample_count: u32,
|
||||
/// Selects the number of inverters (out of four possible
|
||||
/// selections) in the ring oscillator (the entropy source). Higher values select
|
||||
/// longer inverter chain lengths.
|
||||
pub inverter_chain_length: InverterChainLength,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
disable_autocorrelation_test: true,
|
||||
disable_crngt_test: true,
|
||||
disable_von_neumann_balancer: true,
|
||||
sample_count: 25,
|
||||
inverter_chain_length: InverterChainLength::One,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// True Random Number Generator Driver for RP2350
|
||||
///
|
||||
/// This driver provides async and blocking options.
|
||||
///
|
||||
/// See [Config] for configuration details.
|
||||
///
|
||||
/// Usage example:
|
||||
/// ```no_run
|
||||
/// use embassy_executor::Spawner;
|
||||
/// use embassy_rp::trng::Trng;
|
||||
/// use embassy_rp::peripherals::TRNG;
|
||||
/// use embassy_rp::bind_interrupts;
|
||||
///
|
||||
/// bind_interrupts!(struct Irqs {
|
||||
/// TRNG_IRQ => embassy_rp::trng::InterruptHandler<TRNG>;
|
||||
/// });
|
||||
///
|
||||
/// #[embassy_executor::main]
|
||||
/// async fn main(spawner: Spawner) {
|
||||
/// let peripherals = embassy_rp::init(Default::default());
|
||||
/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default());
|
||||
///
|
||||
/// let mut randomness = [0u8; 58];
|
||||
/// loop {
|
||||
/// trng.fill_bytes(&mut randomness).await;
|
||||
/// assert_ne!(randomness, [0u8; 58]);
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub struct Trng<'d, T: Instance> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
}
|
||||
|
||||
/// 12.12.1. Overview
|
||||
/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of
|
||||
/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC).
|
||||
const TRNG_BLOCK_SIZE_BITS: usize = 192;
|
||||
const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8;
|
||||
|
||||
impl<'d, T: Instance> Trng<'d, T> {
|
||||
/// Create a new TRNG driver.
|
||||
pub fn new(
|
||||
_trng: impl Peripheral<P = T> + 'd,
|
||||
_irq: impl Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
let regs = T::regs();
|
||||
|
||||
regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false));
|
||||
|
||||
let trng_config_register = regs.trng_config();
|
||||
trng_config_register.write(|w| {
|
||||
w.set_rnd_src_sel(config.inverter_chain_length.clone().into());
|
||||
});
|
||||
|
||||
let sample_count_register = regs.sample_cnt1();
|
||||
sample_count_register.write(|w| {
|
||||
*w = config.sample_count;
|
||||
});
|
||||
|
||||
let debug_control_register = regs.trng_debug_control();
|
||||
debug_control_register.write(|w| {
|
||||
w.set_auto_correlate_bypass(config.disable_autocorrelation_test);
|
||||
w.set_trng_crngt_bypass(config.disable_crngt_test);
|
||||
w.set_vnc_bypass(config.disable_von_neumann_balancer)
|
||||
});
|
||||
|
||||
Trng { phantom: PhantomData }
|
||||
}
|
||||
|
||||
fn start_rng(&self) {
|
||||
let regs = T::regs();
|
||||
let source_enable_register = regs.rnd_source_enable();
|
||||
// Enable TRNG ROSC
|
||||
source_enable_register.write(|w| w.set_rnd_src_en(true));
|
||||
}
|
||||
|
||||
fn stop_rng(&self) {
|
||||
let regs = T::regs();
|
||||
let source_enable_register = regs.rnd_source_enable();
|
||||
source_enable_register.write(|w| w.set_rnd_src_en(false));
|
||||
let reset_bits_counter_register = regs.rst_bits_counter();
|
||||
reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true));
|
||||
}
|
||||
|
||||
fn enable_irq(&self) {
|
||||
unsafe { T::Interrupt::enable() }
|
||||
}
|
||||
|
||||
fn disable_irq(&self) {
|
||||
T::Interrupt::disable();
|
||||
}
|
||||
|
||||
fn blocking_wait_for_successful_generation(&self) {
|
||||
let regs = T::regs();
|
||||
|
||||
let trng_busy_register = regs.trng_busy();
|
||||
let trng_valid_register = regs.trng_valid();
|
||||
|
||||
let mut success = false;
|
||||
while success.not() {
|
||||
while trng_busy_register.read().trng_busy() {}
|
||||
if trng_valid_register.read().ehr_valid().not() {
|
||||
if regs.rng_isr().read().autocorr_err() {
|
||||
regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true));
|
||||
} else {
|
||||
panic!("RNG not busy, but ehr is not valid!")
|
||||
}
|
||||
} else {
|
||||
success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
|
||||
let regs = T::regs();
|
||||
let ehr_data_regs = [
|
||||
regs.ehr_data0(),
|
||||
regs.ehr_data1(),
|
||||
regs.ehr_data2(),
|
||||
regs.ehr_data3(),
|
||||
regs.ehr_data4(),
|
||||
regs.ehr_data5(),
|
||||
];
|
||||
|
||||
for (i, reg) in ehr_data_regs.iter().enumerate() {
|
||||
buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
|
||||
self.blocking_wait_for_successful_generation();
|
||||
self.read_ehr_registers_into_array(buffer);
|
||||
}
|
||||
|
||||
/// Fill the buffer with random bytes, async version.
|
||||
pub async fn fill_bytes(&mut self, destination: &mut [u8]) {
|
||||
if destination.is_empty() {
|
||||
return; // Nothing to fill
|
||||
}
|
||||
|
||||
self.start_rng();
|
||||
self.enable_irq();
|
||||
|
||||
let mut bytes_transferred = 0usize;
|
||||
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
|
||||
|
||||
let regs = T::regs();
|
||||
|
||||
let trng_busy_register = regs.trng_busy();
|
||||
let trng_valid_register = regs.trng_valid();
|
||||
|
||||
let waker = T::waker();
|
||||
|
||||
let destination_length = destination.len();
|
||||
|
||||
poll_fn(|context| {
|
||||
waker.register(context.waker());
|
||||
if bytes_transferred == destination_length {
|
||||
self.stop_rng();
|
||||
self.disable_irq();
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
if trng_busy_register.read().trng_busy() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
if trng_valid_register.read().ehr_valid().not() {
|
||||
panic!("RNG not busy, but ehr is not valid!")
|
||||
}
|
||||
self.read_ehr_registers_into_array(&mut buffer);
|
||||
let remaining = destination_length - bytes_transferred;
|
||||
if remaining > TRNG_BLOCK_SIZE_BYTES {
|
||||
destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES]
|
||||
.copy_from_slice(&buffer);
|
||||
bytes_transferred += TRNG_BLOCK_SIZE_BYTES
|
||||
} else {
|
||||
destination[bytes_transferred..bytes_transferred + remaining]
|
||||
.copy_from_slice(&buffer[0..remaining]);
|
||||
bytes_transferred += remaining
|
||||
}
|
||||
if bytes_transferred == destination_length {
|
||||
self.stop_rng();
|
||||
self.disable_irq();
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Fill the buffer with random bytes, blocking version.
|
||||
pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) {
|
||||
if destination.is_empty() {
|
||||
return; // Nothing to fill
|
||||
}
|
||||
self.start_rng();
|
||||
|
||||
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
|
||||
|
||||
for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) {
|
||||
self.blocking_wait_for_successful_generation();
|
||||
self.blocking_read_ehr_registers_into_array(&mut buffer);
|
||||
chunk.copy_from_slice(&buffer[..chunk.len()])
|
||||
}
|
||||
self.stop_rng()
|
||||
}
|
||||
|
||||
/// Return a random u32, blocking.
|
||||
pub fn blocking_next_u32(&mut self) -> u32 {
|
||||
let regs = T::regs();
|
||||
self.start_rng();
|
||||
self.blocking_wait_for_successful_generation();
|
||||
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
|
||||
// clear all of the result registers.
|
||||
let result = regs.ehr_data5().read();
|
||||
self.stop_rng();
|
||||
result
|
||||
}
|
||||
|
||||
/// Return a random u64, blocking.
|
||||
pub fn blocking_next_u64(&mut self) -> u64 {
|
||||
let regs = T::regs();
|
||||
self.start_rng();
|
||||
self.blocking_wait_for_successful_generation();
|
||||
|
||||
let low = regs.ehr_data4().read() as u64;
|
||||
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
|
||||
// clear all of the result registers.
|
||||
let result = (regs.ehr_data5().read() as u64) << 32 | low;
|
||||
self.stop_rng();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.blocking_next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.blocking_next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.blocking_fill_bytes(dest)
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||
self.blocking_fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
/// TRNG interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_trng: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
let regs = T::regs();
|
||||
let isr = regs.rng_isr().read();
|
||||
// Clear ehr bit
|
||||
regs.rng_icr().write(|w| {
|
||||
w.set_ehr_valid(true);
|
||||
});
|
||||
if isr.ehr_valid() {
|
||||
T::waker().wake();
|
||||
} else {
|
||||
// 12.12.5. List of Registers
|
||||
// ...
|
||||
// TRNG: RNG_ISR Register
|
||||
// ...
|
||||
// AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row.
|
||||
// When set, RNG ceases functioning until next reset
|
||||
if isr.autocorr_err() {
|
||||
warn!("TRNG Autocorrect error! Resetting TRNG");
|
||||
regs.trng_sw_reset().write(|w| {
|
||||
w.set_trng_sw_reset(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
examples/rp23/src/bin/trng.rs
Normal file
64
examples/rp23/src/bin/trng.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! This example shows TRNG usage
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_rp::block::ImageDef;
|
||||
use embassy_rp::gpio::{Level, Output};
|
||||
use embassy_rp::peripherals::TRNG;
|
||||
use embassy_rp::trng::Trng;
|
||||
use embassy_time::Timer;
|
||||
use rand::RngCore;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[link_section = ".start_block"]
|
||||
#[used]
|
||||
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
|
||||
|
||||
// Program metadata for `picotool info`
|
||||
#[link_section = ".bi_entries"]
|
||||
#[used]
|
||||
pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [
|
||||
embassy_rp::binary_info::rp_program_name!(c"example"),
|
||||
embassy_rp::binary_info::rp_cargo_version!(),
|
||||
embassy_rp::binary_info::rp_program_description!(c"Blinky"),
|
||||
embassy_rp::binary_info::rp_program_build_attribute!(),
|
||||
];
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
TRNG_IRQ => embassy_rp::trng::InterruptHandler<TRNG>;
|
||||
});
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let peripherals = embassy_rp::init(Default::default());
|
||||
|
||||
// Initialize the TRNG with default configuration
|
||||
let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default());
|
||||
// A buffer to collect random bytes in.
|
||||
let mut randomness = [0u8; 58];
|
||||
|
||||
let mut led = Output::new(peripherals.PIN_25, Level::Low);
|
||||
|
||||
loop {
|
||||
trng.fill_bytes(&mut randomness).await;
|
||||
info!("Random bytes async {}", &randomness);
|
||||
trng.blocking_fill_bytes(&mut randomness);
|
||||
info!("Random bytes blocking {}", &randomness);
|
||||
let random_u32 = trng.next_u32();
|
||||
let random_u64 = trng.next_u64();
|
||||
info!("Random u32 {} u64 {}", random_u32, random_u64);
|
||||
// Random number of blinks between 0 and 31
|
||||
let blinks = random_u32 % 32;
|
||||
for _ in 0..blinks {
|
||||
led.set_high();
|
||||
Timer::after_millis(20).await;
|
||||
led.set_low();
|
||||
Timer::after_millis(20).await;
|
||||
}
|
||||
Timer::after_millis(1000).await;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user