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");
|
||||
// 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
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod buffer_upgrade;
|
||||
mod helper;
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod syscall;
|
||||
mod upgrade_storage;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use self::syscall::SyscallStorage;
|
||||
pub use upgrade_storage::UpgradeStorage;
|
||||
|
||||
/// Storage definition for production.
|
||||
/// Definitions for production.
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod prod {
|
||||
pub type Storage = super::SyscallStorage;
|
||||
use super::syscall::{SyscallStorage, SyscallUpgradeStorage};
|
||||
|
||||
pub type Storage = SyscallStorage;
|
||||
|
||||
pub fn new_storage(num_pages: usize) -> Storage {
|
||||
Storage::new(num_pages).unwrap()
|
||||
}
|
||||
|
||||
pub type UpgradeLocations = SyscallUpgradeStorage;
|
||||
}
|
||||
#[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")]
|
||||
mod test {
|
||||
use super::buffer_upgrade::BufferUpgradeStorage;
|
||||
|
||||
pub type Storage = persistent_store::BufferStorage;
|
||||
|
||||
pub fn new_storage(num_pages: usize) -> Storage {
|
||||
@@ -47,6 +56,8 @@ mod test {
|
||||
};
|
||||
Storage::new(store, options)
|
||||
}
|
||||
|
||||
pub type UpgradeLocations = BufferUpgradeStorage;
|
||||
}
|
||||
#[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");
|
||||
// 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
|
||||
// limitations under the License.
|
||||
|
||||
use super::helper::{find_slice, is_aligned, ModRange};
|
||||
use super::upgrade_storage::UpgradeStorage;
|
||||
use alloc::vec::Vec;
|
||||
use libtock_core::syscalls;
|
||||
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
|
||||
@@ -43,6 +45,8 @@ mod memop_nr {
|
||||
|
||||
mod storage_type {
|
||||
pub const STORE: usize = 1;
|
||||
pub const PARTITION: usize = 2;
|
||||
pub const METADATA: usize = 3;
|
||||
}
|
||||
|
||||
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 {
|
||||
word_size: usize,
|
||||
page_size: usize,
|
||||
@@ -118,11 +153,11 @@ impl SyscallStorage {
|
||||
}
|
||||
|
||||
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 {
|
||||
x & (self.page_size - 1) == 0
|
||||
is_aligned(self.page_size, x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,83 +192,136 @@ impl Storage for SyscallStorage {
|
||||
return Err(StorageError::NotAligned);
|
||||
}
|
||||
let ptr = self.read_slice(index, value.len())?.as_ptr() as usize;
|
||||
|
||||
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(())
|
||||
write_slice(ptr, value)
|
||||
}
|
||||
|
||||
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
|
||||
let index = StorageIndex { page, byte: 0 };
|
||||
let length = self.page_size();
|
||||
let ptr = self.read_slice(index, length)?.as_ptr() as usize;
|
||||
let code = syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, length);
|
||||
if code.is_err() {
|
||||
erase_page(ptr, length)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
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 start + length > slice.len() {
|
||||
break;
|
||||
if locations.partition.is_empty() || locations.metadata.is_empty() {
|
||||
Err(StorageError::CustomError)
|
||||
} else {
|
||||
Ok(locations)
|
||||
}
|
||||
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());
|
||||
fn is_page_aligned(&self, x: usize) -> bool {
|
||||
is_aligned(self.page_size, x)
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeStorage for SyscallUpgradeStorage {
|
||||
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
|
||||
let address = self.partition.start() + offset;
|
||||
if self
|
||||
.partition
|
||||
.contains_range(&ModRange::new(address, length))
|
||||
{
|
||||
Ok(unsafe { core::slice::from_raw_parts(address as *const u8, length) })
|
||||
} else {
|
||||
Err(StorageError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
|
||||
let address = self.partition.start() + offset;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn partition_length(&self) -> usize {
|
||||
self.partition.length()
|
||||
}
|
||||
|
||||
fn read_metadata(&self) -> StorageResult<&[u8]> {
|
||||
Ok(unsafe {
|
||||
core::slice::from_raw_parts(self.metadata.start() as *const u8, self.metadata.length())
|
||||
})
|
||||
}
|
||||
|
||||
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
|
||||
// If less data is passed in than is reserved, assume the rest is 0xFF.
|
||||
if data.len() <= self.metadata.length() {
|
||||
for address in self.metadata.aligned_iter(self.page_size) {
|
||||
erase_page(address, self.page_size)?;
|
||||
}
|
||||
write_slice(self.metadata.start(), data)
|
||||
} else {
|
||||
Err(StorageError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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