mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 21:00:59 +00:00
Crypto work queue [AES] (#3880)
* Implement a generic work queue * Implement AES work queue backend with software block modes * Move tail after enqueueing * Use NonNull more liberally * Tweak the queue a bit * Merge the AES-DMA test into the AES test suite * Drop AesFlavour * Document things * Stop the driver when its handle is dropped * Fix docs * Address some correctness concerns
This commit is contained in:
parent
3c12be8d24
commit
3ec26ead9d
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `TrngSource` to manage random number generator entropy (#3829)
|
||||
- On RISC-V you can opt-out of nested interrupts for an interrupt handler by using `new_not_nested` (#3875)
|
||||
- A new default feature `exception-handler` was added (#3887)
|
||||
- `AesBackend, AesContext`: Work-queue based AES driver (#3880)
|
||||
|
||||
### Changed
|
||||
|
||||
@ -32,7 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Removed
|
||||
|
||||
- `Trng::new` (replaced by `Trng::try_new`) (#3829)
|
||||
- `AesDma::write_block` has been removed. (#3882)
|
||||
- `AesDma::{write_key, write_block}` have been removed. (#3880, #3882)
|
||||
- `AesFlavour` trait and `AesX` structs have been removed. (#3880)
|
||||
|
||||
## [v1.0.0-rc.0] - 2025-07-16
|
||||
|
||||
|
299
esp-hal/src/aes/cipher_modes.rs
Normal file
299
esp-hal/src/aes/cipher_modes.rs
Normal file
@ -0,0 +1,299 @@
|
||||
//! Software implementations of the supported block cipher operating modes.
|
||||
//!
|
||||
//! These may be used by the Typical AES operating mode, as well as by the DMA-enabled driver where
|
||||
//! the DMA does not support a particular operating mode in hardware.
|
||||
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use super::{BLOCK_SIZE, CryptoBuffers};
|
||||
|
||||
/// Electronic codebook mode.
|
||||
pub(super) struct Ecb {}
|
||||
impl Ecb {
|
||||
pub(super) fn encrypt_decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
buffer.for_data_chunks(BLOCK_SIZE, |input, output, len| {
|
||||
process_block(
|
||||
NonNull::slice_from_raw_parts(input, len),
|
||||
NonNull::slice_from_raw_parts(output, len),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Cipher block chaining mode.
|
||||
pub(super) struct Cbc {
|
||||
pub(super) iv: [u8; BLOCK_SIZE],
|
||||
}
|
||||
impl Cbc {
|
||||
pub(super) fn encrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
|
||||
buffer.for_data_chunks(BLOCK_SIZE, |plaintext, ciphertext, len| {
|
||||
// Block input is feedback/IV mixed with plaintext. We can let this overwrite the IV
|
||||
// because we'll overwrite this again with the ciphertext as the next IV.
|
||||
xor_into(iv.cast(), plaintext, len);
|
||||
|
||||
// Block output is ciphertext directly
|
||||
process_block(iv, NonNull::slice_from_raw_parts(ciphertext, len));
|
||||
|
||||
// Feed back the ciphertext for the next iteration.
|
||||
copy(iv.cast(), ciphertext, len);
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
|
||||
buffer.for_data_chunks(BLOCK_SIZE, |ciphertext, plaintext, len| {
|
||||
// Block output is plaintext, mixed with IV
|
||||
process_block(iv, NonNull::slice_from_raw_parts(plaintext, len));
|
||||
xor_into(plaintext, iv.cast(), len);
|
||||
|
||||
// Next IV is the previous ciphertext
|
||||
copy(iv.cast(), ciphertext, len);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Output feedback mode.
|
||||
pub(super) struct Ofb {
|
||||
pub(super) iv: [u8; BLOCK_SIZE],
|
||||
pub(super) offset: usize,
|
||||
}
|
||||
impl Ofb {
|
||||
pub(super) fn encrypt_decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let mut offset = self.offset;
|
||||
buffer.for_data_chunks(1, |plaintext, ciphertext, _| {
|
||||
if offset == 0 {
|
||||
// Out of bytes, generate next key.
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
process_block(iv, iv);
|
||||
}
|
||||
|
||||
// Calculate ciphertext by mixing the key with the plaintext.
|
||||
unsafe { ciphertext.write(plaintext.read() ^ self.iv[offset]) };
|
||||
offset = (offset + 1) % BLOCK_SIZE;
|
||||
});
|
||||
self.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Counter mode.
|
||||
pub(super) struct Ctr {
|
||||
/// The nonce + counter. Note that the security of this relies on the nonce being random.
|
||||
pub(super) nonce: [u8; BLOCK_SIZE],
|
||||
/// The key produced by the block cipher.
|
||||
pub(super) buffer: [u8; BLOCK_SIZE],
|
||||
pub(super) offset: usize,
|
||||
}
|
||||
impl Ctr {
|
||||
pub(super) fn encrypt_decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
fn increment(nonce: &mut [u8]) {
|
||||
for byte in nonce.iter_mut().rev() {
|
||||
*byte = byte.wrapping_add(1);
|
||||
if *byte != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut offset = self.offset;
|
||||
buffer.for_data_chunks(1, |plaintext, ciphertext, _| {
|
||||
if offset == 0 {
|
||||
let nonce = NonNull::from(self.nonce.as_mut());
|
||||
let buffer = NonNull::from(self.buffer.as_mut());
|
||||
// Block input is feedback/IV. Block output is feedback/IV.
|
||||
process_block(nonce, buffer);
|
||||
increment(&mut self.nonce);
|
||||
}
|
||||
|
||||
// Calculate ciphertext by mixing the key with the plaintext.
|
||||
unsafe { ciphertext.write(plaintext.read() ^ self.buffer[offset]) };
|
||||
offset = (offset + 1) % BLOCK_SIZE;
|
||||
});
|
||||
self.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cipher feedback with 8-bit shift mode.
|
||||
pub(super) struct Cfb8 {
|
||||
pub(super) iv: [u8; BLOCK_SIZE],
|
||||
}
|
||||
impl Cfb8 {
|
||||
pub(super) fn encrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let mut ov = [0; BLOCK_SIZE];
|
||||
buffer.for_data_chunks(1, |plaintext, ciphertext, _| {
|
||||
ov.copy_from_slice(&self.iv);
|
||||
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
process_block(iv, iv);
|
||||
|
||||
unsafe {
|
||||
let out = self.iv[0] ^ plaintext.read();
|
||||
ciphertext.write(out);
|
||||
|
||||
// Shift the key by a byte.
|
||||
self.iv[0..BLOCK_SIZE - 1].copy_from_slice(&ov[1..BLOCK_SIZE]);
|
||||
self.iv[BLOCK_SIZE - 1] = ciphertext.read();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let mut ov = [0; BLOCK_SIZE];
|
||||
buffer.for_data_chunks(1, |ciphertext, plaintext, _| {
|
||||
ov.copy_from_slice(&self.iv);
|
||||
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
process_block(iv, iv);
|
||||
|
||||
unsafe {
|
||||
let c = ciphertext.read();
|
||||
let out = self.iv[0] ^ c;
|
||||
plaintext.write(out);
|
||||
|
||||
// Shift the key by a byte.
|
||||
self.iv[0..BLOCK_SIZE - 1].copy_from_slice(&ov[1..BLOCK_SIZE]);
|
||||
self.iv[BLOCK_SIZE - 1] = c;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Cipher feedback with 128-bit shift mode.
|
||||
pub(super) struct Cfb128 {
|
||||
pub(super) iv: [u8; BLOCK_SIZE],
|
||||
pub(super) offset: usize,
|
||||
}
|
||||
impl Cfb128 {
|
||||
pub(super) fn encrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let mut offset = self.offset;
|
||||
buffer.for_data_chunks(1, |plaintext, ciphertext, _| {
|
||||
if offset == 0 {
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
process_block(iv, iv);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.iv[offset] ^= plaintext.read();
|
||||
ciphertext.write(self.iv[offset]);
|
||||
}
|
||||
offset = (offset + 1) % BLOCK_SIZE;
|
||||
});
|
||||
self.offset = offset;
|
||||
}
|
||||
|
||||
pub(super) fn decrypt(
|
||||
&mut self,
|
||||
buffer: CryptoBuffers,
|
||||
mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>),
|
||||
) {
|
||||
let mut offset = self.offset;
|
||||
buffer.for_data_chunks(1, |ciphertext, plaintext, _| {
|
||||
if offset == 0 {
|
||||
let iv = NonNull::from(self.iv.as_mut());
|
||||
process_block(iv, iv);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let c = ciphertext.read();
|
||||
plaintext.write(self.iv[offset] ^ c);
|
||||
self.iv[offset] = c;
|
||||
}
|
||||
offset = (offset + 1) % BLOCK_SIZE;
|
||||
});
|
||||
self.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
impl CryptoBuffers {
|
||||
fn for_data_chunks(
|
||||
self,
|
||||
chunk_size: usize,
|
||||
mut cb: impl FnMut(NonNull<u8>, NonNull<u8>, usize),
|
||||
) {
|
||||
let input = pointer_chunks(self.input, chunk_size);
|
||||
let output = pointer_chunks(self.output, chunk_size);
|
||||
|
||||
for (input, output, len) in input
|
||||
.zip(output)
|
||||
.map(|((input, len), (output, _))| (input, output, len))
|
||||
{
|
||||
cb(input, output, len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pointer_chunks<T>(
|
||||
ptr: NonNull<[T]>,
|
||||
chunk: usize,
|
||||
) -> impl Iterator<Item = (NonNull<T>, usize)> + Clone {
|
||||
let mut len = ptr.len();
|
||||
let mut ptr = ptr.cast::<T>();
|
||||
core::iter::from_fn(move || {
|
||||
let advance = if len > chunk {
|
||||
chunk
|
||||
} else if len > 0 {
|
||||
len
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let retval = (ptr, advance);
|
||||
|
||||
unsafe { ptr = ptr.add(advance) };
|
||||
len -= advance;
|
||||
Some(retval)
|
||||
})
|
||||
}
|
||||
|
||||
fn xor_into(mut out: NonNull<u8>, mut a: NonNull<u8>, len: usize) {
|
||||
let end = unsafe { out.add(len) };
|
||||
while out < end {
|
||||
unsafe {
|
||||
out.write(out.read() ^ a.read());
|
||||
a = a.add(1);
|
||||
out = out.add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn copy(out: NonNull<u8>, from: NonNull<u8>, len: usize) {
|
||||
unsafe {
|
||||
out.copy_from(from, len);
|
||||
}
|
||||
}
|
@ -43,16 +43,13 @@
|
||||
//! // The decryption happens in-place, so the plaintext is in `block`
|
||||
//! # {after_snippet}
|
||||
//! ```
|
||||
//!
|
||||
//! ### AES-DMA
|
||||
//!
|
||||
//! Visit the [AES-DMA] test for a more advanced example of using AES-DMA
|
||||
//! mode.
|
||||
//!
|
||||
//! [AES-DMA]: https://github.com/esp-rs/esp-hal/blob/main/hil-test/tests/aes_dma.rs
|
||||
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use crate::{pac, peripherals::AES, system::GenericPeripheralGuard};
|
||||
|
||||
mod cipher_modes;
|
||||
|
||||
for_each_aes_key_length! {
|
||||
($len:literal) => {
|
||||
// Implementing From for easy conversion from array to Key enum.
|
||||
@ -63,16 +60,6 @@ for_each_aes_key_length! {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
#[doc = concat!("Marker type for AES-", stringify!($len))]
|
||||
pub struct [<Aes $len>];
|
||||
|
||||
impl crate::private::Sealed for [<Aes $len>] {}
|
||||
impl AesFlavour for [<Aes $len>] {
|
||||
type KeyType<'b> = &'b [u8; $len / 8];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(bits $( ($len:literal) ),*) => {
|
||||
@ -264,19 +251,70 @@ impl<'d> Aes<'d> {
|
||||
let mode = key.decrypt_mode();
|
||||
self.process(block, mode, key)
|
||||
}
|
||||
|
||||
/// Encrypts/Decrypts the given buffer based on `mode` parameter
|
||||
fn process_work_item(&mut self, work_item: &mut AesOperation) {
|
||||
// Note that we can't just create slices out of the input and output buffers, because they
|
||||
// may alias (when encrypting/decrypting data in place).
|
||||
|
||||
let slice = work_item.key.as_slice();
|
||||
self.write_key(slice);
|
||||
unsafe {
|
||||
match work_item.cipher_mode.as_ref() {
|
||||
CipherModeState::Ecb(_) | CipherModeState::Cbc(_) => {
|
||||
self.write_mode(if work_item.mode == Operation::Encrypt {
|
||||
work_item.key.encrypt_mode()
|
||||
} else {
|
||||
work_item.key.decrypt_mode()
|
||||
})
|
||||
}
|
||||
CipherModeState::Ofb(_)
|
||||
| CipherModeState::Ctr(_)
|
||||
| CipherModeState::Cfb8(_)
|
||||
| CipherModeState::Cfb128(_) => self.write_mode(work_item.key.encrypt_mode()),
|
||||
}
|
||||
}
|
||||
|
||||
let process_block = |input: NonNull<[u8]>, mut output: NonNull<[u8]>| {
|
||||
unsafe { self.write_block(input.as_ref()) };
|
||||
self.start();
|
||||
while !self.is_idle() {}
|
||||
unsafe { self.read_block(output.as_mut()) };
|
||||
};
|
||||
|
||||
// Safety: the reference to the algorithm state is only held for the duration of the
|
||||
// operation.
|
||||
match unsafe { work_item.cipher_mode.as_mut() } {
|
||||
CipherModeState::Ecb(algo) => algo.encrypt_decrypt(work_item.buffers, process_block),
|
||||
CipherModeState::Cbc(algo) => {
|
||||
if work_item.mode == Operation::Encrypt {
|
||||
algo.encrypt(work_item.buffers, process_block);
|
||||
} else {
|
||||
algo.decrypt(work_item.buffers, process_block);
|
||||
}
|
||||
}
|
||||
CipherModeState::Ofb(algo) => algo.encrypt_decrypt(work_item.buffers, process_block),
|
||||
CipherModeState::Ctr(algo) => algo.encrypt_decrypt(work_item.buffers, process_block),
|
||||
CipherModeState::Cfb8(algo) => {
|
||||
if work_item.mode == Operation::Encrypt {
|
||||
algo.encrypt(work_item.buffers, process_block)
|
||||
} else {
|
||||
algo.decrypt(work_item.buffers, process_block)
|
||||
}
|
||||
}
|
||||
CipherModeState::Cfb128(algo) => {
|
||||
if work_item.mode == Operation::Encrypt {
|
||||
algo.encrypt(work_item.buffers, process_block)
|
||||
} else {
|
||||
algo.decrypt(work_item.buffers, process_block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifications for AES flavours
|
||||
pub trait AesFlavour: crate::private::Sealed {
|
||||
/// Type of the AES key, a fixed-size array of bytes
|
||||
///
|
||||
/// The size of this type depends on various factors, such as the device
|
||||
/// being targeted and the desired key size.
|
||||
type KeyType<'b>;
|
||||
}
|
||||
|
||||
/// State matrix endianness
|
||||
#[cfg(any(esp32, esp32s2))]
|
||||
/// Data endianness
|
||||
#[cfg(aes_endianness_configurable)]
|
||||
pub enum Endianness {
|
||||
/// Big endian (most-significant byte at the smallest address)
|
||||
BigEndian = 1,
|
||||
@ -309,8 +347,6 @@ pub mod dma {
|
||||
system::{Peripheral, PeripheralClockControl},
|
||||
};
|
||||
|
||||
const ALIGN_SIZE: usize = core::mem::size_of::<u32>();
|
||||
|
||||
/// Specifies the block cipher modes available for AES operations.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CipherMode {
|
||||
@ -343,7 +379,7 @@ pub mod dma {
|
||||
channel: Channel<Blocking, PeripheralDmaChannel<AES<'d>>>,
|
||||
}
|
||||
|
||||
impl<'d> crate::aes::Aes<'d> {
|
||||
impl<'d> super::Aes<'d> {
|
||||
/// Enable DMA for the current instance of the AES driver
|
||||
pub fn with_dma(self, channel: impl DmaChannelFor<AES<'d>>) -> AesDma<'d> {
|
||||
let channel = Channel::new(channel.degrade());
|
||||
@ -359,13 +395,9 @@ pub mod dma {
|
||||
}
|
||||
|
||||
impl<'d> AesDma<'d> {
|
||||
/// Writes the encryption key to the AES hardware, checking that its
|
||||
/// length matches expected constraints.
|
||||
pub fn write_key(&mut self, key: impl Into<Key>) {
|
||||
let key = key.into(); // Convert into Key enum
|
||||
fn write_key(&mut self, key: impl Into<Key>) {
|
||||
let key = key.into();
|
||||
let key = key.as_slice();
|
||||
debug_assert!(key.len() <= 8 * ALIGN_SIZE);
|
||||
debug_assert_eq!(key.len() % ALIGN_SIZE, 0);
|
||||
self.aes.write_key(key);
|
||||
}
|
||||
|
||||
@ -562,6 +594,497 @@ pub mod dma {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::work_queue::{
|
||||
Handle,
|
||||
Poll,
|
||||
Status,
|
||||
VTable,
|
||||
WorkQueue,
|
||||
WorkQueueDriver,
|
||||
WorkQueueFrontend,
|
||||
};
|
||||
|
||||
/// Specifies the block cipher modes available for AES operations.
|
||||
///
|
||||
/// In DMA-driven mode, if a particular mode is not supported by the hardware, the driver will fall
|
||||
/// back to a CPU-driven method.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CipherMode {
|
||||
/// Electronic Codebook Mode
|
||||
Ecb,
|
||||
/// Cipher Block Chaining Mode
|
||||
Cbc,
|
||||
/// Output Feedback Mode
|
||||
Ofb,
|
||||
/// Counter Mode
|
||||
Ctr,
|
||||
/// Cipher Feedback Mode with 8-bit shifting.
|
||||
Cfb8,
|
||||
/// Cipher Feedback Mode with 128-bit shifting.
|
||||
Cfb128,
|
||||
/// Galois Counter Mode
|
||||
Gcm,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CryptoBuffers {
|
||||
input: NonNull<[u8]>,
|
||||
output: NonNull<[u8]>,
|
||||
text_length: usize,
|
||||
}
|
||||
|
||||
enum CipherModeState {
|
||||
/// Electronic Codebook Mode
|
||||
// TODO: for this, we need text_length to be a muliple of BLOCK_SIZE
|
||||
Ecb(cipher_modes::Ecb),
|
||||
/// Cipher Block Chaining Mode
|
||||
// TODO: for this, we need text_length to be a muliple of BLOCK_SIZE
|
||||
Cbc(cipher_modes::Cbc),
|
||||
/// Output Feedback Mode
|
||||
Ofb(cipher_modes::Ofb),
|
||||
/// Counter Mode
|
||||
Ctr(cipher_modes::Ctr),
|
||||
/// Cipher Feedback Mode with 8-bit shifting.
|
||||
Cfb8(cipher_modes::Cfb8),
|
||||
/// Cipher Feedback Mode with 128-bit shifting.
|
||||
Cfb128(cipher_modes::Cfb128),
|
||||
// Galois Counter Mode
|
||||
// Gcm(*mut Gcm), // TODO: this is more involved
|
||||
}
|
||||
|
||||
const BLOCK_SIZE: usize = 16;
|
||||
|
||||
struct AesOperation {
|
||||
mode: Operation,
|
||||
// The block cipher operating mode.
|
||||
cipher_mode: NonNull<CipherModeState>,
|
||||
// The length of the key is determined by the operating mode.
|
||||
buffers: CryptoBuffers,
|
||||
key: Key,
|
||||
}
|
||||
|
||||
// Safety: AesOperation is safe to share between threads, in the context of a WorkQueue. The
|
||||
// WorkQueue ensures that only a single location can access the data. All the internals, except
|
||||
// for the pointers, are Sync. The pointers are safe to share because they point at data that the
|
||||
// AES driver ensures can be accessed safely and soundly.
|
||||
unsafe impl Sync for AesOperation {}
|
||||
|
||||
static AES_WORK_QUEUE: WorkQueue<AesOperation> = WorkQueue::new();
|
||||
const BLOCKING_AES_VTABLE: VTable<AesOperation> = VTable {
|
||||
post: |driver, _item| {
|
||||
// Since we run the operation in `poll`, there is nothing to do here, besides accepting the
|
||||
// work item. However, DS may be using the AES peripheral. We solve that problem (later) by
|
||||
// taking a lock in `on_work_available`
|
||||
let driver = unsafe { AesBackend::from_raw(driver) };
|
||||
driver.ensure_initialized()
|
||||
},
|
||||
poll: |driver, item| {
|
||||
// Fully process the work item. A single CPU-driven AES operation would complete
|
||||
// faster than we could poll the queue with all the locking around it.
|
||||
let driver = unsafe { AesBackend::from_raw(driver) };
|
||||
driver.process(item)
|
||||
},
|
||||
cancel: |_driver, _item| {
|
||||
// To achieve a decent performance in Typical AES mode, we run the operations in a blocking
|
||||
// manner and so they can't be cancelled.
|
||||
},
|
||||
stop: |driver| {
|
||||
// Drop the AES driver to conserve power when there is nothig to do (or when the driver was
|
||||
// stopped).
|
||||
let driver = unsafe { AesBackend::from_raw(driver) };
|
||||
driver.deinitialize()
|
||||
},
|
||||
};
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// CPU-driven AES processing backend.
|
||||
///
|
||||
/// Due to how this backend works, it provides no `run` method(s). Posting work to this backend will
|
||||
/// immediately be executed, in a blocking way. The backend only needs to be kept alive in order to
|
||||
/// function.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # {before_snippet}
|
||||
/// use esp_hal::aes::{AesBackend, AesContext, Operation};
|
||||
/// #
|
||||
/// let mut aes = AesBackend::new(peripherals.AES);
|
||||
/// // Start the backend, which allows processing AES operations.
|
||||
/// let _backend = aes.start();
|
||||
///
|
||||
/// // Create a new context with a 128-bit key. The context will use the ECB block cipher mode.
|
||||
/// // The key length must be supported by the hardware.
|
||||
/// let mut ecb_encrypt = AesContext::ecb(
|
||||
/// Operation::Encrypt,
|
||||
/// [
|
||||
/// b'S', b'U', b'p', b'4', b'S', b'e', b'C', b'p', b'@', b's', b'S', b'w', b'0', b'r',
|
||||
/// b'd', 0,
|
||||
/// ],
|
||||
/// );
|
||||
///
|
||||
/// // Process a block of data in this context. The ECB operating mode requires that
|
||||
/// // the length of the data is a multiple of the block (16 bytes) size.
|
||||
/// let input_buffer: [u8; 16] = [
|
||||
/// b'm', b'e', b's', b's', b'a', b'g', b'e', 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/// ];
|
||||
/// let mut output_buffer: [u8; 16] = [0; 16];
|
||||
///
|
||||
/// let operation_handle = ecb_encrypt
|
||||
/// .process(&input_buffer, &mut output_buffer)
|
||||
/// .unwrap();
|
||||
/// operation_handle.wait_blocking();
|
||||
///
|
||||
/// // output_buffer now contains the ciphertext
|
||||
/// assert_eq!(
|
||||
/// output_buffer,
|
||||
/// [
|
||||
/// 0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a,
|
||||
/// 0x31, 0x96,
|
||||
/// ]
|
||||
/// );
|
||||
/// #
|
||||
/// # {after_snippet}
|
||||
/// ```
|
||||
pub struct AesBackend<'d> {
|
||||
peri: AES<'d>,
|
||||
driver: Option<Aes<'d>>,
|
||||
}
|
||||
|
||||
impl<'d> AesBackend<'d> {
|
||||
#[procmacros::doc_replace]
|
||||
/// Creates a new AES backend.
|
||||
///
|
||||
/// The backend needs to be [`start`][Self::start]ed before it can execute AES operations.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # {before_snippet}
|
||||
/// use esp_hal::aes::AesBackend;
|
||||
/// #
|
||||
/// let mut aes = AesBackend::new(peripherals.AES);
|
||||
/// # {after_snippet}
|
||||
/// ```
|
||||
pub fn new(aes: AES<'d>) -> Self {
|
||||
Self {
|
||||
peri: aes,
|
||||
driver: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Registers the CPU-driven AES driver to process AES operations.
|
||||
///
|
||||
/// The driver stops operating when the returned object is dropped.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # {before_snippet}
|
||||
/// use esp_hal::aes::AesBackend;
|
||||
/// #
|
||||
/// let mut aes = AesBackend::new(peripherals.AES);
|
||||
/// // Start the backend, which allows processing AES operations.
|
||||
/// let _backend = aes.start();
|
||||
/// # {after_snippet}
|
||||
/// ```
|
||||
pub fn start(&mut self) -> AesWorkQueueDriver<'_, 'd> {
|
||||
AesWorkQueueDriver {
|
||||
_inner: WorkQueueDriver::new(self, BLOCKING_AES_VTABLE, &AES_WORK_QUEUE),
|
||||
}
|
||||
}
|
||||
|
||||
// WorkQueue callbacks. They may run in any context.
|
||||
|
||||
unsafe fn from_raw<'any>(ptr: *const ()) -> &'any mut Self {
|
||||
unsafe { unwrap!(ptr.cast_mut().cast::<AesBackend<'_>>().as_mut()) }
|
||||
}
|
||||
|
||||
fn process(&mut self, item: &mut AesOperation) -> Option<Poll> {
|
||||
let driver = self.driver.as_mut()?;
|
||||
|
||||
driver.process_work_item(item);
|
||||
|
||||
Some(Poll::Ready(Status::Completed))
|
||||
}
|
||||
|
||||
fn ensure_initialized(&mut self) -> bool {
|
||||
if self.driver.is_none() {
|
||||
let peri = unsafe { self.peri.clone_unchecked() };
|
||||
self.driver = Some(Aes::new(peri));
|
||||
}
|
||||
// TODO: when DS is implemented, it will need to be able to lock out AES. In that case,
|
||||
// this function should return `false` if the AES peripheral is locked.
|
||||
true
|
||||
}
|
||||
|
||||
fn deinitialize(&mut self) {
|
||||
self.driver = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// An error related to an AES operation.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// The input and output buffers have different lengths.
|
||||
BuffersNotEqual,
|
||||
|
||||
/// The buffer length is not appropriate for the current cipher mode.
|
||||
IncorrectBufferLength,
|
||||
}
|
||||
|
||||
/// An active work queue driver.
|
||||
///
|
||||
/// This object must be kept around, otherwise AES operations will never complete.
|
||||
pub struct AesWorkQueueDriver<'t, 'd> {
|
||||
_inner: WorkQueueDriver<'t, AesBackend<'d>, AesOperation>,
|
||||
}
|
||||
|
||||
/// An AES work queue user.
|
||||
pub struct AesContext {
|
||||
cipher_mode: CipherModeState,
|
||||
frontend: WorkQueueFrontend<AesOperation>,
|
||||
}
|
||||
|
||||
impl AesContext {
|
||||
fn new(cipher_mode: CipherModeState, operation: Operation, key: Key) -> Self {
|
||||
Self {
|
||||
cipher_mode,
|
||||
frontend: WorkQueueFrontend::new(AesOperation {
|
||||
mode: operation,
|
||||
cipher_mode: NonNull::dangling(),
|
||||
buffers: CryptoBuffers {
|
||||
input: NonNull::from(&[]),
|
||||
output: NonNull::from(&[]),
|
||||
text_length: 0,
|
||||
},
|
||||
key,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Electronic Codebook operating
|
||||
/// mode.
|
||||
pub fn ecb(operation: Operation, key: impl Into<Key>) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Ecb(cipher_modes::Ecb {}),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Cipher Block Chaining operating
|
||||
/// mode.
|
||||
pub fn cbc(operation: Operation, iv: [u8; BLOCK_SIZE], key: impl Into<Key>) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Cbc(cipher_modes::Cbc { iv }),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Counter operating
|
||||
/// mode.
|
||||
pub fn ctr(operation: Operation, key: impl Into<Key>, nonce: [u8; BLOCK_SIZE]) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Ctr(cipher_modes::Ctr {
|
||||
nonce,
|
||||
buffer: [0; BLOCK_SIZE],
|
||||
offset: 0,
|
||||
}),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Cipher feedback (with 8-bit
|
||||
/// shifting) operating mode.
|
||||
pub fn cfb8(operation: Operation, key: impl Into<Key>, iv: [u8; BLOCK_SIZE]) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Cfb8(cipher_modes::Cfb8 { iv }),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Cipher feedback (with 8-bit
|
||||
/// shifting) operating mode.
|
||||
pub fn cfb128(operation: Operation, key: impl Into<Key>, iv: [u8; BLOCK_SIZE]) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Cfb128(cipher_modes::Cfb128 { iv, offset: 0 }),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new context to encrypt or decrypt data with the Output feedback operating mode.
|
||||
pub fn ofb(operation: Operation, key: impl Into<Key>, iv: [u8; BLOCK_SIZE]) -> Self {
|
||||
Self::new(
|
||||
CipherModeState::Ofb(cipher_modes::Ofb { iv, offset: 0 }),
|
||||
operation,
|
||||
key.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn post(&mut self) -> AesHandle<'_> {
|
||||
AesHandle(self.frontend.post(&AES_WORK_QUEUE))
|
||||
}
|
||||
|
||||
fn validate(&self, buffer: &[u8]) -> Result<(), Error> {
|
||||
// TODO: check that data is appropriate for the cipher mode
|
||||
let requires_block_aligned = matches!(
|
||||
self.cipher_mode,
|
||||
CipherModeState::Ecb(_) | CipherModeState::Ofb(_)
|
||||
);
|
||||
|
||||
if requires_block_aligned && !buffer.len().is_multiple_of(BLOCK_SIZE) {
|
||||
return Err(Error::IncorrectBufferLength);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Starts transforming the input buffer, and writes the result into the output buffer.
|
||||
///
|
||||
/// - For encryption the input is the plaintext, the output is the ciphertext.
|
||||
/// - For decryption the input is the ciphertext, the output is the plaintext.
|
||||
///
|
||||
/// The returned Handle must be polled until it returns [`Poll::Ready`]. Dropping the handle
|
||||
/// before the operation finishes will cancel the operation.
|
||||
///
|
||||
/// For an example, see the documentation of [`AesBackend`].
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// - If the lengths of the input and output buffers don't match, an error is returned.
|
||||
/// - The ECB and OFB cipher modes require the data length to be a multiple of the block size
|
||||
/// (16), otherwise an error is returned.
|
||||
pub fn process<'t>(
|
||||
&'t mut self,
|
||||
input: &'t [u8],
|
||||
output: &'t mut [u8],
|
||||
) -> Result<AesHandle<'t>, Error> {
|
||||
if input.len() != output.len() {
|
||||
return Err(Error::BuffersNotEqual);
|
||||
}
|
||||
self.validate(input)?;
|
||||
self.validate(output)?;
|
||||
|
||||
let data = self.frontend.data_mut();
|
||||
data.cipher_mode = NonNull::from(&mut self.cipher_mode);
|
||||
data.buffers.text_length = input.len();
|
||||
data.buffers.input = NonNull::from(input);
|
||||
data.buffers.output = NonNull::from(output);
|
||||
|
||||
Ok(self.post())
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Starts transforming the buffer.
|
||||
///
|
||||
/// The processed data will be written back to the `buffer`.
|
||||
///
|
||||
/// The returned Handle must be polled until it returns [`Poll::Ready`]. Dropping the handle
|
||||
/// before the operation finishes will cancel the operation.
|
||||
///
|
||||
/// This function operates similar to [`AesContext::process`], but it overwrites the data buffer
|
||||
/// with the result of the transformation.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # {before_snippet}
|
||||
/// use esp_hal::aes::{AesBackend, AesContext, Operation};
|
||||
/// #
|
||||
/// let mut aes = AesBackend::new(peripherals.AES);
|
||||
/// // Start the backend, which pins it in place and allows processing AES operations.
|
||||
/// let _backend = aes.start();
|
||||
///
|
||||
/// // Create a new context with a 128-bit key. The context will use the ECB block cipher mode.
|
||||
/// // The key length must be supported by the hardware.
|
||||
/// let mut ecb_encrypt = AesContext::ecb(
|
||||
/// Operation::Encrypt,
|
||||
/// [
|
||||
/// b'S', b'U', b'p', b'4', b'S', b'e', b'C', b'p', b'@', b's', b'S', b'w', b'0', b'r',
|
||||
/// b'd', 0,
|
||||
/// ],
|
||||
/// );
|
||||
///
|
||||
/// // Process a block of data in this context, in place. The ECB operating mode requires that
|
||||
/// // the length of the data is a multiple of the block (16 bytes) size.
|
||||
/// let mut buffer: [u8; 16] = [
|
||||
/// b'm', b'e', b's', b's', b'a', b'g', b'e', 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/// ];
|
||||
///
|
||||
/// let operation_handle = ecb_encrypt.process_in_place(&mut buffer).unwrap();
|
||||
/// operation_handle.wait_blocking();
|
||||
///
|
||||
/// // Instead of the plaintext message, buffer now contains the ciphertext.
|
||||
/// assert_eq!(
|
||||
/// buffer,
|
||||
/// [
|
||||
/// 0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a,
|
||||
/// 0x31, 0x96,
|
||||
/// ]
|
||||
/// );
|
||||
/// #
|
||||
/// # {after_snippet}
|
||||
/// ```
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// The ECB and OFB cipher modes require the data length to be a multiple of the block size
|
||||
/// (16), otherwise an error is returned.
|
||||
pub fn process_in_place<'t>(
|
||||
&'t mut self,
|
||||
buffer: &'t mut [u8],
|
||||
) -> Result<AesHandle<'t>, Error> {
|
||||
self.validate(buffer)?;
|
||||
|
||||
let data = self.frontend.data_mut();
|
||||
|
||||
data.cipher_mode = NonNull::from(&self.cipher_mode);
|
||||
|
||||
data.buffers.text_length = buffer.len();
|
||||
let ptr = NonNull::from(buffer);
|
||||
|
||||
data.buffers.input = ptr;
|
||||
data.buffers.output = ptr;
|
||||
|
||||
Ok(self.post())
|
||||
}
|
||||
}
|
||||
|
||||
/// The handle to the pending AES operation.
|
||||
///
|
||||
/// This object is returned by [`AesContext::process`] and [`AesContext::process_in_place`].
|
||||
///
|
||||
/// Dropping this handle before the operation finishes will cancel the operation.
|
||||
///
|
||||
/// For an example, see the documentation of [`AesBackend`].
|
||||
pub struct AesHandle<'t>(Handle<'t, AesOperation>);
|
||||
|
||||
impl AesHandle<'_> {
|
||||
/// Polls the status of the work item.
|
||||
#[inline]
|
||||
pub fn poll(&mut self) -> Poll {
|
||||
self.0.poll()
|
||||
}
|
||||
|
||||
/// Blocks until the work item to be processed.
|
||||
#[inline]
|
||||
pub fn wait_blocking(mut self) {
|
||||
while self.poll() == Poll::Pending {}
|
||||
}
|
||||
|
||||
/// Waits for the work item to be processed.
|
||||
#[inline]
|
||||
pub fn wait(&mut self) -> impl Future<Output = Status> {
|
||||
self.0.wait()
|
||||
}
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
fn read_words(slice: &[u8]) -> impl Iterator<Item = u32> {
|
||||
|
@ -333,6 +333,7 @@ unstable_module! {
|
||||
#[cfg(psram)] // DMA needs some things from here
|
||||
pub mod psram;
|
||||
pub mod efuse;
|
||||
pub mod work_queue;
|
||||
}
|
||||
|
||||
unstable_driver! {
|
||||
|
490
esp-hal/src/work_queue.rs
Normal file
490
esp-hal/src/work_queue.rs
Normal file
@ -0,0 +1,490 @@
|
||||
//! A generic work queue.
|
||||
//!
|
||||
//! Work queues are the backbone of cryptographic drivers. They enable asynchronous and
|
||||
//! blocking operations using shared peripherals, like crypto accelerators. Clients
|
||||
//! post work items into the work queue, and poll for completion. The act of polling
|
||||
//! processes the queue, which allows any number of clients to post work without deadlocking.
|
||||
//!
|
||||
//! Work queues are configured by backends. Backends register a `process` callback which is
|
||||
//! called when a client posts a work item or polls for completion.
|
||||
//!
|
||||
//! Posting a work item into the queue returns a handle. The handle can be used to poll whether
|
||||
//! the work item has been processed. Dropping the handle will cancel the work item.
|
||||
#![cfg_attr(esp32c2, allow(unused))]
|
||||
|
||||
use core::{future::poll_fn, marker::PhantomData, ptr::NonNull};
|
||||
|
||||
use crate::{asynch::AtomicWaker, sync::Locked};
|
||||
|
||||
/// Queue driver operations.
|
||||
///
|
||||
/// Functions in this VTable are provided by drivers that consume work items.
|
||||
/// These functions may be called both in the context of the queue frontends and drivers.
|
||||
pub(crate) struct VTable<T: Sync> {
|
||||
/// Starts processing a new work item.
|
||||
///
|
||||
/// The function returns whether the work item was accepted. If there is no driver currently
|
||||
/// processing the queue, this function will return false to prevent removing the work item
|
||||
/// from the queue.
|
||||
///
|
||||
/// This function should be as short as possible.
|
||||
pub(crate) post: fn(*const (), &mut T) -> bool,
|
||||
|
||||
/// Polls the status of a particular work item.
|
||||
///
|
||||
/// Return `None` if the work item is not currently being processed.
|
||||
///
|
||||
/// This function should be as short as possible.
|
||||
pub(crate) poll: fn(*const (), &mut T) -> Option<Poll>,
|
||||
|
||||
/// Attempts to abort processing a work item.
|
||||
///
|
||||
/// This function should be as short as possible.
|
||||
pub(crate) cancel: fn(*const (), &mut T),
|
||||
|
||||
/// Called when the driver may be stopped.
|
||||
///
|
||||
/// This function should be as short as possible.
|
||||
pub(crate) stop: fn(*const ()),
|
||||
}
|
||||
|
||||
impl<T: Sync> VTable<T> {
|
||||
pub(crate) const fn noop() -> Self {
|
||||
Self {
|
||||
post: |_, _| false,
|
||||
poll: |_, _| None,
|
||||
cancel: |_, _| (),
|
||||
stop: |_| (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner<T: Sync> {
|
||||
head: Option<NonNull<WorkItem<T>>>,
|
||||
tail: Option<NonNull<WorkItem<T>>>,
|
||||
current: Option<NonNull<WorkItem<T>>>,
|
||||
|
||||
// The data pointer will be passed to VTable functions, which may be called in any context.
|
||||
data: *const (),
|
||||
vtable: VTable<T>,
|
||||
}
|
||||
|
||||
impl<T: Sync> Inner<T> {
|
||||
/// Places a work item at the end of the queue.
|
||||
fn enqueue(&mut self, ptr: NonNull<WorkItem<T>>) {
|
||||
if let Some(tail) = self.tail.as_mut() {
|
||||
// Queue contains something, append to `tail`.
|
||||
unsafe { tail.as_mut().next = Some(ptr) };
|
||||
} else {
|
||||
// Queue was empty, set `head` to the first element.
|
||||
self.head = Some(ptr);
|
||||
}
|
||||
|
||||
// Move `tail` to the newly inserted item.
|
||||
self.tail = Some(ptr);
|
||||
}
|
||||
|
||||
/// Places a work item at the front of the queue.
|
||||
fn enqueue_front(&mut self, mut ptr: NonNull<WorkItem<T>>) {
|
||||
// Chain the node into the list.
|
||||
unsafe { ptr.as_mut().next = self.head };
|
||||
|
||||
// Adjust list `head` to point at the new-first element.
|
||||
self.head = Some(ptr);
|
||||
if self.tail.is_none() {
|
||||
// The queue was empty, we need to set `tail` to the last element.
|
||||
self.tail = Some(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs one processing iteration.
|
||||
///
|
||||
/// This function enqueues a new work item or polls the status of the currently processed one.
|
||||
fn process(&mut self) {
|
||||
if let Some(mut current) = self.current {
|
||||
let result = unsafe { (self.vtable.poll)(self.data, &mut current.as_mut().data) };
|
||||
|
||||
if let Some(Poll::Ready(status)) = result {
|
||||
unsafe { current.as_mut().complete(status) };
|
||||
self.current = None;
|
||||
|
||||
self.dequeue_and_post(true);
|
||||
}
|
||||
} else {
|
||||
// If the queue is empty, the driver should already have been notified when the queue
|
||||
// became empty, so we don't notify it here.
|
||||
self.dequeue_and_post(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: even if the queue itself may be implemented lock-free, dequeuing and posting to the
|
||||
// driver must be done atomically to ensure that the queue can be processed fully by any of
|
||||
// the frontends polling it.
|
||||
fn dequeue_and_post(&mut self, notify_on_empty: bool) {
|
||||
if let Some(mut ptr) = self.dequeue() {
|
||||
// Start processing a new work item.
|
||||
|
||||
if unsafe { (self.vtable.post)(self.data, &mut ptr.as_mut().data) } {
|
||||
self.current = Some(ptr);
|
||||
} else {
|
||||
// If the driver didn't accept the work item, place it back to the front of the
|
||||
// queue.
|
||||
self.enqueue_front(ptr);
|
||||
}
|
||||
} else if notify_on_empty {
|
||||
// There are no more work items. Notify the driver that it can stop.
|
||||
(self.vtable.stop)(self.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pops and returns a work item from the start of the queue.
|
||||
fn dequeue(&mut self) -> Option<NonNull<WorkItem<T>>> {
|
||||
// If the `head` is None, the queue is empty. Return None and do nothing.
|
||||
let ptr = self.head?;
|
||||
|
||||
unsafe { self.head = ptr.as_ref().next };
|
||||
|
||||
// If the new `head` is null, the queue is empty. Clear the `tail` pointer.
|
||||
if self.head.is_none() {
|
||||
self.tail = None;
|
||||
}
|
||||
|
||||
Some(ptr)
|
||||
}
|
||||
|
||||
/// Cancels a particular work item.
|
||||
///
|
||||
/// If the work item is currently being processed, this function notifies the driver. Otherwise,
|
||||
/// it tries to remove the pointer from the work queue.
|
||||
///
|
||||
/// The function returns true when the item was immediately cancelled.
|
||||
///
|
||||
/// This function is not `unsafe` because it only dereferences `work_item` if the function has
|
||||
/// determined that the item belongs to this queue.
|
||||
fn cancel(&mut self, mut work_item: NonNull<WorkItem<T>>) -> bool {
|
||||
if self.current == Some(work_item) {
|
||||
// Cancelling an in-progress item is more complicated than plucking it from the
|
||||
// queue. Forward the request to the driver to (maybe) cancel the
|
||||
// operation.
|
||||
(self.vtable.cancel)(self.data, unsafe {
|
||||
// This is safe to do, because the work item is currently owned by this queue.
|
||||
&mut work_item.as_mut().data
|
||||
});
|
||||
// Queue will need to be polled to query item status.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The work item is not the current one, remove it from the queue. This immediately
|
||||
// cancels the work item. `remove` only uses the address of the work item without
|
||||
// dereferencing it.
|
||||
if self.remove(work_item) {
|
||||
unsafe { work_item.as_mut().complete(Status::Cancelled) };
|
||||
// Cancelled immediately, no further polling necessary for this item.
|
||||
return true;
|
||||
}
|
||||
|
||||
// In this case the item doesn't belong to this queue, it can be in any state. The item will
|
||||
// need to be polled, but this also means something may have gone wrong.
|
||||
false
|
||||
}
|
||||
|
||||
/// Removes the item from the queue.
|
||||
///
|
||||
/// Returns `true` if the work item was successfully removed, `false` if the work item was not
|
||||
/// found in the queue.
|
||||
///
|
||||
/// This function is not `unsafe` because it does not dereference `ptr`, so it does not matter
|
||||
/// that `ptr` may belong to a different work queue.
|
||||
fn remove(&mut self, ptr: NonNull<WorkItem<T>>) -> bool {
|
||||
// Walk the queue to find `ptr`.
|
||||
let mut prev = None;
|
||||
let mut current = self.head;
|
||||
while let Some(current_item) = current {
|
||||
let next = unsafe { current_item.as_ref().next };
|
||||
|
||||
if current_item != ptr {
|
||||
// Not what we're looking for. Move to the next element.
|
||||
prev = current;
|
||||
current = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've found `ptr`. Remove it from the list.
|
||||
if Some(ptr) == self.head {
|
||||
self.head = next;
|
||||
} else {
|
||||
// Unwrapping is fine, because if the current pointer is not the `head`, the
|
||||
// previous pointer must be Some.
|
||||
unsafe { unwrap!(prev).as_mut().next = next };
|
||||
}
|
||||
|
||||
if Some(ptr) == self.tail {
|
||||
self.tail = prev;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Did not find `ptr`.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic work queue.
|
||||
pub(crate) struct WorkQueue<T: Sync> {
|
||||
inner: Locked<Inner<T>>,
|
||||
}
|
||||
|
||||
impl<T: Sync> WorkQueue<T> {
|
||||
/// Creates a new `WorkQueue`.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
inner: Locked::new(Inner {
|
||||
head: None,
|
||||
tail: None,
|
||||
current: None,
|
||||
|
||||
data: core::ptr::null(),
|
||||
vtable: VTable::noop(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the queue.
|
||||
///
|
||||
/// The provided data pointer will be passed to the VTable functions.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `data` pointer must be valid as long as the `WorkQueue` is configured with it. The
|
||||
/// driver must access the data pointer appropriately (i.e. it must not move !Send data out of
|
||||
/// it).
|
||||
pub unsafe fn configure<D: Sync>(&self, data: *const D, vtable: VTable<T>) {
|
||||
self.inner.with(|inner| {
|
||||
(inner.vtable.stop)(inner.data);
|
||||
|
||||
inner.data = data.cast();
|
||||
inner.vtable = vtable;
|
||||
})
|
||||
}
|
||||
|
||||
/// Enqueues a work item.
|
||||
pub fn post_work<'t>(&'t self, work_item: &'t mut WorkItem<T>) -> Handle<'t, T> {
|
||||
let ptr = unsafe {
|
||||
// Safety: `Handle` and `work_item` have lifetime 't, which ensures this call
|
||||
// can't be called on an in-flight work item. As `Handle` (and the underlying driver
|
||||
// that processed the work queue) does not use the reference, and using the
|
||||
// reference is not possible while `Handle` exists, this should be safe.
|
||||
work_item.prepare()
|
||||
};
|
||||
|
||||
self.inner.with(|inner| inner.enqueue(ptr));
|
||||
|
||||
Handle {
|
||||
queue: self,
|
||||
work_item: ptr,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls the queue once.
|
||||
pub fn process(&self) {
|
||||
self.inner.with(|inner| inner.process());
|
||||
}
|
||||
|
||||
/// Polls the queue once and returns the status of the given work item.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// The caller must ensure that `item` belongs to the polled queue. An item belongs to the
|
||||
/// **last queue it was enqueued in**, even if the item is no longer in the queue's linked
|
||||
/// list. This relationship is broken when the Handle that owns the WorkItem is dropped.
|
||||
pub unsafe fn poll(&self, item: NonNull<WorkItem<T>>) -> Poll {
|
||||
self.inner.with(|inner| {
|
||||
let status = unsafe { (*item.as_ptr()).status };
|
||||
if status == Poll::Pending {
|
||||
inner.process();
|
||||
unsafe { (*item.as_ptr()).status }
|
||||
} else {
|
||||
status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Schedules the work item to be cancelled.
|
||||
///
|
||||
/// The function returns true when the item was immediately cancelled. If the function returns
|
||||
/// `false`, the item will need to be polled until its status becomes [`Poll::Ready`].
|
||||
///
|
||||
/// The work item should not be assumed to be immediately cancelled. Polling its handle
|
||||
/// is necessary to ensure it is no longer being processed by the underlying driver.
|
||||
pub fn cancel(&self, work_item: NonNull<WorkItem<T>>) -> bool {
|
||||
self.inner.with(|inner| inner.cancel(work_item))
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of a work item.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Status {
|
||||
/// The processing has completed.
|
||||
Completed,
|
||||
|
||||
/// The work item has been cancelled.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// A unit of work in the work queue.
|
||||
pub(crate) struct WorkItem<T: Sync> {
|
||||
next: Option<NonNull<WorkItem<T>>>,
|
||||
status: Poll,
|
||||
data: T,
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl<T: Sync> WorkItem<T> {
|
||||
/// Completes the work item.
|
||||
///
|
||||
/// This function is intended to be called from the underlying drivers.
|
||||
pub fn complete(&mut self, status: Status) {
|
||||
self.status = Poll::Ready(status);
|
||||
self.waker.wake();
|
||||
}
|
||||
|
||||
/// Prepares a work item to be enqueued.
|
||||
///
|
||||
/// # Safety:
|
||||
///
|
||||
/// The caller must ensure the reference is not used again while the pointer returned by this
|
||||
/// function is in use.
|
||||
unsafe fn prepare(&mut self) -> NonNull<Self> {
|
||||
self.next = None;
|
||||
self.status = Poll::Pending;
|
||||
|
||||
NonNull::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of a work item posted to a work queue.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Poll {
|
||||
/// The work item has not yet been fully processed.
|
||||
Pending,
|
||||
|
||||
/// The work item has been processed.
|
||||
Ready(Status),
|
||||
}
|
||||
|
||||
/// A reference to a posted [`WorkItem`].
|
||||
///
|
||||
/// This struct ensures that the work item is valid until the item is processed or is removed from
|
||||
/// the work queue.
|
||||
///
|
||||
/// Dropping the handle cancels the work item, but may block for some time if the work item is
|
||||
/// already being processed.
|
||||
pub(crate) struct Handle<'t, T: Sync> {
|
||||
queue: &'t WorkQueue<T>,
|
||||
work_item: NonNull<WorkItem<T>>,
|
||||
// Make sure lifetime is invariant to prevent UB.
|
||||
_marker: PhantomData<&'t mut WorkItem<T>>,
|
||||
}
|
||||
|
||||
impl<'t, T: Sync> Handle<'t, T> {
|
||||
/// Returns the status of the work item.
|
||||
pub fn poll(&mut self) -> Poll {
|
||||
unsafe { self.queue.poll(self.work_item) }
|
||||
}
|
||||
|
||||
/// Waits for the work item to be processed.
|
||||
pub fn wait(&mut self) -> impl Future<Output = Status> {
|
||||
poll_fn(|ctx| {
|
||||
unsafe { (*self.work_item.as_ptr()).waker.register(ctx.waker()) };
|
||||
match self.poll() {
|
||||
Poll::Pending => core::task::Poll::Pending,
|
||||
Poll::Ready(status) => core::task::Poll::Ready(status),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, T: Sync> Drop for Handle<'t, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.queue.cancel(self.work_item) {
|
||||
// We must wait for the driver to release our WorkItem.
|
||||
while self.poll() == Poll::Pending {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WorkQueueDriver<'t, D, T>
|
||||
where
|
||||
D: Sync,
|
||||
T: Sync,
|
||||
{
|
||||
queue: &'t WorkQueue<T>,
|
||||
_marker: PhantomData<&'t mut D>,
|
||||
}
|
||||
|
||||
impl<'t, D, T> WorkQueueDriver<'t, D, T>
|
||||
where
|
||||
D: Sync,
|
||||
T: Sync,
|
||||
{
|
||||
pub fn new(driver: &'t mut D, vtable: VTable<T>, queue: &'t WorkQueue<T>) -> Self {
|
||||
unsafe {
|
||||
// Safety: the lifetime 't ensures the pointer remains valid for the lifetime of the
|
||||
// WorkQueueDriver. The Drop implementation (and the general "Don't forget" clause)
|
||||
// ensure the pointer is not used after the WQD has been dropped.
|
||||
queue.configure((driver as *mut D).cast_const(), vtable);
|
||||
}
|
||||
Self {
|
||||
queue,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unused)] // TODO this will be used with AesDmaBackend, for example
|
||||
pub fn poll(&mut self) {
|
||||
self.queue.process();
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, T> Drop for WorkQueueDriver<'_, D, T>
|
||||
where
|
||||
D: Sync,
|
||||
T: Sync,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Safety: the noop VTable functions don't use the pointer at all.
|
||||
self.queue.configure(core::ptr::null::<D>(), VTable::noop())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by work queue clients, allows hiding WorkItem.
|
||||
pub(crate) struct WorkQueueFrontend<T: Sync> {
|
||||
work_item: WorkItem<T>,
|
||||
}
|
||||
|
||||
impl<T: Sync> WorkQueueFrontend<T> {
|
||||
pub fn new(initial: T) -> Self {
|
||||
Self {
|
||||
work_item: WorkItem {
|
||||
next: None,
|
||||
status: Poll::Pending,
|
||||
data: initial,
|
||||
waker: AtomicWaker::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut T {
|
||||
&mut self.work_item.data
|
||||
}
|
||||
|
||||
pub fn post<'t>(&'t mut self, queue: &'t WorkQueue<T>) -> Handle<'t, T> {
|
||||
queue.post_work(&mut self.work_item)
|
||||
}
|
||||
}
|
@ -11,10 +11,6 @@ name = "hil_test"
|
||||
name = "aes"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "aes_dma"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "alloc_psram"
|
||||
harness = false
|
||||
|
@ -28,7 +28,7 @@ const fn pad_to<const K: usize>(input: &[u8]) -> [u8; K] {
|
||||
mod tests {
|
||||
use esp_hal::{
|
||||
Config,
|
||||
aes::{Aes, Key},
|
||||
aes::{Aes, AesBackend, AesContext, Key, Operation},
|
||||
clock::CpuClock,
|
||||
};
|
||||
use hil_test as _;
|
||||
@ -36,6 +36,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
const KEY: &[u8] = b"SUp4SeCp@sSw0rd";
|
||||
const KEY_128: [u8; 16] = pad_to::<16>(KEY);
|
||||
const KEY_256: [u8; 32] = pad_to::<32>(KEY);
|
||||
|
||||
const PLAINTEXT: &[u8] = b"message";
|
||||
const PLAINTEXT_BUF: [u8; 16] = pad_to::<16>(PLAINTEXT);
|
||||
@ -82,4 +84,160 @@ mod tests {
|
||||
|
||||
test_aes_block::<32>(&mut aes, PLAINTEXT_BUF, CIPHERTEXT_ECB_256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(esp32))]
|
||||
fn test_aes_dma_ecb() {
|
||||
use esp_hal::{
|
||||
aes::dma::{AesDma, CipherMode},
|
||||
dma::{DmaRxBuf, DmaTxBuf},
|
||||
dma_buffers,
|
||||
};
|
||||
|
||||
fn test_aes_ecb<const K: usize>(
|
||||
mut aes: AesDma<'_>,
|
||||
plaintext: [u8; 16],
|
||||
ciphertext: [u8; 16],
|
||||
) -> AesDma<'_>
|
||||
where
|
||||
Key: From<[u8; K]>,
|
||||
{
|
||||
const DMA_BUFFER_SIZE: usize = 16;
|
||||
|
||||
let (output, rx_descriptors, input, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
|
||||
let mut output = DmaRxBuf::new(rx_descriptors, output).unwrap();
|
||||
let mut input = DmaTxBuf::new(tx_descriptors, input).unwrap();
|
||||
|
||||
// Encrypt
|
||||
input.as_mut_slice().copy_from_slice(&plaintext);
|
||||
let transfer = aes
|
||||
.process(
|
||||
1,
|
||||
output,
|
||||
input,
|
||||
Operation::Encrypt,
|
||||
CipherMode::Ecb,
|
||||
pad_to::<K>(KEY),
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
(aes, output, input) = transfer.wait();
|
||||
assert_eq!(output.as_slice(), ciphertext);
|
||||
|
||||
// Decrypt
|
||||
input.as_mut_slice().copy_from_slice(&ciphertext);
|
||||
let transfer = aes
|
||||
.process(
|
||||
1,
|
||||
output,
|
||||
input,
|
||||
Operation::Decrypt,
|
||||
CipherMode::Ecb,
|
||||
pad_to::<K>(KEY),
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
(aes, output, _) = transfer.wait();
|
||||
assert_eq!(output.as_slice(), plaintext);
|
||||
|
||||
aes
|
||||
}
|
||||
let peripherals = esp_hal::init(Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32s2)] {
|
||||
let dma_channel = peripherals.DMA_CRYPTO;
|
||||
} else {
|
||||
let dma_channel = peripherals.DMA_CH0;
|
||||
}
|
||||
}
|
||||
|
||||
let aes = Aes::new(peripherals.AES).with_dma(dma_channel);
|
||||
|
||||
let aes = test_aes_ecb::<16>(aes, PLAINTEXT_BUF, CIPHERTEXT_ECB_128);
|
||||
let _ = test_aes_ecb::<32>(aes, PLAINTEXT_BUF, CIPHERTEXT_ECB_256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_work_queue() {
|
||||
let p = esp_hal::init(Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
let mut aes = AesBackend::new(p.AES);
|
||||
let _backend = aes.start();
|
||||
|
||||
let mut output = [0; 16];
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_128);
|
||||
let handle = ecb_encrypt.process(&PLAINTEXT_BUF, &mut output).unwrap();
|
||||
|
||||
handle.wait_blocking();
|
||||
|
||||
assert_eq!(output, CIPHERTEXT_ECB_128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_work_queue_work_posted_before_queue_started() {
|
||||
let p = esp_hal::init(Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
let mut output = [0; 16];
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_128);
|
||||
let handle = ecb_encrypt.process(&PLAINTEXT_BUF, &mut output).unwrap();
|
||||
|
||||
let mut aes = AesBackend::new(p.AES);
|
||||
let _backend = aes.start();
|
||||
|
||||
handle.wait_blocking();
|
||||
|
||||
assert_eq!(output, CIPHERTEXT_ECB_128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_work_queue_in_place() {
|
||||
let p = esp_hal::init(Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
let mut buffer = PLAINTEXT_BUF;
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_128);
|
||||
let handle = ecb_encrypt.process_in_place(&mut buffer).unwrap();
|
||||
|
||||
let mut aes = AesBackend::new(p.AES);
|
||||
let _backend = aes.start();
|
||||
|
||||
handle.wait_blocking();
|
||||
|
||||
assert_eq!(buffer, CIPHERTEXT_ECB_128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cancelling_work() {
|
||||
// In this test, we post two work items, and cancel the first one before starting the
|
||||
// backend. We will assert that, when the second item has finished correctly, the first did
|
||||
// not modify its output buffer.
|
||||
// Note that this result is not guaranteed. The cancellation can come later than the work
|
||||
// item has finished processing. We can only reliably test it because we start the backend
|
||||
// after cancelling the operation.
|
||||
let p = esp_hal::init(Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
let mut buffer1 = PLAINTEXT_BUF;
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_128);
|
||||
let handle1 = ecb_encrypt.process_in_place(&mut buffer1).unwrap();
|
||||
|
||||
let mut buffer2 = PLAINTEXT_BUF;
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_256);
|
||||
let handle2 = ecb_encrypt.process_in_place(&mut buffer2).unwrap();
|
||||
|
||||
let mut buffer3 = PLAINTEXT_BUF;
|
||||
let mut ecb_encrypt = AesContext::ecb(Operation::Encrypt, KEY_128);
|
||||
let handle3 = ecb_encrypt.process_in_place(&mut buffer3).unwrap();
|
||||
|
||||
core::mem::drop(handle1);
|
||||
|
||||
let mut aes = AesBackend::new(p.AES);
|
||||
let _backend = aes.start();
|
||||
|
||||
handle3.wait_blocking();
|
||||
handle2.wait_blocking();
|
||||
|
||||
assert_eq!(buffer1, PLAINTEXT_BUF);
|
||||
assert_eq!(buffer2, CIPHERTEXT_ECB_256);
|
||||
assert_eq!(buffer3, CIPHERTEXT_ECB_128);
|
||||
}
|
||||
}
|
||||
|
@ -1,120 +0,0 @@
|
||||
//! AES DMA Test
|
||||
|
||||
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
//% FEATURES: unstable
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_hal::{
|
||||
aes::{Aes, Key, Operation, dma::CipherMode},
|
||||
clock::CpuClock,
|
||||
dma::{DmaRxBuf, DmaTxBuf},
|
||||
dma_buffers,
|
||||
};
|
||||
use hil_test as _;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
const fn pad_to<const K: usize>(input: &[u8]) -> [u8; K] {
|
||||
let mut out = [0; K];
|
||||
|
||||
let in_bytes = input.len();
|
||||
assert!(in_bytes <= K);
|
||||
|
||||
let mut i = 0;
|
||||
while i < in_bytes {
|
||||
out[i] = input[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
const DMA_BUFFER_SIZE: usize = 16;
|
||||
|
||||
#[cfg(test)]
|
||||
#[embedded_test::tests(default_timeout = 3)]
|
||||
mod tests {
|
||||
use esp_hal::aes::dma::AesDma;
|
||||
|
||||
use super::*;
|
||||
|
||||
const KEY: &[u8] = b"SUp4SeCp@sSw0rd";
|
||||
|
||||
const PLAINTEXT: &[u8] = b"message";
|
||||
const PLAINTEXT_BUF: [u8; 16] = pad_to::<16>(PLAINTEXT);
|
||||
|
||||
const CIPHERTEXT_ECB_128: [u8; 16] = [
|
||||
0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a, 0x31,
|
||||
0x96,
|
||||
];
|
||||
const CIPHERTEXT_ECB_256: [u8; 16] = [
|
||||
0x0, 0x63, 0x3f, 0x02, 0xa4, 0x53, 0x09, 0x72, 0x20, 0x6d, 0xc9, 0x08, 0x7c, 0xe5, 0xfd,
|
||||
0xc,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_aes_dma_ecb() {
|
||||
fn test_aes_ecb<const K: usize>(
|
||||
mut aes: AesDma<'_>,
|
||||
plaintext: [u8; 16],
|
||||
ciphertext: [u8; 16],
|
||||
) -> AesDma<'_>
|
||||
where
|
||||
Key: From<[u8; K]>,
|
||||
{
|
||||
let (output, rx_descriptors, input, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
|
||||
let mut output = DmaRxBuf::new(rx_descriptors, output).unwrap();
|
||||
let mut input = DmaTxBuf::new(tx_descriptors, input).unwrap();
|
||||
|
||||
// Encrypt
|
||||
input.as_mut_slice().copy_from_slice(&plaintext);
|
||||
let transfer = aes
|
||||
.process(
|
||||
1,
|
||||
output,
|
||||
input,
|
||||
Operation::Encrypt,
|
||||
CipherMode::Ecb,
|
||||
pad_to::<K>(KEY),
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
(aes, output, input) = transfer.wait();
|
||||
assert_eq!(output.as_slice(), ciphertext);
|
||||
|
||||
// Decrypt
|
||||
input.as_mut_slice().copy_from_slice(&ciphertext);
|
||||
let transfer = aes
|
||||
.process(
|
||||
1,
|
||||
output,
|
||||
input,
|
||||
Operation::Decrypt,
|
||||
CipherMode::Ecb,
|
||||
pad_to::<K>(KEY),
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
(aes, output, _) = transfer.wait();
|
||||
assert_eq!(output.as_slice(), plaintext);
|
||||
|
||||
aes
|
||||
}
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32s2)] {
|
||||
let dma_channel = peripherals.DMA_CRYPTO;
|
||||
} else {
|
||||
let dma_channel = peripherals.DMA_CH0;
|
||||
}
|
||||
}
|
||||
|
||||
let aes = Aes::new(peripherals.AES).with_dma(dma_channel);
|
||||
|
||||
let aes = test_aes_ecb::<16>(aes, PLAINTEXT_BUF, CIPHERTEXT_ECB_128);
|
||||
let _ = test_aes_ecb::<32>(aes, PLAINTEXT_BUF, CIPHERTEXT_ECB_256);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user