Skip to content

Commit

Permalink
Flash userspace async API and simple test inside example-app
Browse files Browse the repository at this point in the history
  • Loading branch information
helloxiling committed Dec 21, 2024
1 parent afa092b commit 1174017
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions runtime/apps/apis/flash/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
184 changes: 184 additions & 0 deletions runtime/apps/apis/flash/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<const DRIVER_NUM: u32, S: Syscalls, C: Config = DefaultConfig>(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<const DRIVER_NUM: u32, S: Syscalls, C: Config> AsyncSpiFlash<DRIVER_NUM, S, C> {
/// 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<FlashCapacity, ErrorCode> {
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::<C, DRIVER_NUM, { rw_allow::READ }>(allow_rw, buf)?;

let sub = TockSubscribe::subscribe::<S>(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::<C, DRIVER_NUM, { ro_allow::WRITE }>(allow_ro, buf)?;

let sub = TockSubscribe::subscribe::<S>(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::<S>(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<T: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config>
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;
}
1 change: 1 addition & 0 deletions runtime/apps/example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
74 changes: 73 additions & 1 deletion runtime/apps/example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -74,9 +75,80 @@ pub(crate) async fn async_main<S: Syscalls>() {
sleep::<S>(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::<DRIVER_NUM, S>(&mut console_writer).await;
writeln!(console_writer, "Flash async test done").unwrap();

writeln!(console_writer, "app finished").unwrap();
}


pub async fn flash_test_async<const DRIVER_NUM: u32, S: Syscalls>(
console_writer: &mut ConsoleWriter<S>,
) {
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::<DRIVER_NUM, S>::exists() {
Ok(()) => {}
Err(e) => {
writeln!(
console_writer,
"Flash partition driver does not exist: {:?}",
e
)
.unwrap();
return;
}
}

let capacity = AsyncSpiFlash::<DRIVER_NUM, S>::get_capacity().unwrap();
writeln!(console_writer, "flash partition capacity: {:#X?}", capacity).unwrap();

// Test erase
let ret = AsyncSpiFlash::<DRIVER_NUM, S>::erase(0, 512).await;
assert_eq!(ret, Ok(()));

writeln!(console_writer, "flash test ASYNC erase succeess").unwrap();

// Write test
let ret = AsyncSpiFlash::<DRIVER_NUM, S>::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::<DRIVER_NUM, S>::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
// -----------------------------------------------------------------------------
Expand Down

0 comments on commit 1174017

Please sign in to comment.