esp_hal/
sha.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
//! # Secure Hash Algorithm (SHA) Accelerator
//!
//! ## Overview
//! This SHA accelerator is a hardware device that speeds up the SHA algorithm
//! significantly, compared to a SHA algorithm implemented solely in software
//!
//! ## Configuration
//! This driver allows you to perform cryptographic hash operations using
//! various hash algorithms supported by the SHA peripheral, such as:
//!    * SHA-1
//!    * SHA-224
//!    * SHA-256
//!    * SHA-384
//!    * SHA-512
//!
//! The driver supports two working modes:
//!    * Typical SHA
//!    * DMA-SHA
//!
//! It provides functions to update the hash calculation with input data, finish
//! the hash calculation and retrieve the resulting hash value. The SHA
//! peripheral on ESP chips can handle large data streams efficiently, making it
//! suitable for cryptographic applications that require secure hashing.
//!
//! To use the SHA Peripheral Driver, you need to initialize it with the desired
//! SHA mode and the corresponding SHA peripheral. Once initialized, you can
//! update the hash calculation by providing input data, finish the calculation
//! to retrieve the hash value and repeat the process for a new hash calculation
//! if needed.
//!
//! ## Examples
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::sha::Sha;
//! # use esp_hal::sha::Sha256;
//! # use nb::block;
//! let mut source_data = "HELLO, ESPRESSIF!".as_bytes();
//! let mut sha = Sha::new(peripherals.SHA);
//! let mut hasher = sha.start::<Sha256>();
//! // Short hashes can be created by decreasing the output buffer to the
//! // desired length
//! let mut output = [0u8; 32];
//!
//! while !source_data.is_empty() {
//!     // All the HW Sha functions are infallible so unwrap is fine to use if
//!     // you use block!
//!     source_data = block!(hasher.update(source_data))?;
//! }
//!
//! // Finish can be called as many times as desired to get multiple copies of
//! // the output.
//! block!(hasher.finish(output.as_mut_slice()))?;
//!
//! # Ok(())
//! # }
//! ```
//! ## Implementation State
//! - DMA-SHA Mode is not supported.

use core::{borrow::Borrow, convert::Infallible, marker::PhantomData, mem::size_of};

/// Re-export digest for convenience
#[cfg(feature = "digest")]
pub use digest::Digest;

#[cfg(not(esp32))]
use crate::peripherals::Interrupt;
use crate::{
    peripheral::{Peripheral, PeripheralRef},
    peripherals::SHA,
    reg_access::{AlignmentHelper, SocDependentEndianess},
    system::GenericPeripheralGuard,
};

/// The SHA Accelerator driver instance
pub struct Sha<'d> {
    sha: PeripheralRef<'d, SHA>,
    _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Sha as u8 }>,
}

impl<'d> Sha<'d> {
    /// Create a new instance of the SHA Accelerator driver.
    pub fn new(sha: impl Peripheral<P = SHA> + 'd) -> Self {
        crate::into_ref!(sha);
        let guard = GenericPeripheralGuard::new();

        Self { sha, _guard: guard }
    }

    /// Start a new digest.
    pub fn start<'a, A: ShaAlgorithm>(&'a mut self) -> ShaDigest<'d, A, &'a mut Self> {
        ShaDigest::new(self)
    }

    /// Start a new digest and take ownership of the driver.
    /// This is useful for storage outside a function body. i.e. in static or
    /// struct.
    pub fn start_owned<A: ShaAlgorithm>(self) -> ShaDigest<'d, A, Self> {
        ShaDigest::new(self)
    }

    #[cfg(not(esp32))]
    fn regs(&self) -> &crate::pac::sha::RegisterBlock {
        self.sha.register_block()
    }
}

impl crate::private::Sealed for Sha<'_> {}

#[cfg(not(esp32))]
#[instability::unstable]
impl crate::interrupt::InterruptConfigurable for Sha<'_> {
    fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
        for core in crate::system::Cpu::other() {
            crate::interrupt::disable(core, Interrupt::SHA);
        }
        unsafe { crate::interrupt::bind_interrupt(Interrupt::SHA, handler.handler()) };
        unwrap!(crate::interrupt::enable(Interrupt::SHA, handler.priority()));
    }
}

// A few notes on this implementation with regards to 'memcpy',
// - The registers are *not* cleared after processing, so padding needs to be
//   written out
// - This component uses core::intrinsics::volatile_* which is unstable, but is
//   the only way to
// efficiently copy memory with volatile
// - For this particular registers (and probably others), a full u32 needs to be
//   written partial
// register writes (i.e. in u8 mode) does not work
//   - This means that we need to buffer bytes coming in up to 4 u8's in order
//     to create a full u32

/// An active digest
///
/// This implementation might fail after u32::MAX/8 bytes, to increase please
/// see ::finish() length/self.cursor usage
pub struct ShaDigest<'d, A, S: Borrow<Sha<'d>>> {
    sha: S,
    alignment_helper: AlignmentHelper<SocDependentEndianess>,
    cursor: usize,
    first_run: bool,
    finished: bool,
    message_buffer_is_full: bool,
    phantom: PhantomData<(&'d (), A)>,
}

impl<'d, A: ShaAlgorithm, S: Borrow<Sha<'d>>> ShaDigest<'d, A, S> {
    /// Creates a new digest
    #[allow(unused_mut)]
    pub fn new(mut sha: S) -> Self {
        #[cfg(not(esp32))]
        // Setup SHA Mode.
        sha.borrow()
            .regs()
            .mode()
            .write(|w| unsafe { w.mode().bits(A::MODE_AS_BITS) });

        Self {
            sha,
            alignment_helper: AlignmentHelper::default(),
            cursor: 0,
            first_run: true,
            finished: false,
            message_buffer_is_full: false,
            phantom: PhantomData,
        }
    }

    /// Restores a previously saved digest.
    #[cfg(not(esp32))]
    pub fn restore(sha: S, ctx: &mut Context<A>) -> Self {
        // Setup SHA Mode.
        sha.borrow()
            .regs()
            .mode()
            .write(|w| unsafe { w.mode().bits(A::MODE_AS_BITS) });

        // Restore the message buffer
        unsafe {
            core::ptr::copy_nonoverlapping(ctx.buffer.as_ptr(), m_mem(&sha.borrow().sha, 0), 32);
        }

        let mut ah = ctx.alignment_helper.clone();

        // Restore previously saved hash
        ah.volatile_write_regset(h_mem(&sha.borrow().sha, 0), &ctx.saved_digest, 64);

        Self {
            sha,
            alignment_helper: ah,
            cursor: ctx.cursor,
            first_run: ctx.first_run,
            finished: ctx.finished,
            message_buffer_is_full: ctx.message_buffer_is_full,
            phantom: PhantomData,
        }
    }

    /// Returns true if the hardware is processing the next message.
    pub fn is_busy(&self) -> bool {
        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                A::is_busy(&self.sha.borrow().sha)
            } else {
                self.sha.borrow().regs().busy().read().state().bit_is_set()
            }
        }
    }

    /// Updates the SHA digest with the provided data buffer.
    pub fn update<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
        self.finished = false;

        self.write_data(incoming)
    }

    /// Finish of the calculation (if not already) and copy result to output
    /// After `finish()` is called `update()`s will contribute to a new hash
    /// which can be calculated again with `finish()`.
    ///
    /// Typically, output is expected to be the size of
    /// [ShaAlgorithm::DIGEST_LENGTH], but smaller inputs can be given to
    /// get a "short hash"
    pub fn finish(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> {
        // Store message length for padding
        let length = (self.cursor as u64 * 8).to_be_bytes();
        nb::block!(self.update(&[0x80]))?; // Append "1" bit

        // Flush partial data, ensures aligned cursor
        {
            while self.is_busy() {}
            if self.message_buffer_is_full {
                self.process_buffer();
                self.message_buffer_is_full = false;
                while self.is_busy() {}
            }

            let flushed = self.alignment_helper.flush_to(
                m_mem(&self.sha.borrow().sha, 0),
                (self.cursor % A::CHUNK_LENGTH) / self.alignment_helper.align_size(),
            );
            self.cursor = self.cursor.wrapping_add(flushed);

            if flushed > 0 && self.cursor % A::CHUNK_LENGTH == 0 {
                self.process_buffer();
                while self.is_busy() {}
            }
        }
        debug_assert!(self.cursor % 4 == 0);

        let mod_cursor = self.cursor % A::CHUNK_LENGTH;
        if (A::CHUNK_LENGTH - mod_cursor) < A::CHUNK_LENGTH / 8 {
            // Zero out remaining data if buffer is almost full (>=448/896), and process
            // buffer
            let pad_len = A::CHUNK_LENGTH - mod_cursor;
            self.alignment_helper.volatile_write(
                m_mem(&self.sha.borrow().sha, 0),
                0_u8,
                pad_len / self.alignment_helper.align_size(),
                mod_cursor / self.alignment_helper.align_size(),
            );
            self.process_buffer();
            self.cursor = self.cursor.wrapping_add(pad_len);

            debug_assert_eq!(self.cursor % A::CHUNK_LENGTH, 0);

            // Spin-wait for finish
            while self.is_busy() {}
        }

        let mod_cursor = self.cursor % A::CHUNK_LENGTH; // Should be zero if branched above
        let pad_len = A::CHUNK_LENGTH - mod_cursor - size_of::<u64>();

        self.alignment_helper.volatile_write(
            m_mem(&self.sha.borrow().sha, 0),
            0,
            pad_len / self.alignment_helper.align_size(),
            mod_cursor / self.alignment_helper.align_size(),
        );

        self.alignment_helper.aligned_volatile_copy(
            m_mem(&self.sha.borrow().sha, 0),
            &length,
            A::CHUNK_LENGTH / self.alignment_helper.align_size(),
            (A::CHUNK_LENGTH - size_of::<u64>()) / self.alignment_helper.align_size(),
        );

        self.process_buffer();
        // Spin-wait for final buffer to be processed
        while self.is_busy() {}

        // ESP32 requires additional load to retrieve output
        #[cfg(esp32)]
        {
            A::load(&self.sha.borrow().sha);

            // Spin wait for result, 8-20 clock cycles according to manual
            while self.is_busy() {}
        }

        self.alignment_helper.volatile_read_regset(
            h_mem(&self.sha.borrow().sha, 0),
            output,
            core::cmp::min(output.len(), 32) / self.alignment_helper.align_size(),
        );

        self.first_run = true;
        self.cursor = 0;
        self.alignment_helper.reset();

        Ok(())
    }

    /// Save the current state of the digest for later continuation.
    #[cfg(not(esp32))]
    pub fn save(&mut self, context: &mut Context<A>) -> nb::Result<(), Infallible> {
        if self.is_busy() {
            return Err(nb::Error::WouldBlock);
        }

        context.alignment_helper = self.alignment_helper.clone();
        context.cursor = self.cursor;
        context.first_run = self.first_run;
        context.finished = self.finished;
        context.message_buffer_is_full = self.message_buffer_is_full;

        // Save the content of the current hash.
        self.alignment_helper.volatile_read_regset(
            h_mem(&self.sha.borrow().sha, 0),
            &mut context.saved_digest,
            64 / self.alignment_helper.align_size(),
        );

        // Save the content of the current (probably partially written) message.
        unsafe {
            core::ptr::copy_nonoverlapping(
                m_mem(&self.sha.borrow().sha, 0),
                context.buffer.as_mut_ptr(),
                32,
            );
        }

        Ok(())
    }

    /// Discard the current digest and return the peripheral.
    pub fn cancel(self) -> S {
        self.sha
    }

    /// Processes the data buffer and updates the hash state.
    ///
    /// This method is platform-specific and differs for ESP32 and non-ESP32
    /// platforms.
    fn process_buffer(&mut self) {
        let sha = self.sha.borrow();

        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                if self.first_run {
                    A::start(&sha.sha);
                    self.first_run = false;
                } else {
                    A::r#continue(&sha.sha);
                }
            } else {
                if self.first_run {
                    // Set SHA_START_REG
                    // FIXME: raw register access
                    sha.regs().start().write(|w| unsafe { w.bits(1) });
                    self.first_run = false;
                } else {
                    // SET SHA_CONTINUE_REG
                    // FIXME: raw register access
                    sha.regs().continue_().write(|w| unsafe { w.bits(1) });
                }
            }
        }
    }

    fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
        if self.message_buffer_is_full {
            if self.is_busy() {
                // The message buffer is full and the hardware is still processing the previous
                // message. There's nothing to be done besides wait for the hardware.
                return Err(nb::Error::WouldBlock);
            } else {
                // Submit the full buffer.
                self.process_buffer();
                // The buffer is now free for filling.
                self.message_buffer_is_full = false;
            }
        }

        let mod_cursor = self.cursor % A::CHUNK_LENGTH;
        let chunk_len = A::CHUNK_LENGTH;

        let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy(
            m_mem(&self.sha.borrow().sha, 0),
            incoming,
            chunk_len / self.alignment_helper.align_size(),
            mod_cursor / self.alignment_helper.align_size(),
        );

        self.cursor = self.cursor.wrapping_add(incoming.len() - remaining.len());

        if bound_reached {
            // Message is full now.

            if self.is_busy() {
                // The message buffer is full and the hardware is still processing the previous
                // message. There's nothing to be done besides wait for the hardware.
                self.message_buffer_is_full = true;
            } else {
                // Send the full buffer.
                self.process_buffer();
            }
        }

        Ok(remaining)
    }
}

#[cfg(not(esp32))]
/// Context for a SHA Accelerator driver instance
#[derive(Debug, Clone)]
pub struct Context<A: ShaAlgorithm> {
    alignment_helper: AlignmentHelper<SocDependentEndianess>,
    cursor: usize,
    first_run: bool,
    finished: bool,
    message_buffer_is_full: bool,
    /// Buffered bytes (SHA_M_n_REG) to be processed.
    buffer: [u32; 32],
    /// Saved digest (SHA_H_n_REG) for interleaving operation
    saved_digest: [u8; 64],
    phantom: PhantomData<A>,
}

#[cfg(not(esp32))]
impl<A: ShaAlgorithm> Context<A> {
    /// Create a new empty context
    pub fn new() -> Self {
        Self {
            cursor: 0,
            first_run: true,
            finished: false,
            message_buffer_is_full: false,
            alignment_helper: AlignmentHelper::default(),
            buffer: [0; 32],
            saved_digest: [0; 64],
            phantom: PhantomData,
        }
    }

    /// Indicates if the SHA context is in the first run.
    ///
    /// Returns `true` if this is the first time processing data with the SHA
    /// instance, otherwise returns `false`.
    pub fn first_run(&self) -> bool {
        self.first_run
    }

    /// Indicates if the SHA context has finished processing the data.
    ///
    /// Returns `true` if the SHA calculation is complete, otherwise returns.
    pub fn finished(&self) -> bool {
        self.finished
    }
}

#[cfg(not(esp32))]
impl<A: ShaAlgorithm> Default for Context<A> {
    fn default() -> Self {
        Self::new()
    }
}

/// This trait encapsulates the configuration for a specific SHA algorithm.
pub trait ShaAlgorithm: crate::private::Sealed {
    /// Constant containing the name of the algorithm as a string.
    const ALGORITHM: &'static str;

    /// The length of the chunk that the algorithm processes at a time.
    ///
    /// For example, in SHA-256, this would typically be 64 bytes.
    const CHUNK_LENGTH: usize;

    /// The length of the resulting digest produced by the algorithm.
    ///
    /// For example, in SHA-256, this would be 32 bytes.
    const DIGEST_LENGTH: usize;

    #[cfg(feature = "digest")]
    #[doc(hidden)]
    type DigestOutputSize: digest::generic_array::ArrayLength<u8> + 'static;

    #[cfg(not(esp32))]
    #[doc(hidden)]
    const MODE_AS_BITS: u8;

    #[cfg(esp32)]
    #[doc(hidden)]
    // Initiate the operation
    fn start(sha: &crate::peripherals::SHA);

    #[cfg(esp32)]
    #[doc(hidden)]
    // Continue the operation
    fn r#continue(sha: &crate::peripherals::SHA);

    #[cfg(esp32)]
    #[doc(hidden)]
    // Calculate the final hash
    fn load(sha: &crate::peripherals::SHA);

    #[cfg(esp32)]
    #[doc(hidden)]
    // Check if peripheral is busy
    fn is_busy(sha: &crate::peripherals::SHA) -> bool;
}

/// implement digest traits if digest feature is present.
/// Note: digest has a blanket trait implementation for [digest::Digest] for any
/// element that implements FixedOutput + Default + Update + HashMarker
#[cfg(feature = "digest")]
impl<'d, A: ShaAlgorithm, S: Borrow<Sha<'d>>> digest::HashMarker for ShaDigest<'d, A, S> {}

#[cfg(feature = "digest")]
impl<'d, A: ShaAlgorithm, S: Borrow<Sha<'d>>> digest::OutputSizeUser for ShaDigest<'d, A, S> {
    type OutputSize = A::DigestOutputSize;
}

#[cfg(feature = "digest")]
impl<'d, A: ShaAlgorithm, S: Borrow<Sha<'d>>> digest::Update for ShaDigest<'d, A, S> {
    fn update(&mut self, data: &[u8]) {
        let mut remaining = data.as_ref();
        while !remaining.is_empty() {
            remaining = nb::block!(Self::update(self, remaining)).unwrap();
        }
    }
}

#[cfg(feature = "digest")]
impl<'d, A: ShaAlgorithm, S: Borrow<Sha<'d>>> digest::FixedOutput for ShaDigest<'d, A, S> {
    fn finalize_into(mut self, out: &mut digest::Output<Self>) {
        nb::block!(self.finish(out)).unwrap();
    }
}

/// This macro implements the Sha<'a, Dm> trait for a specified Sha algorithm
/// and a set of parameters
macro_rules! impl_sha {
    ($name: ident, $mode_bits: tt, $digest_length: tt, $chunk_length: tt) => {
        /// A SHA implementation struct.
        ///
        /// This struct is generated by the macro and represents a specific SHA hashing
        /// algorithm (e.g., SHA-256, SHA-1). It manages the context and state required
        /// for processing data using the selected hashing algorithm.
        ///
        /// The struct provides various functionalities such as initializing the hashing
        /// process, updating the internal state with new data, and finalizing the
        /// hashing operation to generate the final digest.
        #[non_exhaustive]
        pub struct $name;

        impl crate::private::Sealed for $name {}

        impl $crate::sha::ShaAlgorithm for $name {
            const ALGORITHM: &'static str = stringify!($name);

            const CHUNK_LENGTH: usize = $chunk_length;

            const DIGEST_LENGTH: usize = $digest_length;

            #[cfg(not(esp32))]
            const MODE_AS_BITS: u8 = $mode_bits;

            #[cfg(feature = "digest")]
            // We use paste to append `U` to the digest size to match a const defined in
            // digest
            type DigestOutputSize = paste::paste!(digest::consts::[< U $digest_length >]);

            #[cfg(esp32)]
            fn start(sha: &crate::peripherals::SHA) {
                paste::paste! {
                    sha.register_block().[< $name:lower _start >]().write(|w| w.[< $name:lower _start >]().set_bit());
                }
            }

            #[cfg(esp32)]
            fn r#continue(sha: &crate::peripherals::SHA) {
                paste::paste! {
                    sha.register_block().[< $name:lower _continue >]().write(|w| w.[< $name:lower _continue >]().set_bit());
                }
            }

            #[cfg(esp32)]
            fn load(sha: &crate::peripherals::SHA) {
                paste::paste! {
                    sha.register_block().[< $name:lower _load >]().write(|w| w.[< $name:lower _load >]().set_bit());
                }
            }

            #[cfg(esp32)]
            fn is_busy(sha: &crate::peripherals::SHA) -> bool {
                paste::paste! {
                    sha.register_block().[< $name:lower _busy >]().read().[< $name:lower _busy >]().bit_is_set()
                }
            }
        }
    };
}

// All the hash algorithms introduced in FIPS PUB 180-4 Spec.
// – SHA-1
// – SHA-224
// – SHA-256
// – SHA-384
// – SHA-512
// – SHA-512/224
// – SHA-512/256
// – SHA-512/t (not implemented yet)
// Two working modes
// – Typical SHA
// – DMA-SHA (not implemented yet)
//
// TODO: Allow/Implement SHA512_(u16)
impl_sha!(Sha1, 0, 20, 64);
#[cfg(not(esp32))]
impl_sha!(Sha224, 1, 28, 64);
impl_sha!(Sha256, 2, 32, 64);
#[cfg(any(esp32, esp32s2, esp32s3))]
impl_sha!(Sha384, 3, 48, 128);
#[cfg(any(esp32, esp32s2, esp32s3))]
impl_sha!(Sha512, 4, 64, 128);
#[cfg(any(esp32s2, esp32s3))]
impl_sha!(Sha512_224, 5, 28, 128);
#[cfg(any(esp32s2, esp32s3))]
impl_sha!(Sha512_256, 6, 32, 128);

fn h_mem(sha: &crate::peripherals::SHA, index: usize) -> *mut u32 {
    let sha = sha.register_block();
    cfg_if::cfg_if! {
        if #[cfg(esp32)] {
            sha.text(index).as_ptr()
        } else {
            sha.h_mem(index).as_ptr()
        }
    }
}

fn m_mem(sha: &crate::peripherals::SHA, index: usize) -> *mut u32 {
    let sha = sha.register_block();
    cfg_if::cfg_if! {
        if #[cfg(esp32)] {
            sha.text(index).as_ptr()
        } else {
            sha.m_mem(index).as_ptr()
        }
    }
}