From 881c7f85551af36419fea8489afa13798ec9efff Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Fri, 18 Oct 2024 19:43:56 +0200 Subject: [PATCH] Add DMA driver 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 --- builder/src/firmware.rs | 6 + drivers/src/dma.rs | 303 ++++++++++++++++++ drivers/src/lib.rs | 2 + drivers/test-fw/Cargo.toml | 4 + drivers/test-fw/src/bin/dma_tests.rs | 78 +++++ .../tests/drivers_integration_tests/main.rs | 20 ++ error/src/lib.rs | 7 + sw-emulator/app/src/main.rs | 1 - 8 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 drivers/src/dma.rs create mode 100644 drivers/test-fw/src/bin/dma_tests.rs diff --git a/builder/src/firmware.rs b/builder/src/firmware.rs index 503a0b5238..83415d1bb7 100644 --- a/builder/src/firmware.rs +++ b/builder/src/firmware.rs @@ -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 @@ -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, diff --git a/drivers/src/dma.rs b/drivers/src/dma.rs new file mode 100644 index 0000000000..a2b8262757 --- /dev/null +++ b/drivers/src/dma.rs @@ -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` - Read value or error code + pub fn read_dword(&mut self, read_addr: usize) -> CaliptraResult { + let mut read_val: u32 = 0; + + self.flush(); + + let read_transaction = DmaReadTransaction { + read_addr, + fixed_addr: false, + length: core::mem::size_of::() 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::() 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(()) + } +} diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index f3ed62283b..9ae82bbee4 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -21,6 +21,7 @@ mod wait; mod bounded_address; mod csrng; mod data_vault; +mod dma; mod doe; mod ecc384; mod error_reporter; @@ -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, diff --git a/drivers/test-fw/Cargo.toml b/drivers/test-fw/Cargo.toml index e913821aab..7bb61459ed 100644 --- a/drivers/test-fw/Cargo.toml +++ b/drivers/test-fw/Cargo.toml @@ -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"] diff --git a/drivers/test-fw/src/bin/dma_tests.rs b/drivers/test-fw/src/bin/dma_tests.rs new file mode 100644 index 0000000000..d87cc78b50 --- /dev/null +++ b/drivers/test-fw/src/bin/dma_tests.rs @@ -0,0 +1,78 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + dma_tests.rs + +Abstract: + + File contains test cases for DMA driver API + +--*/ + +#![no_std] +#![no_main] + +use caliptra_drivers::{memory_layout, Dma, Mailbox}; +use caliptra_registers::{axi_dma::AxiDmaReg, ecc::EccReg, mbox::MboxCsr}; +use caliptra_test_harness::test_suite; +use core::slice; + +// We test that reading from a periph into the fifo works by reading ECC name +fn test_dma_read_from_periph() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let ecc_regs = unsafe { EccReg::new() }; + let ecc_name = ecc_regs.regs().name().ptr(); + + let dword = dma.read_dword(ecc_name as usize).unwrap(); + assert_eq!(dword.to_ne_bytes(), [0x70, 0x63, 0x65, 0x73]); // secp +} + +// We test that reading from a periph into the fifo works by reading ECC name +fn test_dma_write_to_periph() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let ecc_regs = unsafe { EccReg::new() }; + let ecc_iv = ecc_regs.regs().iv().at(0).ptr; + + let data: u32 = 0xdead_beef; + + dma.write_dword(ecc_iv as usize, data).unwrap(); + let dword = dma.read_dword(ecc_iv as usize).unwrap(); + assert_eq!(dword, data); +} + +fn test_read_rri_to_mailbox() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let test_image = [0xab; 512]; + let block_size = 256; + + // TODO use i3c generated regs + let rri_regs = 0x1003_806c; + + // Get mailbox lock + let mut mbox_driver = unsafe { Mailbox::new(MboxCsr::new()) }; + let mut txn = mbox_driver.try_start_send_txn().unwrap(); + txn.send_request(0xdead_beef, b"").unwrap(); + + dma.transfer_payload_to_mbox(rri_regs, test_image.len() as u32, true, block_size) + .unwrap(); + + let mbox_fifo = unsafe { + slice::from_raw_parts( + memory_layout::MBOX_ORG as *const u8, + memory_layout::MBOX_SIZE as usize, + ) + }; + assert_eq!(mbox_fifo.get(..test_image.len()).unwrap(), test_image); +} + +test_suite! { + test_dma_read_from_periph, + test_dma_write_to_periph, + test_read_rri_to_mailbox, +} diff --git a/drivers/tests/drivers_integration_tests/main.rs b/drivers/tests/drivers_integration_tests/main.rs index 206d8e3cf0..780fa0baf9 100644 --- a/drivers/tests/drivers_integration_tests/main.rs +++ b/drivers/tests/drivers_integration_tests/main.rs @@ -1125,3 +1125,23 @@ fn test_uart() { fn test_mailbox_txn_drop() { run_driver_test(&firmware::driver_tests::MBOX_SEND_TXN_DROP); } + +#[test] +fn test_dma() { + let rom = caliptra_builder::build_firmware_rom(&firmware::driver_tests::DMA).unwrap(); + let recovery_image = &[0xab; 512]; + + let init_params = InitParams { + rom: &rom, + ..default_init_params() + }; + + let boot_params = BootParams { + ..Default::default() + }; + + let mut model = caliptra_hw_model::new_unbooted(init_params).unwrap(); + model.put_firmware_in_rri(recovery_image).unwrap(); + model.boot(boot_params).unwrap(); + model.step_until_exit_success().unwrap(); +} diff --git a/error/src/lib.rs b/error/src/lib.rs index 7372bd8683..0522ea2b16 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -321,6 +321,13 @@ impl CaliptraError { pub const DRIVER_HANDOFF_INVALID_WARM_RESET_ENTRY48: CaliptraError = CaliptraError::new_const(0x000D104); + /// DMA driver Errors + pub const DRIVER_DMA_TRANSACTION_ALREADY_BUSY: CaliptraError = + CaliptraError::new_const(0x0000f000); + pub const DRIVER_DMA_TRANSACTION_ERROR: CaliptraError = CaliptraError::new_const(0x0000f001); + pub const DRIVER_DMA_FIFO_UNDERRUN: CaliptraError = CaliptraError::new_const(0x0000f002); + pub const DRIVER_DMA_FIFO_OVERRUN: CaliptraError = CaliptraError::new_const(0x0000f003); + /// Runtime Errors pub const RUNTIME_INTERNAL: CaliptraError = CaliptraError::new_const(0x000E0001); pub const RUNTIME_UNIMPLEMENTED_COMMAND: CaliptraError = CaliptraError::new_const(0x000E0002); diff --git a/sw-emulator/app/src/main.rs b/sw-emulator/app/src/main.rs index 050e2dd622..2cae70601c 100644 --- a/sw-emulator/app/src/main.rs +++ b/sw-emulator/app/src/main.rs @@ -269,7 +269,6 @@ fn main() -> io::Result<()> { let bus_args = CaliptraRootBusArgs { rom: rom_buffer, - recovery_image: current_fw_buf.clone(), log_dir: args_log_dir.clone(), tb_services_cb: TbServicesCb::new(move |val| match val { 0x01 => exit(0xFF),