From 6e9ddd46267fd0fce2333af4f15bfd86f6f17f4d Mon Sep 17 00:00:00 2001 From: Caleb Garrett <47389035+caleb-garrett@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:21:36 -0500 Subject: [PATCH] Added hash module with blocking implementation. Included SHA256 example. --- embassy-stm32/Cargo.toml | 4 +- embassy-stm32/src/hash/mod.rs | 260 +++++++++++++++++++++++++++++++ embassy-stm32/src/lib.rs | 2 + examples/stm32f7/Cargo.toml | 5 +- examples/stm32f7/src/bin/hash.rs | 49 ++++++ 5 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 embassy-stm32/src/hash/mod.rs create mode 100644 examples/stm32f7/src/bin/hash.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 70d4daf09..d8a4c65fa 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -68,7 +68,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ab2bc2a739324793656ca1640e1caee2d88df72d" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-0cb3a4fcaec702c93b3700715de796636d562b15" } vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" @@ -87,7 +87,7 @@ critical-section = { version = "1.1", features = ["std"] } proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ab2bc2a739324793656ca1640e1caee2d88df72d", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-0cb3a4fcaec702c93b3700715de796636d562b15", default-features = false, features = ["metadata"]} [features] diff --git a/embassy-stm32/src/hash/mod.rs b/embassy-stm32/src/hash/mod.rs new file mode 100644 index 000000000..e3d2d7b16 --- /dev/null +++ b/embassy-stm32/src/hash/mod.rs @@ -0,0 +1,260 @@ +//! Hash generator (HASH) +use core::cmp::min; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use stm32_metapac::hash::regs::*; + +use crate::pac::HASH as PAC_HASH; +use crate::peripherals::HASH; +use crate::rcc::sealed::RccPeripheral; +use crate::Peripheral; + +const NUM_CONTEXT_REGS: usize = 54; +const HASH_BUFFER_LEN: usize = 68; +const DIGEST_BLOCK_SIZE: usize = 64; + +///Hash algorithm selection +#[derive(PartialEq)] +pub enum Algorithm { + /// SHA-1 Algorithm + SHA1 = 0, + /// MD5 Algorithm + MD5 = 1, + /// SHA-224 Algorithm + SHA224 = 2, + /// SHA-256 Algorithm + SHA256 = 3, +} + +/// Input data width selection +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum DataType { + ///32-bit data, no data is swapped. + Width32 = 0, + ///16-bit data, each half-word is swapped. + Width16 = 1, + ///8-bit data, all bytes are swapped. + Width8 = 2, + ///1-bit data, all bits are swapped. + Width1 = 3, +} + +/// Stores the state of the HASH peripheral for suspending/resuming +/// digest calculation. +pub struct Context { + first_word_sent: bool, + buffer: [u8; HASH_BUFFER_LEN], + buflen: usize, + algo: Algorithm, + format: DataType, + imr: u32, + str: u32, + cr: u32, + csr: [u32; NUM_CONTEXT_REGS], +} + +/// HASH driver. +pub struct Hash<'d> { + _peripheral: PeripheralRef<'d, HASH>, +} + +impl<'d> Hash<'d> { + /// Instantiates, resets, and enables the HASH peripheral. + pub fn new(peripheral: impl Peripheral
+ 'd) -> Self { + HASH::enable_and_reset(); + into_ref!(peripheral); + let instance = Self { + _peripheral: peripheral, + }; + instance + } + + /// Starts computation of a new hash and returns the saved peripheral state. + pub fn start(&mut self, algorithm: Algorithm, format: DataType) -> Context { + // Define a context for this new computation. + let mut ctx = Context { + first_word_sent: false, + buffer: [0; 68], + buflen: 0, + algo: algorithm, + format: format, + imr: 0, + str: 0, + cr: 0, + csr: [0; NUM_CONTEXT_REGS], + }; + + // Set the data type in the peripheral. + PAC_HASH.cr().modify(|w| w.set_datatype(ctx.format as u8)); + + // Select the algorithm. + let mut algo0 = false; + let mut algo1 = false; + if ctx.algo == Algorithm::MD5 || ctx.algo == Algorithm::SHA256 { + algo0 = true; + } + if ctx.algo == Algorithm::SHA224 || ctx.algo == Algorithm::SHA256 { + algo1 = true; + } + PAC_HASH.cr().modify(|w| w.set_algo0(algo0)); + PAC_HASH.cr().modify(|w| w.set_algo1(algo1)); + PAC_HASH.cr().modify(|w| w.set_init(true)); + + // Store and return the state of the peripheral. + self.store_context(&mut ctx); + ctx + } + + /// Restores the peripheral state using the given context, + /// then updates the state with the provided data. + pub fn update(&mut self, ctx: &mut Context, input: &[u8]) { + let mut data_waiting = input.len() + ctx.buflen; + if data_waiting < DIGEST_BLOCK_SIZE || (data_waiting < ctx.buffer.len() && !ctx.first_word_sent) { + // There isn't enough data to digest a block, so append it to the buffer. + ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); + ctx.buflen += input.len(); + return; + } + + //Restore the peripheral state. + self.load_context(&ctx); + + let mut ilen_remaining = input.len(); + let mut input_start = 0; + + // Handle first block. + if !ctx.first_word_sent { + let empty_len = ctx.buffer.len() - ctx.buflen; + let copy_len = min(empty_len, ilen_remaining); + // Fill the buffer. + if copy_len > 0 { + ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[0..copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + assert_eq!(ctx.buflen, HASH_BUFFER_LEN); + self.accumulate(ctx.buffer.as_slice()); + data_waiting -= ctx.buflen; + ctx.buflen = 0; + ctx.first_word_sent = true; + } + + if data_waiting < 64 { + // There isn't enough data remaining to process another block, so store it. + assert_eq!(ctx.buflen, 0); + ctx.buffer[0..ilen_remaining].copy_from_slice(&input[input_start..input_start + ilen_remaining]); + ctx.buflen += ilen_remaining; + } else { + let mut total_data_sent = 0; + // First ingest the data in the buffer. + let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; + if empty_len > 0 { + let copy_len = min(empty_len, ilen_remaining); + ctx.buffer[ctx.buflen..ctx.buflen + copy_len] + .copy_from_slice(&input[input_start..input_start + copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + assert_eq!(ctx.buflen % 64, 0); + self.accumulate(&ctx.buffer[0..64]); + total_data_sent += ctx.buflen; + ctx.buflen = 0; + + // Move any extra data to the now-empty buffer. + let leftovers = ilen_remaining % 64; + if leftovers > 0 { + assert!(ilen_remaining >= leftovers); + ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); + ctx.buflen += leftovers; + ilen_remaining -= leftovers; + } + assert_eq!(ilen_remaining % 64, 0); + + // Hash the remaining data. + self.accumulate(&input[input_start..input_start + ilen_remaining]); + + total_data_sent += ilen_remaining; + assert_eq!(total_data_sent % 64, 0); + assert!(total_data_sent >= 64); + } + + // Save the peripheral context. + self.store_context(ctx); + } + + /// Computes a digest for the given context. A slice of the provided digest buffer is returned. + /// The length of the returned slice is dependent on the digest length of the selected algorithm. + pub fn finish<'a>(&mut self, mut ctx: Context, digest: &'a mut [u8; 32]) -> &'a [u8] { + // Restore the peripheral state. + self.load_context(&ctx); + // Hash the leftover bytes, if any. + self.accumulate(&ctx.buffer[0..ctx.buflen]); + ctx.buflen = 0; + + //Start the digest calculation. + PAC_HASH.str().write(|w| w.set_dcal(true)); + + //Wait for completion. + while !PAC_HASH.sr().read().dcis() {} + + //Return the digest. + let digest_words = match ctx.algo { + Algorithm::SHA1 => 5, + Algorithm::MD5 => 4, + Algorithm::SHA224 => 7, + Algorithm::SHA256 => 8, + }; + let mut i = 0; + while i < digest_words { + let word = PAC_HASH.hr(i).read(); + digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); + i += 1; + } + &digest[0..digest_words * 4] + } + + fn accumulate(&mut self, input: &[u8]) { + //Set the number of valid bits. + let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; + PAC_HASH.str().modify(|w| w.set_nblw(num_valid_bits)); + + let mut i = 0; + while i < input.len() { + let mut word: [u8; 4] = [0; 4]; + let copy_idx = min(i + 4, input.len()); + word[0..copy_idx - i].copy_from_slice(&input[i..copy_idx]); + PAC_HASH.din().write_value(u32::from_ne_bytes(word)); + i += 4; + } + } + + /// Save the peripheral state to a context. + fn store_context(&mut self, ctx: &mut Context) { + while !PAC_HASH.sr().read().dinis() {} + ctx.imr = PAC_HASH.imr().read().0; + ctx.str = PAC_HASH.str().read().0; + ctx.cr = PAC_HASH.cr().read().0; + let mut i = 0; + while i < NUM_CONTEXT_REGS { + ctx.csr[i] = PAC_HASH.csr(i).read(); + i += 1; + } + } + + /// Restore the peripheral state from a context. + fn load_context(&mut self, ctx: &Context) { + // Restore the peripheral state from the context. + PAC_HASH.imr().write_value(Imr { 0: ctx.imr }); + PAC_HASH.str().write_value(Str { 0: ctx.str }); + PAC_HASH.cr().write_value(Cr { 0: ctx.cr }); + PAC_HASH.cr().modify(|w| w.set_init(true)); + let mut i = 0; + while i < NUM_CONTEXT_REGS { + PAC_HASH.csr(i).write_value(ctx.csr[i]); + i += 1; + } + } +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index a465fccd8..cd1ede0fa 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -45,6 +45,8 @@ pub mod exti; pub mod flash; #[cfg(fmc)] pub mod fmc; +#[cfg(hash)] +pub mod hash; #[cfg(hrtim)] pub mod hrtim; #[cfg(i2c)] diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 941ba38cd..a612c2554 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -5,8 +5,8 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -# Change stm32f767zi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f767zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } +# Change stm32f777zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } @@ -28,6 +28,7 @@ rand_core = "0.6.3" critical-section = "1.1" embedded-storage = "0.3.1" static_cell = "2" +sha2 = { version = "0.10.8", default-features = false } [profile.release] debug = 2 diff --git a/examples/stm32f7/src/bin/hash.rs b/examples/stm32f7/src/bin/hash.rs new file mode 100644 index 000000000..1fd0e87eb --- /dev/null +++ b/examples/stm32f7/src/bin/hash.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::{Duration, Instant}; +use {defmt_rtt as _, panic_probe as _}; + +use embassy_stm32::hash::*; +use sha2::{Digest, Sha256}; + +const TEST_STRING_1: &[u8] = b"hello world"; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let hw_start_time = Instant::now(); + + // Compute a digest in hardware. + let mut hw_hasher = Hash::new(p.HASH); + let mut context = hw_hasher.start(Algorithm::SHA256, DataType::Width8); + hw_hasher.update(&mut context, TEST_STRING_1); + let mut buffer: [u8; 32] = [0; 32]; + let hw_digest = hw_hasher.finish(context, &mut buffer); + + let hw_end_time = Instant::now(); + let hw_execution_time = hw_end_time - hw_start_time; + + let sw_start_time = Instant::now(); + + // Compute a digest in software. + let mut sw_hasher = Sha256::new(); + sw_hasher.update(TEST_STRING_1); + let sw_digest = sw_hasher.finalize(); + + let sw_end_time = Instant::now(); + let sw_execution_time = sw_end_time - sw_start_time; + + info!("Hardware Digest: {:?}", hw_digest); + info!("Software Digest: {:?}", sw_digest[..]); + info!("Hardware Execution Time: {:?}", hw_execution_time); + info!("Software Execution Time: {:?}", sw_execution_time); + assert_eq!(*hw_digest, sw_digest[..]); + + loop {} +}