Skip to content

Commit

Permalink
Add DMA driver
Browse files Browse the repository at this point in the history
This adds a DMA driver which can read and write to registers and put a
payload into the mailbox.

To be able to read that content from the mailbox a new helper function
is added.

Signed-off-by: Arthur Heymans <[email protected]>
  • Loading branch information
ArthurHeymans committed Oct 22, 2024
1 parent d86418b commit e61539d
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 1 deletion.
6 changes: 6 additions & 0 deletions builder/src/firmware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ pub mod driver_tests {
features: &["emu"],
};

pub const DMA: FwId = FwId {
bin_name: "axi_dma_tests",
..BASE_FWID
};

pub const DOE: FwId = FwId {
bin_name: "doe",
..BASE_FWID
Expand Down Expand Up @@ -404,6 +409,7 @@ pub const REGISTERED_FW: &[&FwId] = &[
&hw_model_tests::TEST_DCCM_DOUBLE_BIT_ECC,
&hw_model_tests::TEST_UNITIALIZED_READ,
&hw_model_tests::TEST_PCR_EXTEND,
&driver_tests::DMA,
&driver_tests::DOE,
&driver_tests::ECC384,
&driver_tests::ECC384_SIGN_VALIDATION_FAILURE,
Expand Down
303 changes: 303 additions & 0 deletions drivers/src/dma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
/*++
Licensed under the Apache-2.0 license.
File Name:
dma.rs
Abstract:
File contains API for DMA Widget operations
--*/

use caliptra_error::{CaliptraError, CaliptraResult};
use caliptra_registers::axi_dma::{
enums::{RdRouteE, WrRouteE},
AxiDmaReg,
};
use zerocopy::AsBytes;

pub enum DmaReadTarget {
Mbox,
AhbFifo,
AxiWr(usize),
}

pub struct DmaReadTransaction {
pub read_addr: usize,
pub fixed_addr: bool,
pub length: u32,
pub target: DmaReadTarget,
}

pub enum DmaWriteOrigin {
Mbox,
AhbFifo,
AxiRd(usize),
}

pub struct DmaWriteTransaction {
pub write_addr: usize,
pub fixed_addr: bool,
pub length: u32,
pub origin: DmaWriteOrigin,
}

/// Dma Widget
pub struct Dma {
dma: AxiDmaReg,
}

impl Dma {
/// Create a new DMA instance
///
/// # Arguments
///
/// * `dma` - DMA register block
pub fn new(dma: AxiDmaReg) -> Self {
Self { dma }
}

fn flush(&mut self) {
let dma = self.dma.regs_mut();

dma.ctrl().write(|c| c.flush(true));

// Wait till we're not busy and have no errors
// TODO this assumes the peripheral does not clear that status0 state
// in one cycle. Maybe it can be removed if the assumption proves false

while {
let status0 = dma.status0().read();
status0.busy() || status0.error()
} {}
}

fn setup_dma_read(&mut self, read_transaction: DmaReadTransaction) {
let dma = self.dma.regs_mut();

let read_addr: usize = read_transaction.read_addr;
#[cfg(target_pointer_width = "64")]
dma.src_addr_h().write(|_| (read_addr >> 32) as u32);
dma.src_addr_l().write(|_| (read_addr & 0xffff_ffff) as u32);

if let DmaReadTarget::AxiWr(target_addr) = read_transaction.target {
#[cfg(target_pointer_width = "64")]
dma.dst_addr_h().write(|_| (target_addr >> 32) as u32);
dma.dst_addr_l()
.write(|_| (target_addr & 0xffff_ffff) as u32);
}

dma.ctrl().modify(|c| {
c.rd_route(|_| match read_transaction.target {
DmaReadTarget::Mbox => RdRouteE::Mbox,
DmaReadTarget::AhbFifo => RdRouteE::AhbFifo,
DmaReadTarget::AxiWr(_) => RdRouteE::AxiWr,
})
.rd_fixed(read_transaction.fixed_addr)
.wr_route(|_| match read_transaction.target {
DmaReadTarget::AxiWr(_) => WrRouteE::AxiRd,
_ => WrRouteE::Disable,
})
});

dma.byte_count().write(|_| read_transaction.length);
}

fn setup_dma_write(&mut self, write_transaction: DmaWriteTransaction) {
let dma = self.dma.regs_mut();

let write_addr = write_transaction.write_addr;
#[cfg(target_pointer_width = "64")]
dma.dst_addr_h().write(|_| (write_addr >> 32) as u32);
dma.dst_addr_l()
.write(|_| (write_addr & 0xffff_ffff) as u32);

if let DmaWriteOrigin::AxiRd(origin_addr) = write_transaction.origin {
#[cfg(target_pointer_width = "64")]
dma.dst_addr_h().write(|_| (origin_addr >> 32) as u32);
dma.dst_addr_l()
.write(|_| (origin_addr & 0xffff_ffff) as u32);
}

dma.ctrl().modify(|c| {
c.wr_route(|_| match write_transaction.origin {
DmaWriteOrigin::Mbox => WrRouteE::Mbox,
DmaWriteOrigin::AhbFifo => WrRouteE::AhbFifo,
DmaWriteOrigin::AxiRd(_) => WrRouteE::AxiRd,
})
.wr_fixed(write_transaction.fixed_addr)
.rd_route(|_| match write_transaction.origin {
DmaWriteOrigin::AxiRd(_) => RdRouteE::AxiWr,
_ => RdRouteE::Disable,
})
});

dma.byte_count().write(|_| write_transaction.length);
}

/// Read data from the DMA FIFO
///
/// # Arguments
///
/// * `read_data` - Buffer to store the read data
///
/// # Returns
///
/// * `CaliptraResult<()>` - Success or error code
pub fn dma_read_fifo(&mut self, read_data: &mut [u8]) -> CaliptraResult<()> {
let dma = self.dma.regs_mut();

let status = dma.status0().read();

if read_data.len() > status.fifo_depth() as usize {
return Err(CaliptraError::DRIVER_DMA_FIFO_UNDERRUN);
}

read_data.chunks_mut(4).for_each(|word| {
let ptr = dma.read_data().ptr as *mut u8;
// Reg only exports u32 writes but we need finer grained access
unsafe {
ptr.copy_to_nonoverlapping(word.as_mut_ptr(), word.len());
}
});

Ok(())
}

fn dma_write_fifo(&mut self, write_data: &[u8]) -> CaliptraResult<()> {
let dma = self.dma.regs_mut();

let max_fifo_depth = dma.cap().read().fifo_max_depth();
let current_fifo_depth = dma.status0().read().fifo_depth();

if write_data.len() as u32 > max_fifo_depth - current_fifo_depth {
return Err(CaliptraError::DRIVER_DMA_FIFO_OVERRUN);
}

write_data.chunks(4).for_each(|word| {
let ptr = dma.write_data().ptr as *mut u8;
// Reg only exports u32 writes but we need finer grained access
unsafe {
ptr.copy_from_nonoverlapping(word.as_ptr(), word.len());
}
});

Ok(())
}

fn do_transaction(&mut self) -> CaliptraResult<()> {
let dma = self.dma.regs_mut();

let status0 = dma.status0().read();
if status0.busy() {
return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ALREADY_BUSY);
}

if status0.error() {
return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR);
}

dma.ctrl().modify(|c| c.go(true));

while dma.status0().read().busy() {
if dma.status0().read().error() {
return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR);
}
}

Ok(())
}

/// Read a 32-bit word from the specified address
///
/// # Arguments
///
/// * `read_addr` - Address to read from
///
/// # Returns
///
/// * `CaliptraResult<u32>` - Read value or error code
pub fn read_dword(&mut self, read_addr: usize) -> CaliptraResult<u32> {
let mut read_val: u32 = 0;

self.flush();

let read_transaction = DmaReadTransaction {
read_addr,
fixed_addr: false,
length: core::mem::size_of::<u32>() as u32,
target: DmaReadTarget::AhbFifo,
};

self.setup_dma_read(read_transaction);
self.do_transaction()?;
self.dma_read_fifo(read_val.as_bytes_mut())?;
Ok(read_val)
}

/// Write a 32-bit word to the specified address
///
/// # Arguments
///
/// * `write_addr` - Address to write to
/// * `write_val` - Value to write
///
/// # Returns
///
/// * `CaliptraResult<()>` - Success or error code
pub fn write_dword(&mut self, write_addr: usize, write_val: u32) -> CaliptraResult<()> {
self.flush();

let write_transaction = DmaWriteTransaction {
write_addr,
fixed_addr: false,
length: core::mem::size_of::<u32>() as u32,
origin: DmaWriteOrigin::AhbFifo,
};
self.dma_write_fifo(write_val.as_bytes())?;
self.setup_dma_write(write_transaction);
self.do_transaction()?;
Ok(())
}

/// Transfer payload to mailbox
///
/// The mailbox lock needs to be acquired before this can be called
///
/// # Arguments
///
/// * `read_addr` - Source address to read from
/// * `payload_len_bytes` - Length of the payload in bytes
/// * `fixed_addr` - Whether to use a fixed address for reading
/// * `block_size` - Size of each block transfer
///
/// # Returns
///
/// * `CaliptraResult<()>` - Success or error code
pub fn transfer_payload_to_mbox(
&mut self,
read_addr: usize,
payload_len_bytes: u32,
fixed_addr: bool,
block_size: u32,
) -> CaliptraResult<()> {
self.flush();

let read_transaction = DmaReadTransaction {
read_addr,
fixed_addr,
length: payload_len_bytes,
target: DmaReadTarget::Mbox,
};
self.setup_dma_read(read_transaction);
self.dma
.regs_mut()
.block_size()
.write(|f| f.size(block_size));
self.do_transaction()?;
Ok(())
}
}
2 changes: 2 additions & 0 deletions drivers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod wait;
mod bounded_address;
mod csrng;
mod data_vault;
mod dma;
mod doe;
mod ecc384;
mod error_reporter;
Expand Down Expand Up @@ -61,6 +62,7 @@ pub use csrng::{Csrng, HealthFailCounts as CsrngHealthFailCounts, Seed as CsrngS
pub use data_vault::{
ColdResetEntry4, ColdResetEntry48, DataVault, WarmResetEntry4, WarmResetEntry48,
};
pub use dma::{Dma, DmaReadTarget, DmaReadTransaction, DmaWriteOrigin, DmaWriteTransaction};
pub use doe::DeobfuscationEngine;
pub use ecc384::{
Ecc384, Ecc384PrivKeyIn, Ecc384PrivKeyOut, Ecc384PubKey, Ecc384Result, Ecc384Scalar,
Expand Down
4 changes: 4 additions & 0 deletions drivers/test-fw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@ name = "trng_driver_responder"
path = "src/bin/trng_driver_responder.rs"
required-features = ["riscv"]

[[bin]]
name = "axi_dma_tests"
path = "src/bin/dma_tests.rs"
required-features = ["riscv"]
Loading

0 comments on commit e61539d

Please sign in to comment.