diff --git a/Cargo.lock b/Cargo.lock index 3439147..2ec3b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,6 +1089,7 @@ dependencies = [ "critical-section", "embassy-executor", "embedded-alloc", + "libsyscall-caliptra", "libtock", "libtock_alarm", "libtock_caliptra", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index d4b178d..0199a59 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -36,3 +36,4 @@ test-flash-storage-erase = [] test-mctp-ctrl-cmds = [] test-mctp-capsule-loopback = [] test-mctp-user-loopback = [] +test-flash-usermode = [] \ No newline at end of file diff --git a/runtime/apps/example/Cargo.toml b/runtime/apps/example/Cargo.toml index 753aeb8..b4da019 100644 --- a/runtime/apps/example/Cargo.toml +++ b/runtime/apps/example/Cargo.toml @@ -11,6 +11,7 @@ critical-section.workspace = true embassy-executor.workspace = true libtock_alarm.workspace = true libtock_caliptra.workspace = true +libsyscall-caliptra.workspace = true libtock_console.workspace = true libtock_debug_panic.workspace = true libtock_platform.workspace = true @@ -38,3 +39,4 @@ test-flash-storage-erase = [] test-mctp-ctrl-cmds = [] test-mctp-capsule-loopback = [] test-mctp-user-loopback = [] +test-flash-usermode = [] \ No newline at end of file diff --git a/runtime/apps/example/src/main.rs b/runtime/apps/example/src/main.rs index 57d7fbc..a3f356e 100644 --- a/runtime/apps/example/src/main.rs +++ b/runtime/apps/example/src/main.rs @@ -6,6 +6,8 @@ #![allow(static_mut_refs)] use core::fmt::Write; +#[cfg(feature = "test-flash-usermode")] +use libsyscall_caliptra::flash::{driver_num as par_driver_num, FlashCapacity, SpiFlash}; use libtock::alarm::*; use libtock_caliptra::mctp::{driver_num, Mctp}; use libtock_console::Console; @@ -86,6 +88,34 @@ pub(crate) async fn async_main() { test_mctp_loopback::().await; } + #[cfg(feature = "test-flash-usermode")] + { + writeln!(console_writer, "flash usermode test starts").unwrap(); + let mut user_r_buf: [u8; flash_test::BUF_LEN] = [0u8; flash_test::BUF_LEN]; + // Fill the write buffer with a pattern + let user_w_buf: [u8; flash_test::BUF_LEN] = { + let mut buf = [0u8; flash_test::BUF_LEN]; + for i in 0..buf.len() { + buf[i] = (i % 256) as u8; + } + buf + }; + + let mut test_cfg = flash_test::FlashTestConfig { + drv_num: par_driver_num::IMAGE_PARTITION, + expected_capacity: flash_test::EXPECTED_CAPACITY, + expected_chunk_size: flash_test::EXPECTED_CHUNK_SIZE, + e_offset: 0, + e_len: flash_test::BUF_LEN, + w_offset: 20, + w_len: 1000, + w_buf: &user_w_buf, + r_buf: &mut user_r_buf, + }; + flash_test::simple_test::(&mut test_cfg).await; + writeln!(console_writer, "flash usermode test succeeds").unwrap(); + } + writeln!(console_writer, "app finished").unwrap(); } @@ -112,6 +142,102 @@ async fn test_mctp_loopback() { } } +#[cfg(feature = "test-flash-usermode")] +pub mod flash_test { + use super::*; + pub const BUF_LEN: usize = 1024; + pub const EXPECTED_CAPACITY: FlashCapacity = FlashCapacity(0x200_0000); + pub const EXPECTED_CHUNK_SIZE: usize = 512; + + pub struct FlashTestConfig<'a> { + pub drv_num: u32, + pub expected_capacity: FlashCapacity, + pub expected_chunk_size: usize, + pub e_offset: usize, + pub e_len: usize, + pub w_offset: usize, + pub w_len: usize, + pub w_buf: &'a [u8], + pub r_buf: &'a mut [u8], + } + + pub async fn simple_test<'a, S: Syscalls>(test_cfg: &'a mut FlashTestConfig<'a>) { + let flash_par = SpiFlash::::new(test_cfg.drv_num); + assert_eq!( + flash_par.get_capacity().unwrap(), + test_cfg.expected_capacity + ); + assert_eq!( + flash_par.get_chunk_size().unwrap(), + test_cfg.expected_chunk_size + ); + + let ret = flash_par.erase(test_cfg.e_offset, test_cfg.e_len).await; + assert_eq!(ret, Ok(())); + + // Write test region partially + let ret = flash_par + .write(test_cfg.w_offset, test_cfg.w_len, test_cfg.w_buf as &[u8]) + .await; + assert_eq!(ret, Ok(())); + + // Read the written region + let ret = flash_par + .read( + test_cfg.w_offset, + test_cfg.w_len, + test_cfg.r_buf as &mut [u8], + ) + .await; + assert_eq!(ret, Ok(())); + + // Data compare read and write + for i in 0..test_cfg.w_len { + assert_eq!( + test_cfg.r_buf[i], test_cfg.w_buf[i], + "data mismatch at {}", + i + ); + } + + // Reset read buffer + test_cfg.r_buf.iter_mut().for_each(|x| *x = 0); + + // Read whole test region + let ret = flash_par + .read( + test_cfg.e_offset, + test_cfg.e_len, + test_cfg.r_buf as &mut [u8], + ) + .await; + assert_eq!(ret, Ok(())); + + // Data integrity check + { + for i in 0..test_cfg.w_offset.min(test_cfg.r_buf.len()) { + assert_eq!(test_cfg.r_buf[i], 0xFF, "data mismatch at {}", i); + } + for i in + test_cfg.w_offset..(test_cfg.w_offset + test_cfg.w_len).min(test_cfg.r_buf.len()) + { + assert_eq!( + test_cfg.r_buf[i], + test_cfg.w_buf[i - test_cfg.w_offset], + "data mismatch at {}", + i + ); + } + + for i in (test_cfg.w_offset + test_cfg.w_len).min(test_cfg.r_buf.len()) + ..test_cfg.e_len.min(test_cfg.r_buf.len()) + { + assert_eq!(test_cfg.r_buf[i], 0xFF, "data mismatch at {}", i); + } + } + } +} + // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- diff --git a/runtime/apps/syscall/src/flash.rs b/runtime/apps/syscall/src/flash.rs new file mode 100644 index 0000000..b6db8e9 --- /dev/null +++ b/runtime/apps/syscall/src/flash.rs @@ -0,0 +1,272 @@ +// Licensed under the Apache-2.0 license + +// Flash userspace library + +use core::marker::PhantomData; +use libtock_platform::{share, DefaultConfig, ErrorCode, Syscalls}; +use libtockasync::TockSubscribe; + +pub struct SpiFlash { + syscall: PhantomData, + driver_num: u32, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FlashCapacity(pub u32); + +/// Represents an asynchronous SPI flash memory interface. +/// +/// This struct provides methods to interact with SPI flash memory in an asynchronous manner, +/// allowing for non-blocking read, write and erase operations. +impl SpiFlash { + /// Creates a new instance of `SpiFlash`. + /// + /// # Arguments + /// + /// * `driver_num` - The driver number associated with the SPI flash. + /// + /// # Returns + /// A new instance of `SpiFlash`. + pub fn new(driver_num: u32) -> Self { + Self { + syscall: PhantomData, + driver_num, + } + } + + /// Checks if the SPI flash exists. + /// + /// # Returns + /// + /// * `Ok(())` if the SPI flash exists. + /// * `Err(ErrorCode)` if there is an error. + pub fn exists(&self) -> Result<(), ErrorCode> { + S::command(self.driver_num, flash_storage_cmd::EXISTS, 0, 0).to_result() + } + + /// Gets the capacity of the SPI flash memory that is available to userspace. + /// + /// # Returns + /// + /// * `Ok(FlashCapacity)` with the capacity of the SPI flash memory. + /// * `Err(ErrorCode)` if there is an error. + pub fn get_capacity(&self) -> Result { + S::command(self.driver_num, flash_storage_cmd::GET_CAPACITY, 0, 0) + .to_result() + .map(FlashCapacity) + } + + /// Gets the chunk size for read and write operations. + /// + /// # Returns + /// + /// * `Ok(usize)` with the chunk size for read and write operations. + /// * `Err(ErrorCode)` if there is an error. + pub fn get_chunk_size(&self) -> Result { + S::command(self.driver_num, flash_storage_cmd::GET_CHUNK_SIZE, 0, 0) + .to_result() + .map(|x: u32| x as usize) + } + + /// Internal function to read a chunk of data from the flash memory. + /// Don't use this function directly, use `read` instead. + async fn read_chunk( + &self, + address: usize, + len: usize, + buf: &mut [u8], + ) -> Result<(), ErrorCode> { + // Check if the buffer is large enough and the length is within the chunk size + if buf.len() < len || len > self.get_chunk_size()? { + return Err(ErrorCode::NoMem); + } + + share::scope::<(), _, _>(|_handle| { + let sub = TockSubscribe::subscribe_allow_rw::( + self.driver_num, + subscribe::READ_DONE, + rw_allow::READ, + buf, + ); + + S::command( + self.driver_num, + flash_storage_cmd::READ, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + + Ok(sub) + })? + .await?; + + Ok(()) + } + + /// Reads data from the SPI flash memory in an asynchronous manner. + /// + /// # Arguments + /// * `address` - The address in the SPI flash memory to read from. + /// * `len` - The number of bytes to read. + /// * `buf` - The buffer to read the data into. The buffer must be at least `len` bytes long. + /// + /// # Returns + /// + /// * `Ok(())` if the read operation is successful. + /// * `Err(ErrorCode)` if there is an error. + pub async fn read(&self, address: usize, len: usize, buf: &mut [u8]) -> Result<(), ErrorCode> { + if buf.len() < len { + return Err(ErrorCode::NoMem); + } + + // Split into chunk reads + let chunk_size = self.get_chunk_size()?; + let mut remaining = len; + let mut offset = 0; + while remaining > 0 { + let len = core::cmp::min(remaining, chunk_size); + self.read_chunk(address + offset, len, &mut buf[offset..offset + len]) + .await?; + remaining -= len; + offset += len; + } + + Ok(()) + } + + /// Internal helper function to write a chunk of data to the flash memory. + /// Don't use this function directly, use `write` instead. + async fn write_chunk(&self, address: usize, len: usize, buf: &[u8]) -> Result<(), ErrorCode> { + // Check if the buffer is large enough and the length is within the chunk size + if buf.len() < len || len > self.get_chunk_size()? { + return Err(ErrorCode::NoMem); + } + + share::scope::<(), _, _>(|_handle| { + let sub = TockSubscribe::subscribe_allow_ro::( + self.driver_num, + subscribe::WRITE_DONE, + ro_allow::WRITE, + buf, + ); + + S::command( + self.driver_num, + flash_storage_cmd::WRITE, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + + Ok(sub) + })? + .await?; + + Ok(()) + } + + /// Writes an arbitrary number of bytes to the flash memory in an asynchronous manner. + /// + /// # Arguments + /// + /// * `address` - The starting address to write to. + /// * `len` - The number of bytes to write. + /// * `buf` - The buffer containing the bytes to write. + /// + /// # Returns + /// + /// * `Ok(())` if the write operation is successful. + /// * `Err(ErrorCode)` if there is an error. + pub async fn write(&self, address: usize, len: usize, buf: &[u8]) -> Result<(), ErrorCode> { + if buf.len() < len { + return Err(ErrorCode::NoMem); + } + + // Split into chunk writes + let chunk_size = self.get_chunk_size()?; + let mut remaining = len; + let mut offset = 0; + while remaining > 0 { + let len = core::cmp::min(remaining, chunk_size); + self.write_chunk(address + offset, len, &buf[offset..offset + len]) + .await?; + remaining -= len; + offset += len; + } + + Ok(()) + } + + /// Erases an arbitrary number of bytes from the flash memory. + /// + /// This method erases `len` bytes from the flash memory starting at the specified `address`. + /// + /// # Arguments + /// + /// * `address` - The starting address to erase from. + /// * `len` - The number of bytes to erase. + /// + /// # Returns + /// + /// * `Ok(())` if the erase operation is successful. + /// * `Err(ErrorCode)` if there is an error. + pub async fn erase(&self, address: usize, len: usize) -> Result<(), ErrorCode> { + let async_erase_sub = TockSubscribe::subscribe::(self.driver_num, subscribe::ERASE_DONE); + S::command( + self.driver_num, + flash_storage_cmd::ERASE, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + async_erase_sub.await.map(|_| Ok(()))? + } +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +pub mod driver_num { + pub const IMAGE_PARTITION: u32 = 0x8000_0006; + pub const STAGING_PARTITION: u32 = 0x8000_0007; +} + +mod subscribe { + /// Read done callback. + pub const READ_DONE: u32 = 0; + /// Write done callback. + pub const WRITE_DONE: u32 = 1; + /// Erase done callback + pub const ERASE_DONE: u32 = 2; +} + +/// Ids for read-only allow buffers +mod ro_allow { + /// Setup a buffer to write bytes to the flash storage. + pub const WRITE: u32 = 0; +} + +/// Ids for read-write allow buffers +mod rw_allow { + /// Setup a buffer to read from the flash storage into. + pub const READ: u32 = 0; +} + +/// Command IDs for flash partition driver capsule +/// +/// - `0`: Return Ok(()) if this driver is included on the platform. +/// - `1`: Return flash capacity available to userspace. +/// - `2`: Start a read +/// - `3`: Start a write +/// - `4`: Start an erase +/// - `5`: Get the chunk size for read/write operations. +mod flash_storage_cmd { + pub const EXISTS: u32 = 0; + pub const GET_CAPACITY: u32 = 1; + pub const READ: u32 = 2; + pub const WRITE: u32 = 3; + pub const ERASE: u32 = 4; + pub const GET_CHUNK_SIZE: u32 = 5; +} diff --git a/runtime/apps/syscall/src/lib.rs b/runtime/apps/syscall/src/lib.rs index 1a82009..83abf73 100644 --- a/runtime/apps/syscall/src/lib.rs +++ b/runtime/apps/syscall/src/lib.rs @@ -3,4 +3,5 @@ #![no_std] pub mod dma; +pub mod flash; pub mod mailbox; diff --git a/runtime/capsules/src/flash_partition.rs b/runtime/capsules/src/flash_partition.rs index 74dc382..f035b58 100644 --- a/runtime/capsules/src/flash_partition.rs +++ b/runtime/capsules/src/flash_partition.rs @@ -327,6 +327,7 @@ impl SyscallDriver for FlashPartition<'_> { /// - `2`: Start a read /// - `3`: Start a write /// - `4`: Start an erase + /// - `5`: Return the chunk size for reads and writes fn command( &self, command_num: usize, @@ -387,6 +388,11 @@ impl SyscallDriver for FlashPartition<'_> { } } + 5 => { + // Return the chunk size for reads and writes + CommandReturn::success_u32(BUF_LEN as u32) + } + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } diff --git a/runtime/src/tests/flash_storage_test.rs b/runtime/src/tests/flash_storage_test.rs index 2a0c204..077e6a5 100644 --- a/runtime/src/tests/flash_storage_test.rs +++ b/runtime/src/tests/flash_storage_test.rs @@ -130,11 +130,6 @@ pub(crate) fn test_flash_storage_erase() -> Option { // Start writing data to the entire test range [0..TEST_BUF_LEN) let write_in_buf = test_cb.write_in_buf.take().unwrap(); - // Debug prints the buffer of write_in_buf - for i in 0..16 { - println!("[xs debug]write in buf contents: {:02X}", write_in_buf[i]); - } - assert!(flash_storage_drv .write(write_in_buf, 0, TEST_BUF_LEN) .is_ok());