diff --git a/Cargo.lock b/Cargo.lock index e934616..9899e0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,6 +1120,7 @@ dependencies = [ "libtock_alarm", "libtock_console", "libtock_debug_panic", + "libtock_flash", "libtock_platform", "libtock_runtime", "libtock_unittest", @@ -1527,6 +1528,16 @@ dependencies = [ "libtock_runtime", ] +[[package]] +name = "libtock_flash" +version = "0.1.0" +dependencies = [ + "libtock", + "libtock_platform", + "libtockasync", + "romtime", +] + [[package]] name = "libtock_gpio" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bf98044..2f73ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "romtime", "runtime", "runtime/apps/example", + "runtime/apps/apis/flash", "runtime/apps/libtock/apis/interface/buttons", "runtime/apps/libtock/apis/interface/buzzer", "runtime/apps/libtock/apis/interface/console", diff --git a/runtime/apps/apis/flash/Cargo.toml b/runtime/apps/apis/flash/Cargo.toml new file mode 100644 index 0000000..358f9cd --- /dev/null +++ b/runtime/apps/apis/flash/Cargo.toml @@ -0,0 +1,15 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "libtock_flash" +version = "0.1.0" +edition = "2021" + + +[dependencies] +libtock = { path = "../../libtock" } +libtock_platform = { path = "../../libtock/platform" } +libtockasync = { path = "../../libtockasync" } + +# TODO: Remove this when creating PR +romtime.workspace = true diff --git a/runtime/apps/apis/flash/src/lib.rs b/runtime/apps/apis/flash/src/lib.rs new file mode 100644 index 0000000..9530adf --- /dev/null +++ b/runtime/apps/apis/flash/src/lib.rs @@ -0,0 +1,184 @@ +// Licensed under the Apache-2.0 license + +// Flash userspace library + +#![no_std] + +use libtock_platform as platform; +use libtock_platform::allow_rw::AllowRw; +use libtock_platform::share; +use libtock_platform::AllowRo; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; +use libtockasync::TockSubscribe; + +pub struct AsyncSpiFlash(S, C); + +#[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 AsyncSpiFlash { + /// Checks if the SPI flash exists. + /// + /// # Returns + /// + /// * `Ok(())` if the SPI flash exists. + /// * `Err(ErrorCode)` if there is an error. + pub fn exists() -> Result<(), ErrorCode> { + S::command(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() -> Result { + S::command(DRIVER_NUM, flash_storage_cmd::CAPACITY, 0, 0) + .to_result() + .map(FlashCapacity) + } + + /// 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(address: usize, len: usize, buf: &mut [u8]) -> Result<(), ErrorCode> { + if buf.len() < len { + return Err(ErrorCode::NoMem); + } + + let async_read_sub = + share::scope::<(AllowRw<_, DRIVER_NUM, { rw_allow::READ }>,), _, _>(|handle| { + let allow_rw = handle.split().0; + S::allow_rw::(allow_rw, buf)?; + + let sub = TockSubscribe::subscribe::(DRIVER_NUM, subscribe::READ_DONE); + S::command( + DRIVER_NUM, + flash_storage_cmd::READ, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + + Ok(sub) + })?; + + async_read_sub.await.map(|_| Ok(()))? + } + + pub async fn write(address: usize, len: usize, buf: &[u8]) -> Result<(), ErrorCode> { + let async_write_sub = + share::scope::<(AllowRo<_, DRIVER_NUM, { ro_allow::WRITE }>,), _, _>(|handle| { + let allow_ro = handle.split().0; + S::allow_ro::(allow_ro, buf)?; + + let sub = TockSubscribe::subscribe::(DRIVER_NUM, subscribe::WRITE_DONE); + + S::command( + DRIVER_NUM, + flash_storage_cmd::WRITE, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + + Ok(sub) + })?; + + async_write_sub.await.map(|_| 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(address: usize, len: usize) -> Result<(), ErrorCode> { + let async_erase_sub = TockSubscribe::subscribe::(DRIVER_NUM, subscribe::ERASE_DONE); + S::command( + DRIVER_NUM, + flash_storage_cmd::ERASE, + address as u32, + len as u32, + ) + .to_result::<(), ErrorCode>()?; + + async_erase_sub.await.map(|_| Ok(()))? + } +} + +/// System call configuration trait for `AsyncSpiFlash`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +// ----------------------------------------------------------------------------- +// 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 +mod flash_storage_cmd { + pub const EXISTS: u32 = 0; + pub const CAPACITY: u32 = 1; + pub const READ: u32 = 2; + pub const WRITE: u32 = 3; + pub const ERASE: u32 = 4; +} diff --git a/runtime/apps/example/Cargo.toml b/runtime/apps/example/Cargo.toml index 7d6650c..c269217 100644 --- a/runtime/apps/example/Cargo.toml +++ b/runtime/apps/example/Cargo.toml @@ -11,6 +11,7 @@ critical-section = "1.1.2" embassy-executor = { version = "0.6.3", features = ["nightly"] } libtock = { path = "../libtock" } libtock_alarm = { path = "../libtock/apis/peripherals/alarm" } +libtock_flash = { path = "../apis/flash" } libtock_console = { path = "../libtock/apis/interface/console" } libtock_debug_panic = { path = "../libtock/panic_handlers/debug_panic" } libtock_platform = { path = "../libtock/platform" } diff --git a/runtime/apps/example/src/main.rs b/runtime/apps/example/src/main.rs index a3cc49f..ee5fe2e 100644 --- a/runtime/apps/example/src/main.rs +++ b/runtime/apps/example/src/main.rs @@ -7,7 +7,8 @@ use core::fmt::Write; use libtock::alarm::*; -use libtock_console::Console; +use libtock_console::{Console, ConsoleWriter}; +use libtock_flash::{driver_num, AsyncSpiFlash}; use libtock_platform::{self as platform}; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; use libtockasync::TockSubscribe; @@ -74,9 +75,80 @@ pub(crate) async fn async_main() { sleep::(Milliseconds(1)).await; writeln!(console_writer, "async sleeper woke").unwrap(); } + + writeln!(console_writer, "Flash async test start").unwrap(); + // Temporarily add usermode flash test here + pub const DRIVER_NUM: u32 = driver_num::IMAGE_PARTITION; + flash_test_async::(&mut console_writer).await; + writeln!(console_writer, "Flash async test done").unwrap(); + writeln!(console_writer, "app finished").unwrap(); } + +pub async fn flash_test_async( + console_writer: &mut ConsoleWriter, +) { + struct TestConfig { + start_addr: usize, + length: usize, + w_offset: usize, + w_len: usize, + w_buf: [u8; 512], + r_buf: [u8; 512], + } + + let mut test_cfg = TestConfig { + start_addr: 0, + length: 512, + w_offset: 50, + w_len: 512 - 50, + w_buf: [0xAA; 512], + r_buf: [0x0u8; 512], + }; + match AsyncSpiFlash::::exists() { + Ok(()) => {} + Err(e) => { + writeln!( + console_writer, + "Flash partition driver does not exist: {:?}", + e + ) + .unwrap(); + return; + } + } + + let capacity = AsyncSpiFlash::::get_capacity().unwrap(); + writeln!(console_writer, "flash partition capacity: {:#X?}", capacity).unwrap(); + + // Test erase + let ret = AsyncSpiFlash::::erase(0, 512).await; + assert_eq!(ret, Ok(())); + + writeln!(console_writer, "flash test ASYNC erase succeess").unwrap(); + + // Write test + let ret = AsyncSpiFlash::::write( + test_cfg.start_addr + test_cfg.w_offset, + test_cfg.w_len, + &test_cfg.w_buf, + ) + .await; + assert_eq!(ret, Ok(())); + writeln!(console_writer, "flash test ASYNC WRITE succeess").unwrap(); + + // Read test + let ret = AsyncSpiFlash::::read( + test_cfg.start_addr, + test_cfg.length, + &mut test_cfg.r_buf, + ) + .await; + assert_eq!(ret, Ok(())); + writeln!(console_writer, "flash test ASYNC READ succeess").unwrap(); +} + // ----------------------------------------------------------------------------- // Driver number and command IDs // -----------------------------------------------------------------------------