diff options
| author | Caleb Garrett <[email protected]> | 2024-01-31 21:21:36 -0500 |
|---|---|---|
| committer | Caleb Garrett <[email protected]> | 2024-01-31 21:21:36 -0500 |
| commit | 6e9ddd46267fd0fce2333af4f15bfd86f6f17f4d (patch) | |
| tree | b5614fec7c6e3e32e13c01dac6e050e32831fd77 | |
| parent | dcce40c8a2eefb956ffadbfcc3db6c27cde55dab (diff) | |
Added hash module with blocking implementation. Included SHA256 example.
| -rw-r--r-- | embassy-stm32/Cargo.toml | 4 | ||||
| -rw-r--r-- | embassy-stm32/src/hash/mod.rs | 260 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 2 | ||||
| -rw-r--r-- | examples/stm32f7/Cargo.toml | 5 | ||||
| -rw-r--r-- | examples/stm32f7/src/bin/hash.rs | 49 |
5 files changed, 316 insertions, 4 deletions
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" | |||
| 68 | sdio-host = "0.5.0" | 68 | sdio-host = "0.5.0" |
| 69 | critical-section = "1.1" | 69 | critical-section = "1.1" |
| 70 | #stm32-metapac = { version = "15" } | 70 | #stm32-metapac = { version = "15" } |
| 71 | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ab2bc2a739324793656ca1640e1caee2d88df72d" } | 71 | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-0cb3a4fcaec702c93b3700715de796636d562b15" } |
| 72 | vcell = "0.1.3" | 72 | vcell = "0.1.3" |
| 73 | bxcan = "0.7.0" | 73 | bxcan = "0.7.0" |
| 74 | nb = "1.0.0" | 74 | nb = "1.0.0" |
| @@ -87,7 +87,7 @@ critical-section = { version = "1.1", features = ["std"] } | |||
| 87 | proc-macro2 = "1.0.36" | 87 | proc-macro2 = "1.0.36" |
| 88 | quote = "1.0.15" | 88 | quote = "1.0.15" |
| 89 | #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} | 89 | #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} |
| 90 | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ab2bc2a739324793656ca1640e1caee2d88df72d", default-features = false, features = ["metadata"]} | 90 | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-0cb3a4fcaec702c93b3700715de796636d562b15", default-features = false, features = ["metadata"]} |
| 91 | 91 | ||
| 92 | 92 | ||
| 93 | [features] | 93 | [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 @@ | |||
| 1 | //! Hash generator (HASH) | ||
| 2 | use core::cmp::min; | ||
| 3 | |||
| 4 | use embassy_hal_internal::{into_ref, PeripheralRef}; | ||
| 5 | use stm32_metapac::hash::regs::*; | ||
| 6 | |||
| 7 | use crate::pac::HASH as PAC_HASH; | ||
| 8 | use crate::peripherals::HASH; | ||
| 9 | use crate::rcc::sealed::RccPeripheral; | ||
| 10 | use crate::Peripheral; | ||
| 11 | |||
| 12 | const NUM_CONTEXT_REGS: usize = 54; | ||
| 13 | const HASH_BUFFER_LEN: usize = 68; | ||
| 14 | const DIGEST_BLOCK_SIZE: usize = 64; | ||
| 15 | |||
| 16 | ///Hash algorithm selection | ||
| 17 | #[derive(PartialEq)] | ||
| 18 | pub enum Algorithm { | ||
| 19 | /// SHA-1 Algorithm | ||
| 20 | SHA1 = 0, | ||
| 21 | /// MD5 Algorithm | ||
| 22 | MD5 = 1, | ||
| 23 | /// SHA-224 Algorithm | ||
| 24 | SHA224 = 2, | ||
| 25 | /// SHA-256 Algorithm | ||
| 26 | SHA256 = 3, | ||
| 27 | } | ||
| 28 | |||
| 29 | /// Input data width selection | ||
| 30 | #[repr(u8)] | ||
| 31 | #[derive(Clone, Copy)] | ||
| 32 | pub enum DataType { | ||
| 33 | ///32-bit data, no data is swapped. | ||
| 34 | Width32 = 0, | ||
| 35 | ///16-bit data, each half-word is swapped. | ||
| 36 | Width16 = 1, | ||
| 37 | ///8-bit data, all bytes are swapped. | ||
| 38 | Width8 = 2, | ||
| 39 | ///1-bit data, all bits are swapped. | ||
| 40 | Width1 = 3, | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Stores the state of the HASH peripheral for suspending/resuming | ||
| 44 | /// digest calculation. | ||
| 45 | pub struct Context { | ||
| 46 | first_word_sent: bool, | ||
| 47 | buffer: [u8; HASH_BUFFER_LEN], | ||
| 48 | buflen: usize, | ||
| 49 | algo: Algorithm, | ||
| 50 | format: DataType, | ||
| 51 | imr: u32, | ||
| 52 | str: u32, | ||
| 53 | cr: u32, | ||
| 54 | csr: [u32; NUM_CONTEXT_REGS], | ||
| 55 | } | ||
| 56 | |||
| 57 | /// HASH driver. | ||
| 58 | pub struct Hash<'d> { | ||
| 59 | _peripheral: PeripheralRef<'d, HASH>, | ||
| 60 | } | ||
| 61 | |||
| 62 | impl<'d> Hash<'d> { | ||
| 63 | /// Instantiates, resets, and enables the HASH peripheral. | ||
| 64 | pub fn new(peripheral: impl Peripheral<P = HASH> + 'd) -> Self { | ||
| 65 | HASH::enable_and_reset(); | ||
| 66 | into_ref!(peripheral); | ||
| 67 | let instance = Self { | ||
| 68 | _peripheral: peripheral, | ||
| 69 | }; | ||
| 70 | instance | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Starts computation of a new hash and returns the saved peripheral state. | ||
| 74 | pub fn start(&mut self, algorithm: Algorithm, format: DataType) -> Context { | ||
| 75 | // Define a context for this new computation. | ||
| 76 | let mut ctx = Context { | ||
| 77 | first_word_sent: false, | ||
| 78 | buffer: [0; 68], | ||
| 79 | buflen: 0, | ||
| 80 | algo: algorithm, | ||
| 81 | format: format, | ||
| 82 | imr: 0, | ||
| 83 | str: 0, | ||
| 84 | cr: 0, | ||
| 85 | csr: [0; NUM_CONTEXT_REGS], | ||
| 86 | }; | ||
| 87 | |||
| 88 | // Set the data type in the peripheral. | ||
| 89 | PAC_HASH.cr().modify(|w| w.set_datatype(ctx.format as u8)); | ||
| 90 | |||
| 91 | // Select the algorithm. | ||
| 92 | let mut algo0 = false; | ||
| 93 | let mut algo1 = false; | ||
| 94 | if ctx.algo == Algorithm::MD5 || ctx.algo == Algorithm::SHA256 { | ||
| 95 | algo0 = true; | ||
| 96 | } | ||
| 97 | if ctx.algo == Algorithm::SHA224 || ctx.algo == Algorithm::SHA256 { | ||
| 98 | algo1 = true; | ||
| 99 | } | ||
| 100 | PAC_HASH.cr().modify(|w| w.set_algo0(algo0)); | ||
| 101 | PAC_HASH.cr().modify(|w| w.set_algo1(algo1)); | ||
| 102 | PAC_HASH.cr().modify(|w| w.set_init(true)); | ||
| 103 | |||
| 104 | // Store and return the state of the peripheral. | ||
| 105 | self.store_context(&mut ctx); | ||
| 106 | ctx | ||
| 107 | } | ||
| 108 | |||
| 109 | /// Restores the peripheral state using the given context, | ||
| 110 | /// then updates the state with the provided data. | ||
| 111 | pub fn update(&mut self, ctx: &mut Context, input: &[u8]) { | ||
| 112 | let mut data_waiting = input.len() + ctx.buflen; | ||
| 113 | if data_waiting < DIGEST_BLOCK_SIZE || (data_waiting < ctx.buffer.len() && !ctx.first_word_sent) { | ||
| 114 | // There isn't enough data to digest a block, so append it to the buffer. | ||
| 115 | ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); | ||
| 116 | ctx.buflen += input.len(); | ||
| 117 | return; | ||
| 118 | } | ||
| 119 | |||
| 120 | //Restore the peripheral state. | ||
| 121 | self.load_context(&ctx); | ||
| 122 | |||
| 123 | let mut ilen_remaining = input.len(); | ||
| 124 | let mut input_start = 0; | ||
| 125 | |||
| 126 | // Handle first block. | ||
| 127 | if !ctx.first_word_sent { | ||
| 128 | let empty_len = ctx.buffer.len() - ctx.buflen; | ||
| 129 | let copy_len = min(empty_len, ilen_remaining); | ||
| 130 | // Fill the buffer. | ||
| 131 | if copy_len > 0 { | ||
| 132 | ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[0..copy_len]); | ||
| 133 | ctx.buflen += copy_len; | ||
| 134 | ilen_remaining -= copy_len; | ||
| 135 | input_start += copy_len; | ||
| 136 | } | ||
| 137 | assert_eq!(ctx.buflen, HASH_BUFFER_LEN); | ||
| 138 | self.accumulate(ctx.buffer.as_slice()); | ||
| 139 | data_waiting -= ctx.buflen; | ||
| 140 | ctx.buflen = 0; | ||
| 141 | ctx.first_word_sent = true; | ||
| 142 | } | ||
| 143 | |||
| 144 | if data_waiting < 64 { | ||
| 145 | // There isn't enough data remaining to process another block, so store it. | ||
| 146 | assert_eq!(ctx.buflen, 0); | ||
| 147 | ctx.buffer[0..ilen_remaining].copy_from_slice(&input[input_start..input_start + ilen_remaining]); | ||
| 148 | ctx.buflen += ilen_remaining; | ||
| 149 | } else { | ||
| 150 | let mut total_data_sent = 0; | ||
| 151 | // First ingest the data in the buffer. | ||
| 152 | let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; | ||
| 153 | if empty_len > 0 { | ||
| 154 | let copy_len = min(empty_len, ilen_remaining); | ||
| 155 | ctx.buffer[ctx.buflen..ctx.buflen + copy_len] | ||
| 156 | .copy_from_slice(&input[input_start..input_start + copy_len]); | ||
| 157 | ctx.buflen += copy_len; | ||
| 158 | ilen_remaining -= copy_len; | ||
| 159 | input_start += copy_len; | ||
| 160 | } | ||
| 161 | assert_eq!(ctx.buflen % 64, 0); | ||
| 162 | self.accumulate(&ctx.buffer[0..64]); | ||
| 163 | total_data_sent += ctx.buflen; | ||
| 164 | ctx.buflen = 0; | ||
| 165 | |||
| 166 | // Move any extra data to the now-empty buffer. | ||
| 167 | let leftovers = ilen_remaining % 64; | ||
| 168 | if leftovers > 0 { | ||
| 169 | assert!(ilen_remaining >= leftovers); | ||
| 170 | ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); | ||
| 171 | ctx.buflen += leftovers; | ||
| 172 | ilen_remaining -= leftovers; | ||
| 173 | } | ||
| 174 | assert_eq!(ilen_remaining % 64, 0); | ||
| 175 | |||
| 176 | // Hash the remaining data. | ||
| 177 | self.accumulate(&input[input_start..input_start + ilen_remaining]); | ||
| 178 | |||
| 179 | total_data_sent += ilen_remaining; | ||
| 180 | assert_eq!(total_data_sent % 64, 0); | ||
| 181 | assert!(total_data_sent >= 64); | ||
| 182 | } | ||
| 183 | |||
| 184 | // Save the peripheral context. | ||
| 185 | self.store_context(ctx); | ||
| 186 | } | ||
| 187 | |||
| 188 | /// Computes a digest for the given context. A slice of the provided digest buffer is returned. | ||
| 189 | /// The length of the returned slice is dependent on the digest length of the selected algorithm. | ||
| 190 | pub fn finish<'a>(&mut self, mut ctx: Context, digest: &'a mut [u8; 32]) -> &'a [u8] { | ||
| 191 | // Restore the peripheral state. | ||
| 192 | self.load_context(&ctx); | ||
| 193 | // Hash the leftover bytes, if any. | ||
| 194 | self.accumulate(&ctx.buffer[0..ctx.buflen]); | ||
| 195 | ctx.buflen = 0; | ||
| 196 | |||
| 197 | //Start the digest calculation. | ||
| 198 | PAC_HASH.str().write(|w| w.set_dcal(true)); | ||
| 199 | |||
| 200 | //Wait for completion. | ||
| 201 | while !PAC_HASH.sr().read().dcis() {} | ||
| 202 | |||
| 203 | //Return the digest. | ||
| 204 | let digest_words = match ctx.algo { | ||
| 205 | Algorithm::SHA1 => 5, | ||
| 206 | Algorithm::MD5 => 4, | ||
| 207 | Algorithm::SHA224 => 7, | ||
| 208 | Algorithm::SHA256 => 8, | ||
| 209 | }; | ||
| 210 | let mut i = 0; | ||
| 211 | while i < digest_words { | ||
| 212 | let word = PAC_HASH.hr(i).read(); | ||
| 213 | digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); | ||
| 214 | i += 1; | ||
| 215 | } | ||
| 216 | &digest[0..digest_words * 4] | ||
| 217 | } | ||
| 218 | |||
| 219 | fn accumulate(&mut self, input: &[u8]) { | ||
| 220 | //Set the number of valid bits. | ||
| 221 | let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; | ||
| 222 | PAC_HASH.str().modify(|w| w.set_nblw(num_valid_bits)); | ||
| 223 | |||
| 224 | let mut i = 0; | ||
| 225 | while i < input.len() { | ||
| 226 | let mut word: [u8; 4] = [0; 4]; | ||
| 227 | let copy_idx = min(i + 4, input.len()); | ||
| 228 | word[0..copy_idx - i].copy_from_slice(&input[i..copy_idx]); | ||
| 229 | PAC_HASH.din().write_value(u32::from_ne_bytes(word)); | ||
| 230 | i += 4; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | /// Save the peripheral state to a context. | ||
| 235 | fn store_context(&mut self, ctx: &mut Context) { | ||
| 236 | while !PAC_HASH.sr().read().dinis() {} | ||
| 237 | ctx.imr = PAC_HASH.imr().read().0; | ||
| 238 | ctx.str = PAC_HASH.str().read().0; | ||
| 239 | ctx.cr = PAC_HASH.cr().read().0; | ||
| 240 | let mut i = 0; | ||
| 241 | while i < NUM_CONTEXT_REGS { | ||
| 242 | ctx.csr[i] = PAC_HASH.csr(i).read(); | ||
| 243 | i += 1; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Restore the peripheral state from a context. | ||
| 248 | fn load_context(&mut self, ctx: &Context) { | ||
| 249 | // Restore the peripheral state from the context. | ||
| 250 | PAC_HASH.imr().write_value(Imr { 0: ctx.imr }); | ||
| 251 | PAC_HASH.str().write_value(Str { 0: ctx.str }); | ||
| 252 | PAC_HASH.cr().write_value(Cr { 0: ctx.cr }); | ||
| 253 | PAC_HASH.cr().modify(|w| w.set_init(true)); | ||
| 254 | let mut i = 0; | ||
| 255 | while i < NUM_CONTEXT_REGS { | ||
| 256 | PAC_HASH.csr(i).write_value(ctx.csr[i]); | ||
| 257 | i += 1; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
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; | |||
| 45 | pub mod flash; | 45 | pub mod flash; |
| 46 | #[cfg(fmc)] | 46 | #[cfg(fmc)] |
| 47 | pub mod fmc; | 47 | pub mod fmc; |
| 48 | #[cfg(hash)] | ||
| 49 | pub mod hash; | ||
| 48 | #[cfg(hrtim)] | 50 | #[cfg(hrtim)] |
| 49 | pub mod hrtim; | 51 | pub mod hrtim; |
| 50 | #[cfg(i2c)] | 52 | #[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" | |||
| 5 | license = "MIT OR Apache-2.0" | 5 | license = "MIT OR Apache-2.0" |
| 6 | 6 | ||
| 7 | [dependencies] | 7 | [dependencies] |
| 8 | # Change stm32f767zi to your chip name, if necessary. | 8 | # Change stm32f777zi to your chip name, if necessary. |
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f767zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } | 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } |
| 10 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } | 10 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } |
| 11 | embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } | 11 | embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } |
| 12 | embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | 12 | 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" | |||
| 28 | critical-section = "1.1" | 28 | critical-section = "1.1" |
| 29 | embedded-storage = "0.3.1" | 29 | embedded-storage = "0.3.1" |
| 30 | static_cell = "2" | 30 | static_cell = "2" |
| 31 | sha2 = { version = "0.10.8", default-features = false } | ||
| 31 | 32 | ||
| 32 | [profile.release] | 33 | [profile.release] |
| 33 | debug = 2 | 34 | 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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::info; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::Config; | ||
| 7 | use embassy_time::{Duration, Instant}; | ||
| 8 | use {defmt_rtt as _, panic_probe as _}; | ||
| 9 | |||
| 10 | use embassy_stm32::hash::*; | ||
| 11 | use sha2::{Digest, Sha256}; | ||
| 12 | |||
| 13 | const TEST_STRING_1: &[u8] = b"hello world"; | ||
| 14 | |||
| 15 | #[embassy_executor::main] | ||
| 16 | async fn main(_spawner: Spawner) -> ! { | ||
| 17 | let config = Config::default(); | ||
| 18 | let p = embassy_stm32::init(config); | ||
| 19 | |||
| 20 | let hw_start_time = Instant::now(); | ||
| 21 | |||
| 22 | // Compute a digest in hardware. | ||
| 23 | let mut hw_hasher = Hash::new(p.HASH); | ||
| 24 | let mut context = hw_hasher.start(Algorithm::SHA256, DataType::Width8); | ||
| 25 | hw_hasher.update(&mut context, TEST_STRING_1); | ||
| 26 | let mut buffer: [u8; 32] = [0; 32]; | ||
| 27 | let hw_digest = hw_hasher.finish(context, &mut buffer); | ||
| 28 | |||
| 29 | let hw_end_time = Instant::now(); | ||
| 30 | let hw_execution_time = hw_end_time - hw_start_time; | ||
| 31 | |||
| 32 | let sw_start_time = Instant::now(); | ||
| 33 | |||
| 34 | // Compute a digest in software. | ||
| 35 | let mut sw_hasher = Sha256::new(); | ||
| 36 | sw_hasher.update(TEST_STRING_1); | ||
| 37 | let sw_digest = sw_hasher.finalize(); | ||
| 38 | |||
| 39 | let sw_end_time = Instant::now(); | ||
| 40 | let sw_execution_time = sw_end_time - sw_start_time; | ||
| 41 | |||
| 42 | info!("Hardware Digest: {:?}", hw_digest); | ||
| 43 | info!("Software Digest: {:?}", sw_digest[..]); | ||
| 44 | info!("Hardware Execution Time: {:?}", hw_execution_time); | ||
| 45 | info!("Software Execution Time: {:?}", sw_execution_time); | ||
| 46 | assert_eq!(*hw_digest, sw_digest[..]); | ||
| 47 | |||
| 48 | loop {} | ||
| 49 | } | ||
