blob: c5b55ad8efd780e07a8de4f5034550c2360d23b5 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Generic HACE Digest HAL adapter for OpenPRoT
use super::constants::{
HACE_SG_LAST, POLL_YIELD_NS, SHA256_HASH_CMD, SHA384_HASH_CMD, SHA512_HASH_CMD,
};
use super::context::{
HashContext, HACE_BLOCK_SIZE, HACE_BLOCK_SIZE_128, SHA256_DIGEST_SIZE, SHA256_IV,
SHA384_DIGEST_SIZE, SHA384_IV, SHA512_DIGEST_SIZE, SHA512_IV,
};
use super::error::HaceError;
use super::helpers::{fill_padding, load_iv, ptr_to_u32};
use super::registers::HaceRegisters;
use core::marker::PhantomData;
use openprot_hal_blocking::digest::scoped::{DigestCtrlReset, DigestInit, DigestOp};
use openprot_hal_blocking::digest::{
Digest, DigestAlgorithm, ErrorType, Sha2_256, Sha2_384, Sha2_512,
};
use zerocopy::IntoBytes;
/// Per-algorithm constants required by the HACE driver.
///
/// Mirrors the role of `HashAlgo` methods in aspeed-rust: provides the hardware
/// command word, block size, digest size, and IV for each supported algorithm.
pub(crate) trait HaceDigestSpec: DigestAlgorithm
where
Self::Digest: IntoBytes,
{
const HASH_CMD: u32;
const BLOCK_SIZE: usize;
fn iv() -> &'static [u32];
fn digest_from_context(ctx: &HashContext) -> Self::Digest;
}
impl HaceDigestSpec for Sha2_256 {
const HASH_CMD: u32 = SHA256_HASH_CMD;
const BLOCK_SIZE: usize = HACE_BLOCK_SIZE;
fn iv() -> &'static [u32] {
&SHA256_IV
}
fn digest_from_context(ctx: &HashContext) -> Self::Digest {
let mut out = [0u32; SHA256_DIGEST_SIZE / 4];
for (i, dst) in out.iter_mut().enumerate() {
let off = i * 4;
if let Some(chunk) = ctx.digest.get(off..off + 4) {
*dst = u32::from_ne_bytes(<[u8; 4]>::try_from(chunk).unwrap_or([0u8; 4]));
}
}
Digest::new(out)
}
}
impl HaceDigestSpec for Sha2_384 {
const HASH_CMD: u32 = SHA384_HASH_CMD;
const BLOCK_SIZE: usize = HACE_BLOCK_SIZE_128;
fn iv() -> &'static [u32] {
&SHA384_IV
}
fn digest_from_context(ctx: &HashContext) -> Self::Digest {
let mut out = [0u32; SHA384_DIGEST_SIZE / 4];
for (i, dst) in out.iter_mut().enumerate() {
let off = i * 4;
if let Some(chunk) = ctx.digest.get(off..off + 4) {
*dst = u32::from_ne_bytes(<[u8; 4]>::try_from(chunk).unwrap_or([0u8; 4]));
}
}
Digest::new(out)
}
}
impl HaceDigestSpec for Sha2_512 {
const HASH_CMD: u32 = SHA512_HASH_CMD;
const BLOCK_SIZE: usize = HACE_BLOCK_SIZE_128;
fn iv() -> &'static [u32] {
&SHA512_IV
}
fn digest_from_context(ctx: &HashContext) -> Self::Digest {
let mut out = [0u32; SHA512_DIGEST_SIZE / 4];
for (i, dst) in out.iter_mut().enumerate() {
let off = i * 4;
if let Some(chunk) = ctx.digest.get(off..off + 4) {
*dst = u32::from_ne_bytes(<[u8; 4]>::try_from(chunk).unwrap_or([0u8; 4]));
}
}
Digest::new(out)
}
}
pub struct HaceDigest<'a, T: DigestAlgorithm> {
pub(crate) regs: HaceRegisters,
pub(crate) ctx: &'a mut HashContext,
pub(crate) poll_budget: u32,
/// Cooperative yield hook, borrowed from the originating [`HaceDevice`] and
/// invoked once between every completion poll. Type-erased so the adapter
/// (and the `Digest*` trait impls) need not be generic over the strategy.
pub(crate) yield_fn: &'a mut dyn FnMut(u32),
/// Cache flush hook: invalidates stale CPU cache lines after HACE DMA.
/// Injected from [`HaceDevice`] so this module has no direct SCU dependency.
pub(crate) cache_flush: fn(),
_algo: PhantomData<T>,
}
impl<'a, T: DigestAlgorithm> HaceDigest<'a, T> {
/// Construct a digest adapter from an existing register handle, context,
/// poll budget, and cooperative yield hook.
pub(crate) fn new(
regs: HaceRegisters,
ctx: &'a mut HashContext,
poll_budget: u32,
yield_fn: &'a mut dyn FnMut(u32),
cache_flush: fn(),
) -> Self {
Self {
regs,
ctx,
poll_budget,
yield_fn,
cache_flush,
_algo: PhantomData,
}
}
/// Construct a digest adapter from a [`HaceDevice`].
///
/// # Safety
/// Caller must ensure no concurrent or reentrant HACE access for the
/// lifetime of the returned [`HaceDigest`].
pub unsafe fn from_device<Y: FnMut(u32)>(device: &'a mut super::device::HaceDevice<Y>) -> Self {
// Borrow split. `regs`/`poll_budget`/`ctx` are `Copy`d out; the
// retained `&'a mut device.yield_fn` reborrow pins `&'a mut HaceDevice`
// for the whole life of the returned op — that is the arbiter: a
// second `HaceDigest`/`HaceHmac` needs `&mut device` again and is a
// borrow-check error while this one lives. The context is no longer
// minted by a `shared_ctx_ptr()` free accessor at each call; it is the
// device's sole pointer, so its transient `&mut` is reached only
// *through* that exclusive device borrow
// (`borrow-arbitrated-engine-exclusivity`, Checklist box 2/4).
let regs = device.regs;
let poll_budget = device.poll_budget;
let cache_flush = device.cache_flush;
// SAFETY: the device holds the sole pointer to this `.ram_nc` context
// (acquired once at its `unsafe fn new*` single-instance gate); the
// caller upholds non-reentrancy and the live `&'a mut device` (pinned
// by `yield_fn` below) gates it, so no other `&mut` to it is live.
let ctx: &'a mut HashContext = unsafe { &mut *device.ctx };
let yield_fn: &'a mut dyn FnMut(u32) = &mut device.yield_fn;
Self::new(regs, ctx, poll_budget, yield_fn, cache_flush)
}
/// DMA one full block held in `ctx.buffer` (always `.ram_nc`) to the engine.
///
/// Called only when `ctx.bufcnt == ctx.block_size`. Resets `bufcnt` to 0
/// on success so the buffer can be reused for the next block.
fn flush_block(&mut self) -> Result<(), HaceError> {
let buf_ptr = ptr_to_u32(self.ctx.buffer.as_ptr())?;
let bufcnt = self.ctx.bufcnt;
self.ctx.sg[0].addr = buf_ptr;
self.ctx.sg[0].len = bufcnt | HACE_SG_LAST;
let sg_addr = ptr_to_u32(self.ctx.sg.as_ptr())?;
let digest_addr = ptr_to_u32(self.ctx.digest.as_ptr())?;
let method = self.ctx.method;
self.regs.clear_hash_intflag();
self.regs
.program_hash_operation(sg_addr, digest_addr, bufcnt, method);
for _ in 0..self.poll_budget {
if self.regs.hash_intflag_is_set() {
self.ctx.bufcnt = 0;
return Ok(());
}
(self.yield_fn)(POLL_YIELD_NS);
}
self.regs.stop_hash_operation();
Err(HaceError::Timeout)
}
}
impl<'a, T: DigestAlgorithm> ErrorType for HaceDigest<'a, T> {
type Error = HaceError;
}
impl<'a, T: HaceDigestSpec> DigestInit<T> for HaceDigest<'a, T>
where
T::Digest: IntoBytes,
{
type OpContext<'b>
= HaceDigest<'b, T>
where
Self: 'b;
fn init(&mut self, _algo: T) -> Result<Self::OpContext<'_>, Self::Error> {
// Mirror aspeed-rust init sequence exactly:
// 1. Set method (hardware command word, includes HACE_SG_EN)
// 2. Load IV into digest buffer
// 3. Set block_size
// 4. Zero bufcnt and digcnt
let iv = T::iv();
self.ctx.method = T::HASH_CMD;
let iv_bytes = iv.len().saturating_mul(4);
match self.ctx.digest.get_mut(..iv_bytes) {
Some(dst) => load_iv(dst, iv)?,
None => return Err(HaceError::InvalidInput),
}
self.ctx.block_size = T::BLOCK_SIZE as u32;
self.ctx.bufcnt = 0;
self.ctx.digcnt = [0; 2];
Ok(HaceDigest {
regs: self.regs,
ctx: &mut *self.ctx,
poll_budget: self.poll_budget,
yield_fn: &mut *self.yield_fn,
cache_flush: self.cache_flush,
_algo: PhantomData,
})
}
}
impl<'a, T: HaceDigestSpec> DigestOp for HaceDigest<'a, T>
where
T::Digest: IntoBytes,
{
type Output = T::Digest;
fn update(&mut self, input: &[u8]) -> Result<(), Self::Error> {
let input_len = u32::try_from(input.len()).map_err(|_| HaceError::InvalidInput)?;
// Accumulate total byte count (with carry into digcnt[1]).
let (new_digcnt, carry) = self.ctx.digcnt[0].overflowing_add(u64::from(input_len));
self.ctx.digcnt[0] = new_digcnt;
if carry {
self.ctx.digcnt[1] += 1;
}
// Copy all input through ctx.buffer (.ram_nc) one block at a time.
//
// DMA safety (D1/D2): the HACE engine reads data by physical address
// via SG descriptors. If the caller's `input` slice is in flash, rodata,
// or a cacheable stack buffer, the engine may read stale zeros from
// physical RAM. By staging every byte through `ctx.buffer` (which is
// placed in `.ram_nc` by the linker) we guarantee the DMA source is
// always in non-cacheable SRAM. This also covers HMAC's `scratch`
// buffer (D2) since it routes through `update()` via `one_shot!`.
let block_size = self.ctx.block_size as usize;
let mut offset = 0usize;
while offset < input.len() {
let bufcnt = self.ctx.bufcnt as usize;
let space = block_size.saturating_sub(bufcnt);
let chunk_len = core::cmp::min(space, input.len() - offset);
// Invariant: block_size is 64 or 128 (set by init()); if somehow
// zero, bail rather than infinite-loop.
if chunk_len == 0 {
return Err(HaceError::InvalidInput);
}
let dst = self
.ctx
.buffer
.get_mut(bufcnt..bufcnt.saturating_add(chunk_len))
.ok_or(HaceError::InvalidInput)?;
let src = input
.get(offset..offset.saturating_add(chunk_len))
.ok_or(HaceError::InvalidInput)?;
// Element-wise copy instead of `copy_from_slice`: `dst` and `src` are
// both `chunk_len` long by construction, but the optimizer cannot
// prove `dst.len() == src.len()` through the two `get`/`get_mut`
// range slices, so `copy_from_slice` would keep its length-mismatch
// panic branch. The zip-copy is provably panic-free.
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d = *s;
}
self.ctx.bufcnt += chunk_len as u32;
offset += chunk_len;
// When the buffer holds exactly one full block, flush it via DMA.
if self.ctx.bufcnt == self.ctx.block_size {
self.flush_block()?;
}
}
Ok(())
}
fn finalize(self) -> Result<Self::Output, Self::Error> {
let this = self;
// Append SHA padding at ctx.buffer[bufcnt..]; updates ctx.bufcnt.
fill_padding(this.ctx, 0);
// Set up SG[0] descriptor pointing at the padded buffer (mirror aspeed-rust finalize).
let buf_ptr = ptr_to_u32(this.ctx.buffer.as_ptr())?;
let bufcnt = this.ctx.bufcnt;
this.ctx.sg[0].addr = buf_ptr;
this.ctx.sg[0].len = bufcnt | HACE_SG_LAST;
let sg_addr = ptr_to_u32(this.ctx.sg.as_ptr())?;
let digest_addr = ptr_to_u32(this.ctx.digest.as_ptr())?;
let method = this.ctx.method;
this.regs.clear_hash_intflag();
this.regs
.program_hash_operation(sg_addr, digest_addr, bufcnt, method);
for _ in 0..this.poll_budget {
if this.regs.hash_intflag_is_set() {
// Invalidate stale CPU cache lines over the digest buffer.
(this.cache_flush)();
let result = T::digest_from_context(this.ctx);
// Cleanup context (mirrors cleanup_context in aspeed-rust).
this.ctx.bufcnt = 0;
this.ctx.digcnt = [0; 2];
this.ctx.buffer.fill(0);
this.ctx.digest.fill(0);
this.regs.stop_hash_operation();
return Ok(result);
}
(this.yield_fn)(POLL_YIELD_NS);
}
this.regs.stop_hash_operation();
Err(HaceError::Timeout)
}
}
impl<'a, T: DigestAlgorithm> DigestCtrlReset for HaceDigest<'a, T> {
fn reset(&mut self) -> Result<(), Self::Error> {
self.ctx.bufcnt = 0;
self.ctx.digcnt = [0; 2];
self.ctx.buffer.fill(0);
self.ctx.digest.fill(0);
self.regs.stop_hash_operation();
Ok(())
}
}