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:
kaczmarczyck
2021-07-20 10:37:57 +02:00
committed by GitHub
parent 146b54e9d0
commit a532959e8f
5 changed files with 541 additions and 77 deletions

View 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)
}
}
}

View 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);
}
}

View File

@@ -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};

View File

@@ -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 => (),
fn find_slice<'a>( _ => continue,
slices: &'a [&'a [u8]], };
mut start: usize, let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
length: usize, let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
) -> StorageResult<&'a [u8]> { if !locations.is_page_aligned(storage_ptr) || !locations.is_page_aligned(storage_len) {
for slice in slices { return Err(StorageError::CustomError);
if start >= slice.len() { }
start -= slice.len(); let range = ModRange::new(storage_ptr, storage_len);
continue; 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() { if locations.partition.is_empty() || locations.metadata.is_empty() {
break; Err(StorageError::CustomError)
} else {
Ok(locations)
} }
return Ok(&slice[start..][..length]);
} }
Err(StorageError::OutOfBounds)
}
#[cfg(test)] fn is_page_aligned(&self, x: usize) -> bool {
mod tests { is_aligned(self.page_size, x)
use super::*; }
}
#[test]
fn find_slice_ok() { impl UpgradeStorage for SyscallUpgradeStorage {
assert_eq!( fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
find_slice(&[&[1, 2, 3, 4]], 0, 4).ok(), let address = self.partition.start() + offset;
Some(&[1u8, 2, 3, 4] as &[u8]) if self
); .partition
assert_eq!( .contains_range(&ModRange::new(address, length))
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 1, 2).ok(), {
Some(&[2u8, 3] as &[u8]) Ok(unsafe { core::slice::from_raw_parts(address as *const u8, length) })
); } else {
assert_eq!( Err(StorageError::OutOfBounds)
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 2).ok(), }
Some(&[5u8, 6] as &[u8]) }
);
assert_eq!( fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 0).ok(), let address = self.partition.start() + offset;
Some(&[] as &[u8]) if self
); .partition
assert!(find_slice(&[], 0, 1).is_err()); .contains_range(&ModRange::new(address, data.len()))
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()); // 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)
}
} }
} }

View 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<()>;
}