| //! 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}; |
| 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 { |
| // 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>>, |
| } |
| |
| #[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<'a, F: hil::flash::Flash> StorageCapsule<'a, F> { |
| pub fn new(app_data_grant: Grant<AppData>) -> Self { |
| 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 { |
| match minor_num { |
| 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, |
| } |
| } |
| |
| pub fn handle_subscribe( |
| &self, |
| _app_id: AppId, |
| app_data: &mut AppData, |
| minor_num: usize, |
| callback: Option<Callback>, |
| ) -> ReturnCode { |
| 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, |
| } |
| } |
| |
| pub fn handle_allow( |
| &self, |
| _app_id: AppId, |
| app_data: &mut AppData, |
| minor_num: usize, |
| slice: Option<AppSlice<Shared, u8>>, |
| ) -> ReturnCode { |
| match minor_num { |
| CMD_STORAGE_GET_NAME |
| | CMD_STORAGE_FIND_FILE => { |
| app_data.buffer = slice; |
| ReturnCode::SUCCESS |
| } |
| _ => ReturnCode::EINVAL, |
| } |
| } |
| |
| // 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<'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_id, app_data, minor_num, callback) |
| }) |
| .unwrap_or_else(|err| err.into()) |
| } |
| |
| 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_id, app_data, minor_num, r2, r3) |
| }) |
| .unwrap_or_else(|err| err.into()) |
| } |
| |
| fn allow( |
| &self, |
| app_id: AppId, |
| minor_num: usize, |
| slice: Option<AppSlice<Shared, u8>>, |
| ) -> ReturnCode { |
| self.app_data_grant |
| .enter(app_id, |app_data, _| { |
| 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!() |
| } |
| } |