Skip to content

Commit

Permalink
User space interface for DMA and Mailbox (#75)
Browse files Browse the repository at this point in the history
* User space interface for DMA and Mailbox

Added user APIs for DMA and mailbox

The DMA API allows for AXI-to-AXI transfers or from local buffer to an
AXI component.

The Mailbox API allows for executing a mailbox command in an atomic
fashion, i.e. execute command, wait for response, return response.
  • Loading branch information
mlvisaya authored Jan 10, 2025
1 parent ee04979 commit 9077cb2
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ members = [
"runtime/apps/libtock/ufmt",
"runtime/apps/libtock/unittest",
"runtime/apps/libtockasync",
"runtime/apps/syscall",
"runtime/capsules",
"runtime/i3c",
"tests/hello",
Expand Down Expand Up @@ -131,6 +132,7 @@ libtock_platform = { path = "runtime/apps/libtock/platform" }
libtock_runtime = { path = "runtime/apps/libtock/runtime" }
libtock_unittest = { path = "runtime/apps/libtock/unittest" }
libtock_caliptra = { path = "runtime/apps/apis" }
libsyscall-caliptra = { path = "runtime/apps/syscall" }

# caliptra dependencies; keep git revs in sync
caliptra-api-types = { git = "https://github.com/chipsalliance/caliptra-sw.git", rev = "2f6de531e321b7bb24b17b1bd02b43d2854aef3a" }
Expand Down
92 changes: 92 additions & 0 deletions runtime/apps/libtockasync/src/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,98 @@ impl TockSubscribe {
f
}

pub fn subscribe_allow_ro_rw<S: Syscalls, C: allow_rw::Config>(
driver_num: u32,
subscribe_num: u32,
buffer_ro_num: u32,
buffer_ro: &[u8],
buffer_rw_num: u32,
buffer_rw: &mut [u8],
) -> impl Future<Output = Result<(u32, u32, u32), ErrorCode>> {
// Pinning is necessary since we are passing a pointer to the TockSubscribe to the kernel.
let mut f = Pin::new(Box::new(TockSubscribe::new()));
let upcall_fcn = (kernel_upcall::<S> as *const ()) as usize;
let upcall_data = (&*f as *const TockSubscribe) as usize;

// Allow RO
// Safety: we are passing in a fixed (safe) function pointer and a pointer to a pinned instance.
// If the instance is dropped before the upcall comes in, then we panic in the Drop impl.
let [r0, r1, r2, _] = unsafe {
S::syscall4::<{ syscall_class::ALLOW_RO }>([
driver_num.into(),
buffer_ro_num.into(),
buffer_ro.as_ptr().into(),
buffer_ro.len().into(),
])
};

let return_variant: ReturnVariant = r0.as_u32().into();
match return_variant {
return_variant::SUCCESS_2_U32 => {}
return_variant::FAILURE_2_U32 => {
f.set_err(r1.as_u32().try_into().unwrap_or(ErrorCode::Fail));
}
_ => {
f.set_err(ErrorCode::Fail);
}
}

let returned_buffer: (usize, usize) = (r1.into(), r2.into());
if returned_buffer != (0, 0) {
C::returned_nonzero_buffer(driver_num, buffer_ro_num);
}

// Allow RW
// Safety: we are passing in a fixed (safe) function pointer and a pointer to a pinned instance.
// If the instance is dropped before the upcall comes in, then we panic in the Drop impl.
let [r0, r1, r2, _] = unsafe {
S::syscall4::<{ syscall_class::ALLOW_RW }>([
driver_num.into(),
buffer_rw_num.into(),
buffer_rw.as_mut_ptr().into(),
buffer_rw.len().into(),
])
};

let return_variant: ReturnVariant = r0.as_u32().into();
match return_variant {
return_variant::SUCCESS_2_U32 => {}
return_variant::FAILURE_2_U32 => {
f.set_err(r1.as_u32().try_into().unwrap_or(ErrorCode::Fail));
}
_ => {
f.set_err(ErrorCode::Fail);
}
}

let returned_buffer: (usize, usize) = (r1.into(), r2.into());
if returned_buffer != (0, 0) {
C::returned_nonzero_buffer(driver_num, buffer_rw_num);
}

// Safety: we are passing in a fixed (safe) function pointer and a pointer to a pinned instance.
// If the instance is dropped before the upcall comes in, then we panic in the Drop impl.
let [r0, r1, _, _] = unsafe {
S::syscall4::<{ syscall_class::SUBSCRIBE }>([
driver_num.into(),
subscribe_num.into(),
upcall_fcn.into(),
upcall_data.into(),
])
};
let return_variant: ReturnVariant = r0.as_u32().into();
match return_variant {
return_variant::SUCCESS_2_U32 => {}
return_variant::FAILURE_2_U32 => {
f.set_err(r1.as_u32().try_into().unwrap_or(ErrorCode::Fail));
}
_ => {
f.set_err(ErrorCode::Fail);
}
}
f
}

pub fn subscribe<S: Syscalls>(
driver_num: u32,
subscribe_num: u32,
Expand Down
12 changes: 12 additions & 0 deletions runtime/apps/syscall/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Licensed under the Apache-2.0 license

[package]
name = "libsyscall-caliptra"
version.workspace = true
authors.workspace = true
edition.workspace = true

[dependencies]
libtock_console.workspace = true
libtock_platform.workspace = true
libtockasync.workspace = true
154 changes: 154 additions & 0 deletions runtime/apps/syscall/src/dma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Licensed under the Apache-2.0 license

//! # DMA: A DMA Interface for AXI Source to AXI Destination Transfers
//!
//! This library provides an abstraction for performing asynchronous Direct Memory Access (DMA)
//! transfers between AXI source and AXI destination addresses.
use core::marker::PhantomData;
use libtock_platform::{share, DefaultConfig, ErrorCode, Syscalls};
use libtockasync::TockSubscribe;
/// DMA interface.
pub struct DMA<S: Syscalls> {
syscall: PhantomData<S>,
driver_num: u32,
}

/// Define type for AXI address (64-bit wide).
pub type AXIAddr = u64;

/// Configuration parameters for a DMA transfer.
#[derive(Debug, Clone)]
pub struct DMATransaction<'a> {
/// Number of bytes to transfer.
pub byte_count: usize,
/// Source for the transfer.
pub source: DMASource<'a>,
/// Destination AXI address for the transfer.
pub dest_addr: AXIAddr,
}

/// Represents the source of data for a DMA transfer.
#[derive(Debug, Clone)]
pub enum DMASource<'a> {
/// AXI memory address as the source.
Address(AXIAddr),
/// A local buffer as the source.
Buffer(&'a [u8]),
}

impl<S: Syscalls> Default for DMA<S> {
fn default() -> Self {
Self::new()
}
}

impl<S: Syscalls> DMA<S> {
pub fn new() -> Self {
Self {
syscall: PhantomData,
driver_num: DMA_DRIVER_NUM,
}
}

/// Do a DMA transfer.
///
/// This method executes a DMA transfer based on the provided `DMATransaction` configuration.
///
/// # Arguments
/// * `transaction` - A `DMATransaction` struct containing the transfer details.
///
/// # Returns
/// * `Ok(())` if the transfer starts successfully.
/// * `Err(ErrorCode)` if the transfer fails.
pub async fn xfer<'a>(&self, transaction: &DMATransaction<'a>) -> Result<(), ErrorCode> {
self.setup(transaction)?;

match transaction.source {
DMASource::Buffer(buffer) => self.xfer_src_buffer(buffer).await.map(|_| ()),
DMASource::Address(_) => self.xfer_src_address().await.map(|_| ()),
}
}

async fn xfer_src_address(&self) -> Result<(), ErrorCode> {
let async_start = TockSubscribe::subscribe::<S>(self.driver_num, dma_subscribe::XFER_DONE);
S::command(self.driver_num, dma_cmd::XFER_AXI_TO_AXI, 0, 0).to_result::<(), ErrorCode>()?;
async_start.await.map(|_| ())
}

async fn xfer_src_buffer(&self, buffer: &[u8]) -> Result<(), ErrorCode> {
// Use `share::scope` to safely share the buffer with the kernel
share::scope::<(), _, _>(|_| {
let async_start = TockSubscribe::subscribe_allow_ro::<S, DefaultConfig>(
self.driver_num,
dma_subscribe::XFER_DONE,
dma_ro_buffer::LOCAL_SOURCE,
buffer,
);

// Start the DMA transfer
S::command(self.driver_num, dma_cmd::XFER_LOCAL_TO_AXI, 0, 0)
.to_result::<(), ErrorCode>()?;
Ok(async_start)
})?
.await
.map(|_| ())
}

fn setup(&self, config: &DMATransaction<'_>) -> Result<(), ErrorCode> {
S::command(
self.driver_num,
dma_cmd::SET_BYTE_XFER_COUNT,
config.byte_count as u32,
0,
)
.to_result::<(), ErrorCode>()?;

if let DMASource::Address(src_addr) = config.source {
S::command(
self.driver_num,
dma_cmd::SET_SRC_ADDR,
(src_addr & 0xFFFF_FFFF) as u32,
(src_addr >> 32) as u32,
)
.to_result::<(), ErrorCode>()?;
}

S::command(
self.driver_num,
dma_cmd::SET_DEST_ADDR,
(config.dest_addr & 0xFFFF_FFFF) as u32,
(config.dest_addr >> 32) as u32,
)
.to_result::<(), ErrorCode>()?;

Ok(())
}
}

// -----------------------------------------------------------------------------
// Command IDs and DMA-specific constants
// -----------------------------------------------------------------------------

// Driver number for the DMA interface
pub const DMA_DRIVER_NUM: u32 = 0x8000_0008;

/// Command IDs used by the DMA interface.
mod dma_cmd {
pub const SET_BYTE_XFER_COUNT: u32 = 0;
pub const SET_SRC_ADDR: u32 = 1;
pub const SET_DEST_ADDR: u32 = 2;
pub const XFER_AXI_TO_AXI: u32 = 3;
pub const XFER_LOCAL_TO_AXI: u32 = 4;
}

/// Buffer IDs for DMA (read-only)
mod dma_ro_buffer {
/// Buffer ID for local buffers (read-only)
pub const LOCAL_SOURCE: u32 = 0;
}

/// Subscription IDs for asynchronous notifications.
mod dma_subscribe {
pub const XFER_DONE: u32 = 0;
}
6 changes: 6 additions & 0 deletions runtime/apps/syscall/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Licensed under the Apache-2.0 license

#![no_std]

pub mod dma;
pub mod mailbox;
Loading

0 comments on commit 9077cb2

Please sign in to comment.