storage_capsule: initial implementation
Add CMD_GET_NAME for constructing the list of builtins,
CMD_STORAGE_FIND_FILE for finding a file by name, and CMD_STORAGE_READ
for reading a page of data from a previously found file.
CMD_GET_NAME takes a storage offset at which to look for a file
and returns the offset of the next file. If a buffer is allow'd
CMD_GET_NAME copies the file name into the buffer and returns the
length (in bytes).
CMD_STORAGE_FIND_FILE takes a file name and returns a unique "file
identifier" and the file size in bytes. The file identifier is suitable
for issuing CMD_STORAGE_READ requests.
CMD_STORAGE_READ takes a file identifier, an offset into the file, and the
phsical address of an SMC page to write 4KiB (or the remainder at EOF).
More:
- tar searches depend on the per-file magic field being set (true in
any POSIX-compliant tarball); otherwise falling back to searching
the entire SPI flash (set at 16MiB)
- the file offset specified for read's must be SPI flash page-aligned
- SMC memory bounds are baked into smc_is_page
- only one app at a time is supported
Bug: 294433731
Change-Id: I0b731707890f0506800938f828a0782b0dbc7264
diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs
index 4051ccb..2e1ce90 100644
--- a/capsules/src/lib.rs
+++ b/capsules/src/lib.rs
@@ -4,5 +4,5 @@
pub mod dprintf_capsule;
pub mod elfloader_capsule;
pub mod mailbox_capsule;
-pub mod storage_capsule;
pub mod nexus_spiflash;
+pub mod storage_capsule;
diff --git a/capsules/src/storage_capsule.rs b/capsules/src/storage_capsule.rs
index 0dbfed1..db549c7 100644
--- a/capsules/src/storage_capsule.rs
+++ b/capsules/src/storage_capsule.rs
@@ -1,57 +1,163 @@
-//! Stub StorageManager capsule that doesn't do anything yet.
+//! StorageManager capsule
+use core::cell::Cell;
+use core::marker::PhantomData;
+use core::mem::size_of;
+use kernel::common::cells::TakeCell;
+use kernel::hil;
+use kernel::hil::flash::Flash;
use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
-// TODO(sleffler): remove dprintf noise once this code does something
+use matcha_config::*;
use matcha_hal::dprintf;
+use matcha_utils::SMC_PAGE_SIZE;
+use matcha_utils::smc_is_page;
+use matcha_utils::smc_ram_memcpy;
+use matcha_utils::tar_loader::TarHeader;
+
+const FLASH_END: usize = 16 * 1024 * 1024; // 16MiB SPI flash
+
+// Returns true if the specified SMC page address fits in SPI flash
+fn is_spi_page(addr: usize) -> bool {
+ addr + SMC_PAGE_SIZE < FLASH_END
+}
+
+#[inline]
+fn howmany(a: u32, b: u32) -> u32 { (a + b - 1) / b }
+#[inline]
+fn roundup(a: u32, b: u32) -> u32 { howmany(a, b) * b }
#[derive(Default)]
pub struct AppData {
- pub callback: Option<Callback>,
+ // XXX only need 1 callback since we're single-threaded
+ pub get_name_callback: Option<Callback>,
+ pub find_file_callback: Option<Callback>,
+ pub read_page_callback: Option<Callback>,
pub buffer: Option<AppSlice<Shared, u8>>,
- pub minor_num: usize,
- pub arg2: usize,
- pub arg3: usize,
}
-pub struct StorageCapsule {
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum StorageState {
+ Idle,
+ FindFile(u32 /* cursor */), // NB: filename passed with allow
+ ReadPage(u32 /* fid */, u32 /* offset */, u32 /* dest */),
+ GetName(u32 /* cursor */), // NB: filename returned with allow
+}
+impl StorageState {
+ fn is_idle(&self) -> bool {
+ match self {
+ StorageState::Idle => true,
+ _ => false,
+ }
+ }
+}
+
+pub struct StorageCapsule<'a, F: hil::flash::Flash + 'static> {
app_data_grant: Grant<AppData>,
+ pub current_app: Cell<Option<AppId>>,
+ flash: Option<&'static capsules::virtual_flash::FlashUser<'static, F>>,
+ flash_busy: Cell<bool>,
+ read_page: TakeCell<'static, F::Page>,
+ page_len: u32,
+ state: Cell<StorageState>,
+ phantom: PhantomData<&'a ()>,
}
-impl StorageCapsule {
+impl<'a, F: hil::flash::Flash> StorageCapsule<'a, F> {
pub fn new(app_data_grant: Grant<AppData>) -> Self {
- return StorageCapsule {
+ Self {
app_data_grant: app_data_grant,
- };
+ current_app: Cell::new(None),
+ flash: None,
+ flash_busy: Cell::new(false),
+ read_page: TakeCell::empty(),
+ page_len: 0,
+ state: Cell::new(StorageState::Idle),
+ phantom: PhantomData,
+ }
+ }
+
+ pub fn set_flash(
+ &mut self,
+ flash: &'static capsules::virtual_flash::FlashUser<'static, F>,
+ read_page: &'static mut F::Page,
+ ) {
+ self.flash = Some(flash);
+ self.read_page.replace(read_page);
+ let mut page_len = 0;
+ self.read_page.map(|page| {
+ let mut_page = page.as_mut();
+ page_len = mut_page.len() as u32;
+ });
+ self.page_len = page_len;
}
pub fn handle_command(
&self,
+ app_id: AppId,
app_data: &mut AppData,
minor_num: usize,
arg2: usize,
arg3: usize,
) -> ReturnCode {
- dprintf!(
- "StorageCapsule::handle_command({}, {}, {})",
- minor_num,
- arg2,
- arg3
- );
- app_data.minor_num = minor_num;
- app_data.arg2 = arg2;
- app_data.arg3 = arg3;
match minor_num {
- 0 => ReturnCode::SUCCESS,
- 1 => {
- if let Some(mut callback) = app_data.callback {
- dprintf!("StorageCapsule::handle_command : Calling callback!");
- callback.schedule(1, 2, 3);
- app_data.callback = Some(callback);
- ReturnCode::SUCCESS
- } else {
- dprintf!("StorageCapsule::handle_command : No callback!");
- ReturnCode::EINVAL
+ CMD_STORAGE_INIT => {
+ self.current_app.set(Some(app_id));
+ ReturnCode::SUCCESS
+ }
+ CMD_STORAGE_GET_NAME => {
+ let state = self.state.get();
+ if !state.is_idle() {
+ dprintf!("storage: GET_NAME: state {:?}\r\n", state);
+ return ReturnCode::EBUSY;
}
+ // NB: buffer is optional
+ let fid = arg2 as u32;
+ self.state.set(StorageState::GetName(fid));
+ self.read_page(fid); // First read, kicks off state machine
+ return ReturnCode::SUCCESS
+ }
+ CMD_STORAGE_FIND_FILE => {
+ let state = self.state.get();
+ if !state.is_idle() {
+ dprintf!("storage: FIND_FILE: state {:?}\r\n", state);
+ return ReturnCode::EBUSY;
+ }
+ // Verify the filename has been passed to allow and check
+ // the string is valid so work inside the state machine
+ // can just unwrap the result of str::from_utf8.
+ if let Some(app_slice) = app_data.buffer.as_ref() {
+ if core::str::from_utf8(app_slice.as_ref()).is_ok() {
+ self.state.set(StorageState::FindFile(0));
+ self.read_page(0); // First read, kicks off state machine
+ return ReturnCode::SUCCESS
+ }
+ }
+ ReturnCode::EINVAL
+ }
+ CMD_STORAGE_READ_PAGE => {
+ let state = self.state.get();
+ if !state.is_idle() {
+ dprintf!("storage: READ: state {:?}\r\n", state);
+ return ReturnCode::EBUSY;
+ }
+ // NB: this checks the entire 4K page can be read from SPI;
+ // we don't really handle reading less than a full page.
+ let fid = arg2 as u32;
+ if !is_spi_page(fid as usize) {
+ dprintf!("storage: READ: extends past the end-of-flash (fid {})\r\n", fid);
+ return ReturnCode::EINVAL;
+ }
+ // The best we can do is check |dest| looks like an SMC
+ // address; otherwise we just spray data where ever the
+ // SecurityCoordinator tells us.
+ let dest = arg3 as u32;
+ if !smc_is_page(dest as usize) {
+ dprintf!("storage: READ: destination not in SMC (dest {:#x})\r\n", dest);
+ return ReturnCode::EINVAL;
+ }
+ self.state.set(StorageState::ReadPage(fid, 0, dest));
+ self.read_page(fid); // First read, kicks off state machine
+ ReturnCode::SUCCESS
}
_ => ReturnCode::EINVAL,
}
@@ -59,43 +165,217 @@
pub fn handle_subscribe(
&self,
+ _app_id: AppId,
app_data: &mut AppData,
minor_num: usize,
callback: Option<Callback>,
) -> ReturnCode {
- dprintf!("StorageCapsule::handle_subscribe({})", minor_num);
- if callback.is_some() {
- dprintf!("StorageCapsule::handle_subscribe got Some callback");
- } else {
- dprintf!("StorageCapsule::handle_subscribe got None callback");
+ match minor_num {
+ CMD_STORAGE_GET_NAME => {
+ app_data.get_name_callback = callback;
+ ReturnCode::SUCCESS
+ }
+ CMD_STORAGE_FIND_FILE => {
+ app_data.find_file_callback = callback;
+ ReturnCode::SUCCESS
+ }
+ CMD_STORAGE_READ_PAGE => {
+ app_data.read_page_callback = callback;
+ ReturnCode::SUCCESS
+ }
+ _ => ReturnCode::EINVAL,
}
- app_data.callback = callback;
- return ReturnCode::SUCCESS;
}
pub fn handle_allow(
&self,
+ _app_id: AppId,
app_data: &mut AppData,
- _minor_num: usize,
+ minor_num: usize,
slice: Option<AppSlice<Shared, u8>>,
) -> ReturnCode {
- if let Some(slice) = slice {
- dprintf!("StorageCapsule::handle_allow({})", slice.len());
- app_data.buffer = Some(slice);
- } else {
- dprintf!("StorageCapsule::handle_allow(None)");
+ match minor_num {
+ CMD_STORAGE_GET_NAME
+ | CMD_STORAGE_FIND_FILE => {
+ app_data.buffer = slice;
+ ReturnCode::SUCCESS
+ }
+ _ => ReturnCode::EINVAL,
}
- return ReturnCode::SUCCESS;
+ }
+
+ // GetName state machine callback on flash read complete.
+ fn get_name_callback(&self) {
+ self.current_app.get().map(|app_id| {
+ let _ = self.app_data_grant.enter(app_id, |app_data, _| {
+ self.read_page.map(|page| {
+ let mut_page = page.as_mut(); // XXX mut?
+ match self.state.get() {
+ StorageState::GetName(cursor) => {
+ let tar_header = TarHeader::from_bytes(mut_page);
+ if tar_header.has_magic() {
+ // Copy back filename if a buffer is setup.
+ let name_len = if let Some(app_slice) = app_data.buffer.as_mut() {
+ let slice = app_slice.as_mut();
+ let tar_bytes = tar_header.name().as_bytes();
+ if slice.len() > tar_bytes.len() {
+ &mut slice[..tar_bytes.len()].copy_from_slice(tar_bytes);
+ tar_bytes.len()
+ } else {
+ slice.copy_from_slice(&tar_bytes[..slice.len()]);
+ slice.len()
+ }
+ } else { 0 };
+ let tar_size = tar_header.size();
+ let next_cursor = cursor
+ + self.page_len
+ + roundup(tar_size, size_of::<TarHeader>() as u32);
+ self.state.set(StorageState::Idle);
+ app_data.get_name_callback.map(|mut callback| {
+ callback.schedule(1, name_len, next_cursor as usize);
+ });
+ } else {
+ // Tar header looks invalid, this is typically how a failed lookup ends.
+ self.state.set(StorageState::Idle);
+ app_data.get_name_callback.map(|mut callback| {
+ callback.schedule(0, 0, 0);
+ });
+ }
+ }
+ _ => panic!("get_name: bad state"),
+ }
+ });
+ });
+ });
+ }
+
+ // FindFile state machine callback on flash read complete.
+ fn find_file_callback(&self) {
+ self.current_app.get().map(|app_id| {
+ let _ = self.app_data_grant.enter(app_id, |app_data, _| {
+ self.read_page.map(|page| {
+ let mut_page = page.as_mut(); // XXX mut?
+ match self.state.get() {
+ StorageState::FindFile(cursor) => {
+ let tar_header = TarHeader::from_bytes(mut_page);
+ if tar_header.has_magic() {
+ // NB: app_slice is checked to be valid in command; we assume
+ // it does not change while we're running
+ let app_slice = app_data.buffer.as_ref().unwrap();
+ let name = core::str::from_utf8(app_slice.as_ref()).unwrap();
+ let tar_name = tar_header.name();
+ let tar_size = tar_header.size();
+ if tar_name == name {
+ // Found file, return fid = the offset to the first byte of data
+ self.state.set(StorageState::Idle);
+ app_data.find_file_callback.map(|mut callback| {
+ callback.schedule(
+ 1, // success
+ (cursor as usize) + size_of::<TarHeader>(), // fid
+ tar_size as usize, // file size (bytes)
+ );
+ });
+ } else {
+ // Advance to the next file if possible. We depend on
+ // each header having a magic marker; otherwise we use
+ // a fixed bound on the flash size to (try to) avoid
+ // reading past the end of memory.
+ let next_cursor = cursor
+ + self.page_len
+ + roundup(tar_size, size_of::<TarHeader>() as u32);
+ if ((next_cursor as usize) + size_of::<TarHeader>()) >= FLASH_END {
+ dprintf!("storage: {} not found, end of flash\r\n", name);
+ self.state.set(StorageState::Idle);
+ app_data.find_file_callback.map(|mut callback| {
+ callback.schedule(0, 0, 0);
+ });
+ } else {
+ self.state.set(StorageState::FindFile(next_cursor));
+ // NB: defer read to below so we release self.read_page
+ }
+ }
+ } else {
+ // Tar header looks invalid, this is typically how a failed lookup ends.
+ self.state.set(StorageState::Idle);
+ app_data.find_file_callback.map(|mut callback| {
+ callback.schedule(0, 0, 0);
+ });
+ }
+ }
+ _ => panic!("find_file: bad state"),
+ }
+ });
+ match self.state.get() {
+ StorageState::FindFile(cursor) => self.read_page(cursor),
+ _ => {}
+ }
+ });
+ });
+ }
+
+ // ReadPage state machine callback on flash read complete.
+ fn read_page_callback(&self) {
+ self.current_app.get().map(|app_id| {
+ let _ = self.app_data_grant.enter(app_id, |app_data, _| {
+ self.read_page.map(|page| {
+ let mut_page = page.as_mut();
+ match self.state.get() {
+ StorageState::ReadPage(fid, offset, dest) => {
+// dprintf!("copy page {} to {:x}\r\n", fid + offset, dest);
+ smc_ram_memcpy(
+ mut_page,
+ dest,
+ self.page_len as usize, // XXX short read?
+ );
+ let next_offset = offset + self.page_len;
+ if next_offset >= (SMC_PAGE_SIZE as u32) {
+ self.state.set(StorageState::Idle);
+ app_data.read_page_callback.map(|mut callback| {
+ callback.schedule(1, fid as usize, SMC_PAGE_SIZE);
+ });
+ } else {
+ self.state.set(
+ StorageState::ReadPage(
+ fid,
+ next_offset,
+ dest + self.page_len,
+ )
+ );
+ // NB: defer read to below so we release self.read_page
+ }
+ }
+ _ => panic!("read_page: bad state"),
+ }
+ });
+ match self.state.get() {
+ StorageState::ReadPage(fid, offset, _) => self.read_page(fid + offset),
+ _ => {}
+ }
+ });
+ });
+ }
+
+ // Kicks off a SPI flash read at the byte offset |page|.
+ fn read_page(&self, page: u32) {
+ debug_assert!((page % self.page_len) == 0);
+ self.flash.map(|flash| {
+ self.read_page.take().map(|read_page| {
+ self.flash_busy.set(true);
+ if let Err((_, buf)) = flash.read_page((page / self.page_len) as usize, read_page) {
+ self.read_page.replace(buf);
+ }
+ });
+ });
}
}
/// Driver impl just enters the app_data grant and delegates to StorageCapsule.
-impl Driver for StorageCapsule {
+impl<'a, F: hil::flash::Flash> Driver for StorageCapsule<'a, F> {
fn subscribe(&self, minor_num: usize, callback: Option<Callback>, app_id: AppId) -> ReturnCode {
self.app_data_grant
.enter(app_id, |app_data, _| {
- self.handle_subscribe(app_data, minor_num, callback)
+ self.handle_subscribe(app_id, app_data, minor_num, callback)
})
.unwrap_or_else(|err| err.into())
}
@@ -103,7 +383,7 @@
fn command(&self, minor_num: usize, r2: usize, r3: usize, app_id: AppId) -> ReturnCode {
self.app_data_grant
.enter(app_id, |app_data, _| {
- self.handle_command(app_data, minor_num, r2, r3)
+ self.handle_command(app_id, app_data, minor_num, r2, r3)
})
.unwrap_or_else(|err| err.into())
}
@@ -116,8 +396,33 @@
) -> ReturnCode {
self.app_data_grant
.enter(app_id, |app_data, _| {
- self.handle_allow(app_data, minor_num, slice)
+ self.handle_allow(app_id, app_data, minor_num, slice)
})
.unwrap_or_else(|err| err.into())
}
}
+
+impl<'a, F: hil::flash::Flash> hil::flash::Client<capsules::virtual_flash::FlashUser<'a, F>>
+ for StorageCapsule<'a, F>
+{
+ fn read_complete(&self, read_page: &'static mut F::Page, _err: kernel::hil::flash::Error) {
+ self.read_page.replace(read_page);
+ self.flash_busy.set(false);
+ match self.state.get() {
+ StorageState::GetName(..) => self.get_name_callback(),
+ StorageState::FindFile(..) => self.find_file_callback(),
+ StorageState::ReadPage(..) => self.read_page_callback(),
+ _ => panic!("bad state"),
+ }
+ }
+ fn write_complete(
+ &self,
+ _: &'static mut <F as kernel::hil::flash::Flash>::Page,
+ _: kernel::hil::flash::Error,
+ ) {
+ todo!()
+ }
+ fn erase_complete(&self, _: kernel::hil::flash::Error) {
+ todo!()
+ }
+}
diff --git a/config/src/lib.rs b/config/src/lib.rs
index aa777b3..14d4d19 100644
--- a/config/src/lib.rs
+++ b/config/src/lib.rs
@@ -46,5 +46,10 @@
pub const SPI_HOST0_BASE_ADDRESS: u32 = 0x4030_0000; // TOP_MATCHA_SPI_HOST0_BASE_ADDR
pub const SPI_HOST0_SPI_EVENT_IRQ: u32 = 132; // kTopMatchaPlicIrqIdSpiHost0SpiEvent
+pub const CMD_STORAGE_INIT: usize = 1;
+pub const CMD_STORAGE_GET_NAME: usize = 2;
+pub const CMD_STORAGE_FIND_FILE: usize = 3;
+pub const CMD_STORAGE_READ_PAGE: usize = 4;
+
pub const UART0_BASE_ADDRESS: u32 = 0x40000000; // TOP_MATCHA_UART0_BASE_ADDR
pub const UART0_BAUDRATE: u32 = 115200;
diff --git a/platform/src/main.rs b/platform/src/main.rs
index 0d96a7e..2ce5d09 100644
--- a/platform/src/main.rs
+++ b/platform/src/main.rs
@@ -119,7 +119,13 @@
VirtualMuxAlarm<'static, timer_hal::RvTimer<'static>>,
>,
dprintf_capsule: &'static DprintfCapsule,
- storage_capsule: &'static StorageCapsule,
+ storage_capsule: &'static StorageCapsule<
+ 'static,
+ matcha_capsules::nexus_spiflash::NexusSpiflash<
+ 'static,
+ capsules::virtual_spi::VirtualSpiMasterDevice<'static, spi_host_hal::SpiHw>,
+ >,
+ >,
elfloader_capsule: &'static ElfLoaderCapsule<
'static,
matcha_capsules::nexus_spiflash::NexusSpiflash<
@@ -246,9 +252,14 @@
);
let storage_capsule = static_init!(
- matcha_capsules::storage_capsule::StorageCapsule,
+ matcha_capsules::storage_capsule::StorageCapsule<
+ 'static,
+ matcha_capsules::nexus_spiflash::NexusSpiflash<
+ 'static,
+ capsules::virtual_spi::VirtualSpiMasterDevice<'static, spi_host_hal::SpiHw>,
+ >>,
matcha_capsules::storage_capsule::StorageCapsule::new(
- board_kernel.create_grant(&memory_allocation_cap)
+ board_kernel.create_grant(&memory_allocation_cap)
)
);
@@ -325,22 +336,38 @@
capsules::virtual_flash::MuxFlash::new(nexus_spiflash)
);
hil::flash::HasClient::set_client(nexus_spiflash, mux_flash);
- let virtual_flash = static_init!(
+
+ // Storage access reads (only) from flash.
+ let storage_virtual_flash = static_init!(
capsules::virtual_flash::FlashUser<'static, matcha_capsules::nexus_spiflash::NexusSpiflash<
'static,
capsules::virtual_spi::VirtualSpiMasterDevice<'static, spi_host_hal::SpiHw>,
>>,
capsules::virtual_flash::FlashUser::new(mux_flash)
);
- let flash_read_page: &'static mut matcha_capsules::nexus_spiflash::NexusSpiflashPage = static_init!(
+ let storage_read_page: &'static mut matcha_capsules::nexus_spiflash::NexusSpiflashPage = static_init!(
matcha_capsules::nexus_spiflash::NexusSpiflashPage,
matcha_capsules::nexus_spiflash::NexusSpiflashPage::default()
);
+ storage_capsule.set_flash(storage_virtual_flash, storage_read_page);
+ storage_virtual_flash.set_client(storage_capsule);
- elfloader_capsule.set_flash(virtual_flash, flash_read_page);
+ // Elfloader reads artifacts from flash & boots the SMC.
elfloader_capsule.set_smc_ctrl(&smc_ctrl_hal::SMC_CTRL);
- virtual_flash.set_client(elfloader_capsule);
- // elfloader_capsule.load_sel4();
+ let elfloader_virtual_flash = static_init!(
+ capsules::virtual_flash::FlashUser<'static, matcha_capsules::nexus_spiflash::NexusSpiflash<
+ 'static,
+ capsules::virtual_spi::VirtualSpiMasterDevice<'static, spi_host_hal::SpiHw>,
+ >>,
+ capsules::virtual_flash::FlashUser::new(mux_flash)
+ );
+ let elfloader_read_page: &'static mut matcha_capsules::nexus_spiflash::NexusSpiflashPage = static_init!(
+ matcha_capsules::nexus_spiflash::NexusSpiflashPage,
+ matcha_capsules::nexus_spiflash::NexusSpiflashPage::default()
+ );
+ elfloader_capsule.set_flash(elfloader_virtual_flash, elfloader_read_page);
+ elfloader_virtual_flash.set_client(elfloader_capsule);
+ // NB: main app triggers the Elfloader bootstrap of seL4
let platform = MatchaPlatform {
console_capsule: console_capsule,
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index d21d6e9..25f0f1f 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -13,6 +13,15 @@
pub const SMC_CONTROL_BLOCK: *mut u32 = 0x54020000 as *mut u32;
+pub const SMC_PAGE_SIZE: usize = 4096; // SMC page size
+pub const SMC_BEGIN: usize = 0x50000000; // Start of SMC memory in SEC map
+pub const SMC_END: usize = SMC_BEGIN + (4*1024*1024); // NB: 4MiB of SMC memory
+
+// Returns true if the specified page address fits in SMC memory
+pub fn smc_is_page(addr: usize) -> bool {
+ SMC_BEGIN <= addr && (addr + SMC_PAGE_SIZE) < SMC_END
+}
+
pub fn round_up_to_page(addr: u32) -> u32 {
return (addr + 4095) & !4095;
}