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; }