-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
d86418b
commit e61539d
Showing
8 changed files
with
420 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.