Interface to syscalls to partition location types (#340)
* adds syscalls to use the partition location types * no range implementation, helper file, refactorings * more refactoring of syscall interface * adds and refines trait * improved documentation and partition_length function * simplified ModRange * cleanup * new aligned_iter implementation
This commit is contained in:
77
src/embedded_flash/buffer_upgrade.rs
Normal file
77
src/embedded_flash/buffer_upgrade.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2021 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::helper::ModRange;
|
||||||
|
use super::upgrade_storage::UpgradeStorage;
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use persistent_store::{StorageError, StorageResult};
|
||||||
|
|
||||||
|
const PARTITION_LENGTH: usize = 0x40000;
|
||||||
|
const METADATA_LENGTH: usize = 0x1000;
|
||||||
|
|
||||||
|
pub struct BufferUpgradeStorage {
|
||||||
|
/// Content of the partition storage.
|
||||||
|
partition: Box<[u8]>,
|
||||||
|
|
||||||
|
/// Content of the metadata storage.
|
||||||
|
metadata: Box<[u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferUpgradeStorage {
|
||||||
|
pub fn new() -> StorageResult<BufferUpgradeStorage> {
|
||||||
|
Ok(BufferUpgradeStorage {
|
||||||
|
partition: vec![0xff; PARTITION_LENGTH].into_boxed_slice(),
|
||||||
|
metadata: vec![0xff; METADATA_LENGTH].into_boxed_slice(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpgradeStorage for BufferUpgradeStorage {
|
||||||
|
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
|
||||||
|
let partition_range = ModRange::new(0, self.partition.len());
|
||||||
|
if partition_range.contains_range(&ModRange::new(offset, length)) {
|
||||||
|
Ok(&self.partition[offset..][..length])
|
||||||
|
} else {
|
||||||
|
Err(StorageError::OutOfBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
|
||||||
|
let partition_range = ModRange::new(0, self.partition.len());
|
||||||
|
if partition_range.contains_range(&ModRange::new(offset, data.len())) {
|
||||||
|
self.partition[offset..][..data.len()].copy_from_slice(data);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(StorageError::OutOfBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partition_length(&self) -> usize {
|
||||||
|
PARTITION_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_metadata(&self) -> StorageResult<&[u8]> {
|
||||||
|
Ok(&self.metadata[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
|
||||||
|
if data.len() <= METADATA_LENGTH {
|
||||||
|
self.metadata.copy_from_slice(&[0xff; METADATA_LENGTH]);
|
||||||
|
self.metadata[..data.len()].copy_from_slice(data);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(StorageError::OutOfBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
237
src/embedded_flash/helper.rs
Normal file
237
src/embedded_flash/helper.rs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2019-2021 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.
|
||||||
|
|
||||||
|
// For compiling with std outside of tests.
|
||||||
|
#![cfg_attr(feature = "std", allow(dead_code))]
|
||||||
|
|
||||||
|
use core::iter::Iterator;
|
||||||
|
use persistent_store::{StorageError, StorageResult};
|
||||||
|
|
||||||
|
/// Reads a slice from a list of slices.
|
||||||
|
///
|
||||||
|
/// The returned slice contains the interval `[start, start+length)`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`StorageError::OutOfBounds`] if the range is not within exactly one slice.
|
||||||
|
pub 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the address is aligned with the block size.
|
||||||
|
///
|
||||||
|
/// Requires `block_size` to be a power of two.
|
||||||
|
pub fn is_aligned(block_size: usize, address: usize) -> bool {
|
||||||
|
debug_assert!(block_size.is_power_of_two());
|
||||||
|
address & (block_size - 1) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A range implementation using start and length.
|
||||||
|
///
|
||||||
|
/// The range is treated as the interval `[start, start + length)`.
|
||||||
|
/// All objects with length of 0, regardless of the start value, are considered empty.
|
||||||
|
pub struct ModRange {
|
||||||
|
start: usize,
|
||||||
|
length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModRange {
|
||||||
|
/// Returns a new range of given start and length.
|
||||||
|
pub fn new(start: usize, length: usize) -> ModRange {
|
||||||
|
ModRange { start, length }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new empty range.
|
||||||
|
pub fn new_empty() -> ModRange {
|
||||||
|
ModRange::new(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the start of the range.
|
||||||
|
pub fn start(&self) -> usize {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the range.
|
||||||
|
pub fn length(&self) -> usize {
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this range contains any addresses.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the disjoint union with the other range, if is consecutive.
|
||||||
|
///
|
||||||
|
/// Appending empty ranges is not possible.
|
||||||
|
/// Appending to the empty range returns the other range.
|
||||||
|
pub fn append(&self, other: ModRange) -> Option<ModRange> {
|
||||||
|
if self.is_empty() {
|
||||||
|
return Some(other);
|
||||||
|
}
|
||||||
|
if other.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if self.start >= other.start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if self.length != other.start - self.start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let new_length = self.length.checked_add(other.length);
|
||||||
|
new_length.map(|l| ModRange::new(self.start, l))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the given range is fully contained.
|
||||||
|
///
|
||||||
|
/// Mathematically, we calculate whether: `self ∩ range = range`.
|
||||||
|
pub fn contains_range(&self, range: &ModRange) -> bool {
|
||||||
|
range.is_empty()
|
||||||
|
|| (self.start <= range.start
|
||||||
|
&& range.length <= self.length
|
||||||
|
&& range.start - self.start <= self.length - range.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator for all contained numbers that are divisible by the modulus.
|
||||||
|
pub fn aligned_iter(&self, modulus: usize) -> impl Iterator<Item = usize> {
|
||||||
|
(self.start..=usize::MAX)
|
||||||
|
.take(self.length)
|
||||||
|
// Skip the minimum number of elements to align.
|
||||||
|
.skip((modulus - self.start % modulus) % modulus)
|
||||||
|
// Only return aligned elements.
|
||||||
|
.step_by(modulus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alignment() {
|
||||||
|
for exponent in 0..8 {
|
||||||
|
let block_size = 1 << exponent;
|
||||||
|
for i in 0..10 {
|
||||||
|
assert!(is_aligned(block_size, block_size * i));
|
||||||
|
}
|
||||||
|
for i in 1..block_size {
|
||||||
|
assert!(!is_aligned(block_size, block_size + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mod_range_parameters() {
|
||||||
|
let range = ModRange::new(200, 100);
|
||||||
|
assert_eq!(range.start(), 200);
|
||||||
|
assert_eq!(range.length(), 100);
|
||||||
|
assert_eq!(ModRange::new_empty().length(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mod_range_is_empty() {
|
||||||
|
assert!(!ModRange::new(200, 100).is_empty());
|
||||||
|
assert!(ModRange::new(200, 0).is_empty());
|
||||||
|
assert!(ModRange::new_empty().is_empty());
|
||||||
|
assert!(!ModRange::new(usize::MAX, 2).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mod_range_append() {
|
||||||
|
let range = ModRange::new(200, 100);
|
||||||
|
let new_range = range.append(ModRange::new(300, 400)).unwrap();
|
||||||
|
assert!(new_range.start() == 200);
|
||||||
|
assert!(new_range.length() == 500);
|
||||||
|
assert!(range.append(ModRange::new(299, 400)).is_none());
|
||||||
|
assert!(range.append(ModRange::new(301, 400)).is_none());
|
||||||
|
assert!(range.append(ModRange::new(200, 400)).is_none());
|
||||||
|
let empty_append = ModRange::new_empty()
|
||||||
|
.append(ModRange::new(200, 100))
|
||||||
|
.unwrap();
|
||||||
|
assert!(empty_append.start() == 200);
|
||||||
|
assert!(empty_append.length() == 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mod_range_contains_range() {
|
||||||
|
let range = ModRange::new(200, 100);
|
||||||
|
assert!(!range.contains_range(&ModRange::new(199, 100)));
|
||||||
|
assert!(!range.contains_range(&ModRange::new(201, 100)));
|
||||||
|
assert!(!range.contains_range(&ModRange::new(199, 99)));
|
||||||
|
assert!(!range.contains_range(&ModRange::new(202, 99)));
|
||||||
|
assert!(!range.contains_range(&ModRange::new(200, 101)));
|
||||||
|
assert!(range.contains_range(&ModRange::new(200, 100)));
|
||||||
|
assert!(range.contains_range(&ModRange::new(200, 99)));
|
||||||
|
assert!(range.contains_range(&ModRange::new(201, 99)));
|
||||||
|
assert!(ModRange::new_empty().contains_range(&ModRange::new_empty()));
|
||||||
|
assert!(ModRange::new(usize::MAX, 1).contains_range(&ModRange::new(usize::MAX, 1)));
|
||||||
|
assert!(ModRange::new(usize::MAX, 2).contains_range(&ModRange::new(usize::MAX, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mod_range_aligned_iter() {
|
||||||
|
let mut iter = ModRange::new(200, 100).aligned_iter(100);
|
||||||
|
assert_eq!(iter.next(), Some(200));
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
let mut iter = ModRange::new(200, 101).aligned_iter(100);
|
||||||
|
assert_eq!(iter.next(), Some(200));
|
||||||
|
assert_eq!(iter.next(), Some(300));
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
let mut iter = ModRange::new(199, 100).aligned_iter(100);
|
||||||
|
assert_eq!(iter.next(), Some(200));
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
let mut iter = ModRange::new(201, 99).aligned_iter(100);
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
let mut iter = ModRange::new(usize::MAX - 16, 20).aligned_iter(16);
|
||||||
|
assert_eq!(iter.next(), Some(0xf_fff_fff_fff_fff_ff0));
|
||||||
|
assert_eq!(iter.next(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019-2020 Google LLC
|
// Copyright 2019-2021 Google LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,27 +12,36 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod buffer_upgrade;
|
||||||
|
mod helper;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod syscall;
|
mod syscall;
|
||||||
|
mod upgrade_storage;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
pub use upgrade_storage::UpgradeStorage;
|
||||||
pub use self::syscall::SyscallStorage;
|
|
||||||
|
|
||||||
/// Storage definition for production.
|
/// Definitions for production.
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod prod {
|
mod prod {
|
||||||
pub type Storage = super::SyscallStorage;
|
use super::syscall::{SyscallStorage, SyscallUpgradeStorage};
|
||||||
|
|
||||||
|
pub type Storage = SyscallStorage;
|
||||||
|
|
||||||
pub fn new_storage(num_pages: usize) -> Storage {
|
pub fn new_storage(num_pages: usize) -> Storage {
|
||||||
Storage::new(num_pages).unwrap()
|
Storage::new(num_pages).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type UpgradeLocations = SyscallUpgradeStorage;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
pub use self::prod::{new_storage, Storage};
|
pub use self::prod::{new_storage, Storage, UpgradeLocations};
|
||||||
|
|
||||||
/// Storage definition for testing.
|
/// Definitions for testing.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::buffer_upgrade::BufferUpgradeStorage;
|
||||||
|
|
||||||
pub type Storage = persistent_store::BufferStorage;
|
pub type Storage = persistent_store::BufferStorage;
|
||||||
|
|
||||||
pub fn new_storage(num_pages: usize) -> Storage {
|
pub fn new_storage(num_pages: usize) -> Storage {
|
||||||
@@ -47,6 +56,8 @@ mod test {
|
|||||||
};
|
};
|
||||||
Storage::new(store, options)
|
Storage::new(store, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type UpgradeLocations = BufferUpgradeStorage;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use self::test::{new_storage, Storage};
|
pub use self::test::{new_storage, Storage, UpgradeLocations};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019-2020 Google LLC
|
// Copyright 2019-2021 Google LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use super::helper::{find_slice, is_aligned, ModRange};
|
||||||
|
use super::upgrade_storage::UpgradeStorage;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use libtock_core::syscalls;
|
use libtock_core::syscalls;
|
||||||
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
|
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
|
||||||
@@ -43,6 +45,8 @@ mod memop_nr {
|
|||||||
|
|
||||||
mod storage_type {
|
mod storage_type {
|
||||||
pub const STORE: usize = 1;
|
pub const STORE: usize = 1;
|
||||||
|
pub const PARTITION: usize = 2;
|
||||||
|
pub const METADATA: usize = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_info(nr: usize, arg: usize) -> StorageResult<usize> {
|
fn get_info(nr: usize, arg: usize) -> StorageResult<usize> {
|
||||||
@@ -59,6 +63,37 @@ fn memop(nr: u32, arg: usize) -> StorageResult<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_slice(ptr: usize, value: &[u8]) -> StorageResult<()> {
|
||||||
|
let code = unsafe {
|
||||||
|
syscalls::raw::allow(
|
||||||
|
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::CustomError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = syscalls::command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len());
|
||||||
|
if code.is_err() {
|
||||||
|
return Err(StorageError::CustomError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> {
|
||||||
|
let code = syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length);
|
||||||
|
if code.is_err() {
|
||||||
|
return Err(StorageError::CustomError);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SyscallStorage {
|
pub struct SyscallStorage {
|
||||||
word_size: usize,
|
word_size: usize,
|
||||||
page_size: usize,
|
page_size: usize,
|
||||||
@@ -118,11 +153,11 @@ impl SyscallStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_word_aligned(&self, x: usize) -> bool {
|
fn is_word_aligned(&self, x: usize) -> bool {
|
||||||
x & (self.word_size - 1) == 0
|
is_aligned(self.word_size, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_page_aligned(&self, x: usize) -> bool {
|
fn is_page_aligned(&self, x: usize) -> bool {
|
||||||
x & (self.page_size - 1) == 0
|
is_aligned(self.page_size, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,83 +192,136 @@ impl Storage for SyscallStorage {
|
|||||||
return Err(StorageError::NotAligned);
|
return Err(StorageError::NotAligned);
|
||||||
}
|
}
|
||||||
let ptr = self.read_slice(index, value.len())?.as_ptr() as usize;
|
let ptr = self.read_slice(index, value.len())?.as_ptr() as usize;
|
||||||
|
write_slice(ptr, value)
|
||||||
let code = unsafe {
|
|
||||||
syscalls::raw::allow(
|
|
||||||
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::CustomError);
|
|
||||||
}
|
|
||||||
|
|
||||||
let code = syscalls::command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len());
|
|
||||||
if code.is_err() {
|
|
||||||
return Err(StorageError::CustomError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
|
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
|
||||||
let index = StorageIndex { page, byte: 0 };
|
let index = StorageIndex { page, byte: 0 };
|
||||||
let length = self.page_size();
|
let length = self.page_size();
|
||||||
let ptr = self.read_slice(index, length)?.as_ptr() as usize;
|
let ptr = self.read_slice(index, length)?.as_ptr() as usize;
|
||||||
let code = syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, length);
|
erase_page(ptr, length)
|
||||||
if code.is_err() {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyscallUpgradeStorage {
|
||||||
|
page_size: usize,
|
||||||
|
partition: ModRange,
|
||||||
|
metadata: ModRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyscallUpgradeStorage {
|
||||||
|
/// Provides access to the other upgrade partition and metadata if available.
|
||||||
|
///
|
||||||
|
/// The implementation assumes that storage locations returned by the kernel through
|
||||||
|
/// `memop_nr::STORAGE_*` calls are in address space order.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `CustomError` if any of the following conditions do not hold:
|
||||||
|
/// - The page size is a power of two.
|
||||||
|
/// - The storage slices are page-aligned.
|
||||||
|
/// - There are not partition or metadata slices.
|
||||||
|
/// Returns a `NotAligned` error if partitions or metadata ranges are
|
||||||
|
/// - not exclusive or,
|
||||||
|
/// - not consecutive.
|
||||||
|
pub fn new() -> StorageResult<SyscallUpgradeStorage> {
|
||||||
|
let mut locations = SyscallUpgradeStorage {
|
||||||
|
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
||||||
|
partition: ModRange::new_empty(),
|
||||||
|
metadata: ModRange::new_empty(),
|
||||||
|
};
|
||||||
|
if !locations.page_size.is_power_of_two() {
|
||||||
return Err(StorageError::CustomError);
|
return Err(StorageError::CustomError);
|
||||||
}
|
}
|
||||||
Ok(())
|
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
|
||||||
|
let storage_type = memop(memop_nr::STORAGE_TYPE, i)?;
|
||||||
|
match storage_type {
|
||||||
|
storage_type::PARTITION | storage_type::METADATA => (),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
|
||||||
|
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
|
||||||
|
if !locations.is_page_aligned(storage_ptr) || !locations.is_page_aligned(storage_len) {
|
||||||
|
return Err(StorageError::CustomError);
|
||||||
|
}
|
||||||
|
let range = ModRange::new(storage_ptr, storage_len);
|
||||||
|
match storage_type {
|
||||||
|
storage_type::PARTITION => {
|
||||||
|
locations.partition = locations
|
||||||
|
.partition
|
||||||
|
.append(range)
|
||||||
|
.ok_or(StorageError::NotAligned)?
|
||||||
|
}
|
||||||
|
storage_type::METADATA => {
|
||||||
|
locations.metadata = locations
|
||||||
|
.metadata
|
||||||
|
.append(range)
|
||||||
|
.ok_or(StorageError::NotAligned)?
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if locations.partition.is_empty() || locations.metadata.is_empty() {
|
||||||
|
Err(StorageError::CustomError)
|
||||||
|
} else {
|
||||||
|
Ok(locations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_page_aligned(&self, x: usize) -> bool {
|
||||||
|
is_aligned(self.page_size, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_slice<'a>(
|
impl UpgradeStorage for SyscallUpgradeStorage {
|
||||||
slices: &'a [&'a [u8]],
|
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
|
||||||
mut start: usize,
|
let address = self.partition.start() + offset;
|
||||||
length: usize,
|
if self
|
||||||
) -> StorageResult<&'a [u8]> {
|
.partition
|
||||||
for slice in slices {
|
.contains_range(&ModRange::new(address, length))
|
||||||
if start >= slice.len() {
|
{
|
||||||
start -= slice.len();
|
Ok(unsafe { core::slice::from_raw_parts(address as *const u8, length) })
|
||||||
continue;
|
} else {
|
||||||
}
|
|
||||||
if start + length > slice.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return Ok(&slice[start..][..length]);
|
|
||||||
}
|
|
||||||
Err(StorageError::OutOfBounds)
|
Err(StorageError::OutOfBounds)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
|
||||||
mod tests {
|
let address = self.partition.start() + offset;
|
||||||
use super::*;
|
if self
|
||||||
|
.partition
|
||||||
|
.contains_range(&ModRange::new(address, data.len()))
|
||||||
|
{
|
||||||
|
// Erases all pages that have their first byte in the write range.
|
||||||
|
// Since we expect calls in order, we don't want to erase half-written pages.
|
||||||
|
for address in ModRange::new(address, data.len()).aligned_iter(self.page_size) {
|
||||||
|
erase_page(address, self.page_size)?;
|
||||||
|
}
|
||||||
|
write_slice(address, data)
|
||||||
|
} else {
|
||||||
|
Err(StorageError::OutOfBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
fn partition_length(&self) -> usize {
|
||||||
fn find_slice_ok() {
|
self.partition.length()
|
||||||
assert_eq!(
|
}
|
||||||
find_slice(&[&[1, 2, 3, 4]], 0, 4).ok(),
|
|
||||||
Some(&[1u8, 2, 3, 4] as &[u8])
|
fn read_metadata(&self) -> StorageResult<&[u8]> {
|
||||||
);
|
Ok(unsafe {
|
||||||
assert_eq!(
|
core::slice::from_raw_parts(self.metadata.start() as *const u8, self.metadata.length())
|
||||||
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 1, 2).ok(),
|
})
|
||||||
Some(&[2u8, 3] as &[u8])
|
}
|
||||||
);
|
|
||||||
assert_eq!(
|
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
|
||||||
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 2).ok(),
|
// If less data is passed in than is reserved, assume the rest is 0xFF.
|
||||||
Some(&[5u8, 6] as &[u8])
|
if data.len() <= self.metadata.length() {
|
||||||
);
|
for address in self.metadata.aligned_iter(self.page_size) {
|
||||||
assert_eq!(
|
erase_page(address, self.page_size)?;
|
||||||
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 0).ok(),
|
}
|
||||||
Some(&[] as &[u8])
|
write_slice(self.metadata.start(), data)
|
||||||
);
|
} else {
|
||||||
assert!(find_slice(&[], 0, 1).is_err());
|
Err(StorageError::OutOfBounds)
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/embedded_flash/upgrade_storage.rs
Normal file
51
src/embedded_flash/upgrade_storage.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2021 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 persistent_store::StorageResult;
|
||||||
|
|
||||||
|
/// Accessors to storage locations used for upgrading from a CTAP command.
|
||||||
|
pub trait UpgradeStorage {
|
||||||
|
/// Reads a slice of the partition, if within bounds.
|
||||||
|
///
|
||||||
|
/// The offset is relative to the start of the partition.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`StorageError::OutOfBounds`] if the requested slice is not inside the partition.
|
||||||
|
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]>;
|
||||||
|
|
||||||
|
/// Writes the given data to the given offset address, if within bounds of the partition.
|
||||||
|
///
|
||||||
|
/// The offset is relative to the start of the partition.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`StorageError::OutOfBounds`] if the data does not fit the partition.
|
||||||
|
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()>;
|
||||||
|
|
||||||
|
/// Returns the length of the partition.
|
||||||
|
fn partition_length(&self) -> usize;
|
||||||
|
|
||||||
|
/// Reads the metadata location.
|
||||||
|
fn read_metadata(&self) -> StorageResult<&[u8]>;
|
||||||
|
|
||||||
|
/// Writes the given data into the metadata location.
|
||||||
|
///
|
||||||
|
/// The passed in data is appended with 0xFF bytes if shorter than the metadata storage.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`StorageError::OutOfBounds`] if the data is too long to fit the metadata storage.
|
||||||
|
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user