Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions fw/pal/traits/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,20 @@ pub enum HsmError {
/// `x963_kdf` / `sp800_56a_kdf`.
ConcatKdfError = 0x0870010B,

/// A variable-length DMA allocation callback
/// ([`HsmAlloc::dma_alloc_var`](crate::HsmAlloc::dma_alloc_var) /
/// [`dma_alloc_var_with`](crate::HsmAlloc::dma_alloc_var_with)) reported
/// having written more bytes than the buffer it was handed. The
/// allocator refuses to expose a slice longer than it owns, so the
/// command fails instead of over-reading the heap. Like
/// [`UndoLogFull`](Self::UndoLogFull) this indicates a **firmware bug** —
/// an encoder mis-reporting its output length — and should never occur;
/// it is distinct from [`InvalidArg`](Self::InvalidArg), which blames a
/// malformed request, and from
/// [`NotEnoughSpace`](Self::NotEnoughSpace), which signals genuine
/// exhaustion.
DmaAllocLenOverrun = 0x0870010C,

// Firmware-internal diagnostic codes logged by the CPU fault and panic
// exception handlers (`azihsm_fw_uno_fault`). These are not DDI protocol
// statuses: they use the PAL diagnostic facility (`0x08F`) to stay clear of
Expand Down
10 changes: 7 additions & 3 deletions fw/plat/std/pal/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,11 @@ impl HsmAlloc for StdHsmPal {
Ok(len) => {
if len > buf.len() {
// Closure overran the buffer it was handed; refuse to
// expose a longer slice than we actually own.
// expose a longer slice than we actually own. This is a
// firmware bug (an encoder mis-reporting its length), not
// a malformed request, hence the dedicated status.
mark_cell.set(saved_mark);
return Err(HsmError::InvalidArg);
return Err(HsmError::DmaAllocLenOverrun);
}
let final_end = aligned + len;
mark_cell.set(final_end);
Expand Down Expand Up @@ -274,8 +276,10 @@ impl HsmAlloc for StdHsmPal {
match f(buf) {
Ok((len, extra)) => {
if len > buf.len() {
// See `dma_alloc_var`: a firmware bug, not a malformed
// request.
mark_cell.set(saved_mark);
return Err(HsmError::InvalidArg);
return Err(HsmError::DmaAllocLenOverrun);
}
let final_end = aligned + len;
mark_cell.set(final_end);
Expand Down
1 change: 1 addition & 0 deletions fw/plat/uno/fw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ static_assertions = "1.1.0"
syn = { features = ["full"], version = "2" }
tock-registers = { git = "https://github.com/tock/tock.git", rev = "release-2.2" }
zerocopy = "0.8.48"
zeroize = { default-features = false, version = "1.8.1" }

[workspace.lints.rust]
future_incompatible = { level = "deny", priority = -1 }
Expand Down
1 change: 1 addition & 0 deletions fw/plat/uno/fw/pal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tock-registers = { workspace = true }
bitfield-struct = { workspace = true }
open-enum = { workspace = true }
zerocopy = { workspace = true }
zeroize = { workspace = true }

[features]
default = []
Expand Down
128 changes: 126 additions & 2 deletions fw/plat/uno/fw/pal/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,29 @@ fn heap_base_cap(io_index: u16, heap: usize) -> (*mut u8, usize) {
}
}

/// Per-IO SRAM (DMA heap) region that may hold IO data, as
/// `(base_ptr, dirty_len)`.
///
/// `dirty_len` is the slot's peak watermark (see [`pk`]) — every byte
/// written since the last scrub, and nothing beyond it. Used by
/// [`UnoHsmPal::scrub_io_slot`](crate::UnoHsmPal::scrub_io_slot) so a
/// command that touches a few hundred bytes does not pay an 18 KiB wipe.
#[inline(always)]
pub(crate) fn io_slot_dma_dirty(pal: &UnoHsmPal, io_index: u16) -> (*mut u8, usize) {
let (base, cap) = heap_base_cap(io_index, DMA);
(base, pk(pal, io_index, DMA).with(|v| *v).min(cap))
}

/// Per-IO DTCM (NonDma heap) region that may hold IO data, as
/// `(base_ptr, dirty_len)`.
///
/// Companion to [`io_slot_dma_dirty`] for the fast/NonDma scratch heap.
#[inline(always)]
pub(crate) fn io_slot_nondma_dirty(pal: &UnoHsmPal, io_index: u16) -> (*mut u8, usize) {
let (base, cap) = heap_base_cap(io_index, NONDMA);
(base, pk(pal, io_index, NONDMA).with(|v| *v).min(cap))
}

/// Access the watermark cell for one `(io_index, heap)` pair.
///
/// # Parameters
Expand All @@ -173,6 +196,43 @@ fn wm(pal: &UnoHsmPal, io_index: u16, heap: usize) -> &SingleCell<usize> {
&pal.io_alloc[io_index as usize][heap]
}

/// Access the *peak* (high-water) watermark cell for one
/// `(io_index, heap)` pair.
///
/// [`wm`] rewinds whenever a scope drops, so by IO teardown it says nothing
/// about how much of the slot was written. This cell only ever grows until
/// the slot is scrubbed, so it bounds the region that may still hold key
/// material — it is what the teardown wipe is sized from.
///
/// # Parameters
/// - `pal`: PAL instance owning the peak table.
/// - `io_index`: IO slot index.
/// - `heap`: Heap selector (`NONDMA` or `DMA`).
///
/// # Returns
/// Shared reference to the corresponding [`SingleCell`] peak watermark.
#[inline(always)]
fn pk(pal: &UnoHsmPal, io_index: u16, heap: usize) -> &SingleCell<usize> {
&pal.io_peak[io_index as usize][heap]
}

/// Clear both peak watermarks for `io_index`, marking the slot as scrubbed.
///
/// Called by [`UnoHsmPal::scrub_io_slot`](crate::UnoHsmPal::scrub_io_slot)
/// *after* the wipe, so a peak always means "bytes written since the last
/// scrub" — independent of where IOs happen to reset their watermarks. That
/// keeps allocations made outside a [`reset_io_alloc`] / scrub pair covered
/// by the next wipe.
///
/// # Parameters
/// - `pal`: PAL instance owning the peak table.
/// - `io_index`: IO slot index whose peaks are cleared.
pub(crate) fn clear_io_peak(pal: &UnoHsmPal, io_index: u16) {
for cell in &pal.io_peak[io_index as usize] {
cell.with(|v| *v = 0);
}
}

/// Bump-allocate `size` bytes with given alignment, return (start_offset, slice).
///
/// # Parameters
Expand Down Expand Up @@ -210,6 +270,11 @@ fn bump(
}

w.with(|v| *v = end);
// Raise the slot's high-water mark. Unlike `w` this never rewinds, so it
// still bounds the written region once every scope has dropped — that is
// what the teardown scrub wipes. `dma_alloc_var*` reserve the whole
// remaining heap here and correct this back down to the trimmed length.
pk(pal, io_index, heap).with(|v| *v = (*v).max(end));
// SAFETY: start..end is within bounds and does not overlap any prior
// live allocation (the watermark only advances within a scope).
Ok((start, unsafe {
Expand Down Expand Up @@ -400,6 +465,27 @@ impl HsmAlloc for UnoHsmPal {
/// DMA capacity from current aligned watermark. It returns the number of
/// bytes logically consumed.
///
/// # Contract
///
/// **`f` must not write past the `len` it returns.** The teardown scrub is
/// sized from the slot's high-water mark, and this method reports only
/// `start + len` as its contribution — anything written beyond `len` would
/// be left unwiped and could survive into the next IO on this slot. `f`
/// receives the whole remaining heap purely so the encoded length need not
/// be known up front.
///
/// A `len` larger than the buffer is rejected with
/// [`HsmError::DmaAllocLenOverrun`] rather than panicking on the slice,
/// matching the std PAL; the peak is left at the full reservation in that
/// case, since such a callback wrote an unknown amount.
///
/// Callers encode through either `MborEncoder` or a TBOR response frame
/// builder (`Tbor*Resp::encode(...).finish()`); both append sequentially
/// and report their final position, so both write a strict prefix. This
/// was checked on hardware: instrumenting the teardown scrub to scan each
/// slot's whole capacity found no non-zero byte past the recorded peak
/// over ~1.7k IOs.
///
/// # Returns
/// Mutable [`DmaBuf`] of length selected by `f`, or error from `f`/allocator.
fn dma_alloc_var<F>(&self, io: &impl HsmIo, f: F) -> HsmResult<&mut DmaBuf>
Expand All @@ -414,15 +500,36 @@ impl HsmAlloc for UnoHsmPal {
if aligned >= cap {
return Err(HsmError::NotEnoughSpace);
}
// Snapshot the peak *before* `bump`, which is about to inflate it to
// full capacity because this reserves the whole remaining heap. The
// trim below restores it to what `f` actually wrote (see `# Contract`);
// without this the scrub would wipe the whole slot on every IO.
let peak_before = pk(self, io_index, DMA).with(|v| *v);
let (start, buf) = bump(self, io_index, DMA, cap - aligned, 4)?;
match f(buf) {
Ok(len) => {
w.with(|v| *v = start + len.min(buf.len()));
if len > buf.len() {
// Callback overran the buffer it was handed; refuse to
// expose a longer slice than we own, matching the std PAL,
// rather than panicking on the slice below. Rewind the
// watermark but leave the peak at the full reservation:
// the callback wrote an unknown amount.
w.with(|v| *v = start);
return Err(HsmError::DmaAllocLenOverrun);
}
let end = start + len;
w.with(|v| *v = end);
pk(self, io_index, DMA).with(|v| *v = peak_before.max(end));
// SAFETY: `buf` came from the SRAM Dma pool.
Ok(unsafe { DmaBuf::from_raw_mut(&mut buf[..len]) })
}
Err(e) => {
w.with(|v| *v = start);
// Leave the peak at the full reservation `bump` just set. `f`
// may have written a partial encoding before failing and there
// is no `len` to bound it by, so the whole reserved span must
// be treated as dirty. This costs one full-slot wipe on a
// rejected request — rare, and the safe direction.
Err(e)
}
}
Expand All @@ -435,6 +542,11 @@ impl HsmAlloc for UnoHsmPal {
/// - `f`: Callback receiving temporary writable remaining DMA span and
/// returning `(len, extra)` where `len` is consumed bytes.
///
/// # Contract
///
/// Same as [`Self::dma_alloc_var`]: **`f` must not write past `len`**, or
/// the teardown scrub will miss those bytes.
///
/// # Returns
/// Tuple `(&mut DmaBuf, T)` on success, or error from `f`/allocator.
fn dma_alloc_var_with<F, T>(&self, io: &impl HsmIo, f: F) -> HsmResult<(&mut DmaBuf, T)>
Expand All @@ -449,15 +561,27 @@ impl HsmAlloc for UnoHsmPal {
if aligned >= cap {
return Err(HsmError::NotEnoughSpace);
}
// See `dma_alloc_var`: snapshot before `bump` inflates the peak to cap.
let peak_before = pk(self, io_index, DMA).with(|v| *v);
let (start, buf) = bump(self, io_index, DMA, cap - aligned, 4)?;
match f(buf) {
Ok((len, extra)) => {
w.with(|v| *v = start + len.min(buf.len()));
if len > buf.len() {
// See `dma_alloc_var`: refuse the over-report instead of
// panicking, and leave the peak at the full reservation.
w.with(|v| *v = start);
return Err(HsmError::DmaAllocLenOverrun);
}
let end = start + len;
w.with(|v| *v = end);
pk(self, io_index, DMA).with(|v| *v = peak_before.max(end));
// SAFETY: `buf` came from the SRAM Dma pool.
Ok((unsafe { DmaBuf::from_raw_mut(&mut buf[..len]) }, extra))
}
Err(e) => {
w.with(|v| *v = start);
// See `dma_alloc_var`: leave the peak at the full reservation,
// since a partial write before the failure is unbounded.
Err(e)
}
}
Expand Down
Loading
Loading