// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use super::{Index, Storage, StorageError, StorageResult}; use alloc::vec::Vec; use libtock::syscalls; const DRIVER_NUMBER: usize = 0x50003; mod command_nr { pub const GET_INFO: usize = 1; pub mod get_info_nr { pub const WORD_SIZE: usize = 0; pub const PAGE_SIZE: usize = 1; pub const MAX_WORD_WRITES: usize = 2; pub const MAX_PAGE_ERASES: usize = 3; } pub const WRITE_SLICE: usize = 2; pub const ERASE_PAGE: usize = 3; } mod allow_nr { pub const WRITE_SLICE: usize = 0; } mod memop_nr { pub const STORAGE_CNT: u32 = 12; pub const STORAGE_PTR: u32 = 13; pub const STORAGE_LEN: u32 = 14; } fn get_info(nr: usize, arg: usize) -> StorageResult { let code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, arg) }; if code < 0 { Err(StorageError::KernelError { code }) } else { Ok(code as usize) } } fn memop(nr: u32, arg: usize) -> StorageResult { let code = unsafe { syscalls::memop(nr, arg) }; if code < 0 { Err(StorageError::KernelError { code }) } else { Ok(code as usize) } } pub struct SyscallStorage { word_size: usize, page_size: usize, num_pages: usize, max_word_writes: usize, max_page_erases: usize, storage_locations: Vec<&'static [u8]>, } impl SyscallStorage { /// Provides access to the embedded flash if available. /// /// # Errors /// /// Returns `BadFlash` if any of the following conditions do not hold: /// - The word size is a power of two. /// - The page size is a power of two. /// - The page size is a multiple of the word size. /// - The storage is page-aligned. /// /// Returns `OutOfBounds` the number of pages does not fit in the storage. pub fn new(mut num_pages: usize) -> StorageResult { let mut syscall = SyscallStorage { word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?, page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?, num_pages, max_word_writes: get_info(command_nr::get_info_nr::MAX_WORD_WRITES, 0)?, max_page_erases: get_info(command_nr::get_info_nr::MAX_PAGE_ERASES, 0)?, storage_locations: Vec::new(), }; if !syscall.word_size.is_power_of_two() || !syscall.page_size.is_power_of_two() || !syscall.is_word_aligned(syscall.page_size) { return Err(StorageError::BadFlash); } for i in 0..memop(memop_nr::STORAGE_CNT, 0)? { let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?; let max_storage_len = memop(memop_nr::STORAGE_LEN, i)?; if !syscall.is_page_aligned(storage_ptr) || !syscall.is_page_aligned(max_storage_len) { return Err(StorageError::BadFlash); } let storage_len = core::cmp::min(num_pages * syscall.page_size, max_storage_len); num_pages -= storage_len / syscall.page_size; syscall .storage_locations .push(unsafe { core::slice::from_raw_parts(storage_ptr as *mut u8, storage_len) }); } if num_pages > 0 { // The storage locations don't have enough pages. return Err(StorageError::OutOfBounds); } Ok(syscall) } fn is_word_aligned(&self, x: usize) -> bool { x & (self.word_size - 1) == 0 } fn is_page_aligned(&self, x: usize) -> bool { x & (self.page_size - 1) == 0 } } impl Storage for SyscallStorage { fn word_size(&self) -> usize { self.word_size } fn page_size(&self) -> usize { self.page_size } fn num_pages(&self) -> usize { self.num_pages } fn max_word_writes(&self) -> usize { self.max_word_writes } fn max_page_erases(&self) -> usize { self.max_page_erases } fn read_slice(&self, index: Index, length: usize) -> StorageResult<&[u8]> { let start = index.range(length, self)?.start; find_slice(&self.storage_locations, start, length) } fn write_slice(&mut self, index: Index, value: &[u8]) -> StorageResult<()> { if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) { return Err(StorageError::NotAligned); } let ptr = self.read_slice(index, value.len())?.as_ptr() as usize; let code = unsafe { syscalls::allow_ptr( DRIVER_NUMBER, allow_nr::WRITE_SLICE, // We rely on the driver not writing to the slice. This should use read-only allow // when available. See https://github.com/tock/tock/issues/1274. value.as_ptr() as *mut u8, value.len(), ) }; if code < 0 { return Err(StorageError::KernelError { code }); } let code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len()) }; if code < 0 { return Err(StorageError::KernelError { code }); } Ok(()) } fn erase_page(&mut self, page: usize) -> StorageResult<()> { let index = Index { page, byte: 0 }; let length = self.page_size(); let ptr = self.read_slice(index, length)?.as_ptr() as usize; let code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, length) }; if code < 0 { return Err(StorageError::KernelError { code }); } Ok(()) } } fn find_slice<'a>( slices: &'a [&'a [u8]], mut start: usize, length: usize, ) -> StorageResult<&'a [u8]> { for slice in slices { if start >= slice.len() { start -= slice.len(); continue; } if start + length > slice.len() { break; } return Ok(&slice[start..][..length]); } Err(StorageError::OutOfBounds) } #[cfg(test)] mod tests { use super::*; #[test] fn find_slice_ok() { assert_eq!( find_slice(&[&[1, 2, 3, 4]], 0, 4).ok(), Some(&[1u8, 2, 3, 4] as &[u8]) ); assert_eq!( find_slice(&[&[1, 2, 3, 4], &[5, 6]], 1, 2).ok(), Some(&[2u8, 3] as &[u8]) ); assert_eq!( find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 2).ok(), Some(&[5u8, 6] as &[u8]) ); assert_eq!( find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 0).ok(), Some(&[] as &[u8]) ); assert!(find_slice(&[], 0, 1).is_err()); assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 6, 0).is_err()); assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 3, 2).is_err()); } }