| use crate::dprintf; |
| |
| extern crate alloc; |
| use alloc::string::{String, ToString}; |
| use alloc::vec::Vec; |
| use core::cell::Cell; |
| use core::mem::{size_of, transmute}; |
| use core::ptr; |
| use libtock::futures; |
| use libtock::syscalls; |
| use matcha_config::*; |
| use serde::{Deserialize, Serialize}; |
| |
| #[derive(Debug, Serialize, Deserialize)] |
| pub enum SECRequest<'a> { |
| GetBuiltins, // Get package names -> Vec(String) |
| FindFile(&'a str), // Find file by name -> (/*fid*/ u32, /*size*/ u32) |
| GetFilePage(u32, u32), // Read data from fid at offset -> <attached page> |
| |
| Test(/*count*/ u32), // Scribble on count words of supplied page |
| } |
| |
| #[derive(Debug, Serialize, Deserialize)] |
| pub struct GetBuiltinsResponse { |
| pub names: Vec<String>, |
| } |
| |
| #[derive(Debug, Serialize, Deserialize)] |
| pub struct FindFileResponse { |
| pub fid: u32, // Unique file identifier |
| pub size_bytes: u32, // File size |
| } |
| |
| #[derive(Debug, Serialize, Deserialize)] |
| pub struct GetFilePageResponse { |
| pub count_bytes: u32, // Read count (bytes) |
| } |
| |
| #[derive(Debug, Eq, PartialEq)] |
| pub enum SECRequestError { |
| Success = 0, |
| DeserializeFailed, |
| SerializeFailed, |
| UnknownError, |
| PageInvalid, |
| FileNotFound, |
| FileOffsetInvalid, |
| // Generic errors. |
| SendFailed, |
| RecvFailed, |
| } |
| impl From<SECRequestError> for Result<(), SECRequestError> { |
| fn from(err: SECRequestError) -> Result<(), SECRequestError> { |
| if err == SECRequestError::Success { |
| Ok(()) |
| } else { |
| Err(err) |
| } |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| pub struct MailboxClient {} |
| |
| impl MailboxClient { |
| pub fn init() { |
| let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_INIT, 0, 0); |
| let _ = syscalls::command(CAPSULE_STORAGE, CMD_STORAGE_INIT, 0, 0); |
| } |
| |
| // Waits for an inbound message that it writes to |buffer|. The msg |
| // size (in bytes) is returned along with any associated page frame |
| pub async fn wait_message_async(buffer: &mut [u8]) -> (usize, Option<usize>) { |
| // Status, message size (bytes), attached page (or 0) |
| type WaitResult = (Option<bool>, usize, Option<usize>); |
| let message_result: Cell<WaitResult> = Cell::new((None, 0, None)); |
| |
| // Callback on recv complete. |
| unsafe extern "C" fn subscribe_callback(ok: usize, len: usize, page: usize, data: usize) { |
| let message_result = &*(data as *const Cell<WaitResult>); |
| message_result.set(( |
| Some(ok != 0), |
| len, |
| if page == 0 { None } else { Some(page) }, |
| )); |
| } |
| |
| // Give Tock references to our callback, result, +buffer to write; |
| // then wait until the callback sets the message status. Finally, |
| // clear references to our local state. |
| unsafe { |
| syscalls::raw::allow( |
| CAPSULE_MAILBOX, |
| CMD_MAILBOX_RECV, |
| buffer.as_mut_ptr(), |
| buffer.len(), |
| ); |
| syscalls::raw::subscribe( |
| CAPSULE_MAILBOX, |
| CMD_MAILBOX_RECV, |
| subscribe_callback as *const _, |
| &message_result as *const _ as usize, |
| ); |
| } |
| futures::wait_until(|| (message_result.get().0.is_some())).await; |
| unsafe { |
| syscalls::raw::subscribe(CAPSULE_MAILBOX, 0, ptr::null(), 0); |
| syscalls::raw::allow(CAPSULE_MAILBOX, 0, ptr::null_mut(), 0); |
| } |
| |
| let (_, msg_size, attached_page) = message_result.into_inner(); |
| (msg_size, attached_page) |
| } |
| |
| // Sends the message of |size| bytes that resides in |buffer| |
| // XXX why isn't buffer.len() used? |
| pub fn send_message_sync(size: usize, buffer: &[u8]) { |
| unsafe { |
| syscalls::raw::allow( |
| CAPSULE_MAILBOX, |
| CMD_MAILBOX_SEND, |
| buffer.as_ptr() as *mut u8, |
| buffer.len(), |
| ); |
| let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_SEND, size, 0); |
| syscalls::raw::allow(CAPSULE_MAILBOX, CMD_MAILBOX_SEND, ptr::null_mut(), 0); |
| } |
| } |
| |
| // Parse & dispatch the request in |request_slice| and construct the result |
| // in |reply_slice|. |opt_page| holds any page attached to the request. |
| pub async fn dispatch( |
| request_slice: &[u8], |
| opt_page: Option<usize>, |
| reply_slice: &mut [u8], |
| ) -> Result<usize, SECRequestError> { |
| match postcard::from_bytes::<SECRequest>(request_slice) { |
| Ok(request) => { |
| // dprintf!("dispatch {:?} opt_page {:?}\r\n", &request, opt_page); |
| match request { |
| SECRequest::GetBuiltins => { |
| MailboxClient::get_builtins_async(reply_slice).await |
| } |
| SECRequest::FindFile(name) => { |
| MailboxClient::find_file_async(name, reply_slice).await |
| } |
| SECRequest::GetFilePage(fid, offset) => { |
| MailboxClient::get_file_page_async(fid, offset, opt_page, reply_slice).await |
| } |
| SECRequest::Test(count) => { |
| MailboxClient::test_sync(count as usize, opt_page, reply_slice) |
| } |
| } |
| } |
| Err(e) => { |
| dprintf!("Invalid request: {:?}\r\n", e); |
| Err(SECRequestError::DeserializeFailed) |
| } |
| } |
| } |
| |
| // Returns the names of objects that can be looked up using FindFile. |
| async fn get_builtins_async(reply_buffer: &mut [u8]) -> Result<usize, SECRequestError> { |
| const NAME_SIZE: usize = 100; // Arbitrary but matches TarHeader.name |
| let mut name: [u8; NAME_SIZE] = [0u8; NAME_SIZE]; |
| let mut names = Vec::new(); |
| |
| let mut fid: u32 = 0; // First file in SPI |
| loop { |
| // Status, name length, next fid |
| type GetNameResult = (Option<bool>, usize, usize); |
| let get_name_result: Cell<GetNameResult> = Cell::new((None, 0, 0)); |
| |
| unsafe extern "C" fn get_name_callback(ok: usize, name_len: usize, next_fid: usize, data: usize) { |
| let get_name_result = &*(data as *const Cell<GetNameResult>); |
| get_name_result.set((Some(ok != 0), name_len, next_fid)); |
| } |
| unsafe { |
| syscalls::raw::allow( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_GET_NAME, |
| name.as_mut_ptr() as _, |
| NAME_SIZE, |
| ); |
| syscalls::raw::subscribe( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_GET_NAME, |
| get_name_callback as *const _, |
| &get_name_result as *const _ as usize, |
| ); |
| let _ = syscalls::command( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_GET_NAME, |
| fid as usize, |
| 0, |
| ); |
| } |
| futures::wait_until(|| (get_name_result.get().0.is_some())).await; |
| unsafe { |
| syscalls::raw::subscribe(CAPSULE_STORAGE, 0, ptr::null(), 0); |
| syscalls::raw::allow(CAPSULE_STORAGE, 0, ptr::null_mut(), 0); |
| } |
| |
| let (status, name_len, next_fid) = get_name_result.into_inner(); |
| if !status.unwrap() { break; } |
| if let Ok(str) = core::str::from_utf8(&name[..name_len]) { |
| // XXX filter non-builtins? (harder if capdl-loader cpio archive is expanded) |
| match str { |
| "kernel" | |
| "capdl-loader" | |
| "matcha-tock-bundle.bin" => {} |
| _ => names.push(str.to_string()), |
| } |
| } |
| fid = next_fid as u32; |
| } |
| postcard::to_slice(&GetBuiltinsResponse { names }, reply_buffer) |
| .or(Err(SECRequestError::SerializeFailed)) |
| .map(|slice| slice.len()) |
| } |
| |
| // Lookup a file by name. A file identifier (the offset of the file's |
| // data in SPI flash) and the data size (in bytes) is returned. |
| // XXX could use GET_NAME |
| async fn find_file_async( |
| name: &str, |
| reply_buffer: &mut [u8], |
| ) -> Result<usize, SECRequestError> { |
| // Status, file identifier, file size (bytes) |
| type FindResult = (Option<bool>, usize, usize); |
| let find_result: Cell<FindResult> = Cell::new((None, 0, 0)); |
| |
| unsafe extern "C" fn find_callback(ok: usize, fid: usize, size_bytes: usize, data: usize) { |
| let find_result = &*(data as *const Cell<FindResult>); |
| find_result.set((Some(ok != 0), fid, size_bytes)); |
| } |
| unsafe { |
| syscalls::raw::allow( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_FIND_FILE, |
| name.as_ptr() as _, // XXX const |
| name.len(), |
| ); |
| syscalls::raw::subscribe( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_FIND_FILE, |
| find_callback as *const _, |
| &find_result as *const _ as usize, |
| ); |
| let _ = syscalls::command(CAPSULE_STORAGE, CMD_STORAGE_FIND_FILE, 0, 0); |
| } |
| futures::wait_until(|| (find_result.get().0.is_some())).await; |
| unsafe { |
| syscalls::raw::subscribe(CAPSULE_STORAGE, 0, ptr::null(), 0); |
| syscalls::raw::allow(CAPSULE_STORAGE, 0, ptr::null_mut(), 0); |
| } |
| |
| let (ok, fid, size_bytes) = find_result.into_inner(); |
| if ok.unwrap() { |
| postcard::to_slice( |
| &FindFileResponse { |
| fid: fid as u32, |
| size_bytes: size_bytes as u32, |
| }, |
| reply_buffer, |
| ) |
| .or(Err(SECRequestError::SerializeFailed)) |
| .map(|slice| slice.len()) |
| } else { |
| Err(SECRequestError::FileNotFound) |
| } |
| } |
| |
| // Get/read 4KiB (or less at EOF) from |fid| starting at |offset| from |
| // the start of the file. Data is returned in an associated page that |
| // must reside in SMC memory |
| async fn get_file_page_async( |
| fid: u32, |
| offset: u32, |
| opt_page: Option<usize>, |
| reply_buffer: &mut [u8], |
| ) -> Result<usize, SECRequestError> { |
| // Status, read count (bytes) |
| type ReadResult = (Option<bool>, usize); |
| let read_result: Cell<ReadResult> = Cell::new((None, 0)); |
| |
| unsafe extern "C" fn read_callback( |
| ok: usize, |
| _fid: usize, |
| count_bytes: usize, |
| data: usize, |
| ) { |
| let read_result = &*(data as *const Cell<ReadResult>); |
| read_result.set((Some(ok != 0), count_bytes)); |
| } |
| unsafe { |
| // XXX check returns |
| syscalls::raw::subscribe( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_READ_PAGE, |
| read_callback as *const _, |
| &read_result as *const _ as usize, |
| ); |
| // NB: cannot use allow to share opt_page because the address |
| // is in the SMC memory and not the app/process. Instead we |
| // pass an opaque value through command. |
| let _ = syscalls::command( |
| CAPSULE_STORAGE, |
| CMD_STORAGE_READ_PAGE, |
| (fid + offset) as usize, |
| opt_page.unwrap() as _, |
| ); |
| } |
| futures::wait_until(|| (read_result.get().0.is_some())).await; |
| unsafe { syscalls::raw::subscribe(CAPSULE_STORAGE, 0, ptr::null(), 0); } |
| |
| let (ok, count_bytes) = read_result.into_inner(); |
| if ok.unwrap() { |
| postcard::to_slice( |
| &GetFilePageResponse { |
| count_bytes: count_bytes as u32, |
| }, |
| reply_buffer, |
| ) |
| .or(Err(SECRequestError::SerializeFailed)) |
| .map(|slice| slice.len()) |
| } else { |
| Err(SECRequestError::FileOffsetInvalid) // XXX guess |
| } |
| } |
| |
| fn test_sync( |
| count: usize, |
| opt_page: Option<usize>, |
| _reply_buffer: &mut [u8], |
| ) -> Result<usize, SECRequestError> { |
| if let Some(page) = opt_page { |
| if count >= (4096 / size_of::<u32>()) { |
| return Err(SECRequestError::PageInvalid); // XXX |
| } |
| // XXX cannot address page; must push to capsule |
| unsafe { |
| dprintf!("page {:#x}", page); |
| let dst: *mut u32 = transmute(page); |
| // Tweak the first and last dword of the message. |
| dprintf!("request[0] = {:#x}\r\n", dst.add(0).read()); |
| dst.add(0).write(0x12345678); |
| if count > 1 { |
| let last = count - 1; |
| dprintf!("request[{}] = {:#x}\r\n", last, dst.add(last).read()); |
| dst.add(last).write(0x87654321); |
| } |
| } |
| Ok(0) // NB: modified page has result |
| } else { |
| Err(SECRequestError::PageInvalid) |
| } |
| } |
| } |