Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flash userspace async API and simple test inside example-app #68

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
73 changes: 72 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,79 @@ 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