From 2d5fdd1034467e93b9e8c5e7f253eacb30feb353 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Sun, 15 Aug 2021 22:39:13 +0200 Subject: [PATCH] Configure the number of pages in the board This permits to have portable apps. --- .../nordic/nrf52840_dongle_opensk/src/main.rs | 3 +- boards/nordic/nrf52840_mdk_dfu/src/main.rs | 3 +- boards/nordic/nrf52840dk_opensk/src/main.rs | 3 +- deploy.py | 8 +-- examples/erase_storage.rs | 8 +-- examples/store_latency.rs | 62 +++++++++++-------- patches/tock/01-persistent-storage.patch | 15 ++--- patches/tock/06-upgrade-partitions.patch | 5 +- src/ctap/customization.rs | 17 +---- src/ctap/storage.rs | 3 +- src/embedded_flash/mod.rs | 12 ++-- src/embedded_flash/syscall.rs | 17 ++--- 12 files changed, 77 insertions(+), 79 deletions(-) diff --git a/boards/nordic/nrf52840_dongle_opensk/src/main.rs b/boards/nordic/nrf52840_dongle_opensk/src/main.rs index a10207c0..d4309ebf 100644 --- a/boards/nordic/nrf52840_dongle_opensk/src/main.rs +++ b/boards/nordic/nrf52840_dongle_opensk/src/main.rs @@ -66,7 +66,8 @@ static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROC static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 1] = [kernel::StorageLocation { address: 0xC0000, - size: 0x40000, + size: 0x14000, // NUM_PAGES = 20 + unallocated_size: 0x40000, // MPU limitation storage_type: kernel::StorageType::STORE, }]; diff --git a/boards/nordic/nrf52840_mdk_dfu/src/main.rs b/boards/nordic/nrf52840_mdk_dfu/src/main.rs index a92a82d1..e57c1fc7 100644 --- a/boards/nordic/nrf52840_mdk_dfu/src/main.rs +++ b/boards/nordic/nrf52840_mdk_dfu/src/main.rs @@ -60,7 +60,8 @@ static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROC static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 1] = [kernel::StorageLocation { address: 0xC0000, - size: 0x40000, + size: 0x14000, // NUM_PAGES = 20 + unallocated_size: 0x40000, // MPU limitation storage_type: kernel::StorageType::STORE, }]; diff --git a/boards/nordic/nrf52840dk_opensk/src/main.rs b/boards/nordic/nrf52840dk_opensk/src/main.rs index 4503ebd0..176c55a6 100644 --- a/boards/nordic/nrf52840dk_opensk/src/main.rs +++ b/boards/nordic/nrf52840dk_opensk/src/main.rs @@ -130,7 +130,8 @@ static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROC static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 1] = [kernel::StorageLocation { address: 0xC0000, - size: 0x40000, + size: 0x14000, // NUM_PAGES = 20 + unallocated_size: 0x40000, // MPU limitation storage_type: kernel::StorageType::STORE, }]; diff --git a/deploy.py b/deploy.py index dc8b267c..3d2c2f16 100755 --- a/deploy.py +++ b/deploy.py @@ -94,7 +94,7 @@ app_ldscript="nrf52840_layout.ld", app_address=0x40000, storage_address=0xC0000, - storage_size=0x40000, + storage_size=0x14000, pyocd_target="nrf52840", openocd_board="nordic_nrf52840_dongle.cfg", openocd_options=[], @@ -113,7 +113,7 @@ app_ldscript="nrf52840_layout.ld", app_address=0x40000, storage_address=0xC0000, - storage_size=0x40000, + storage_size=0x14000, pyocd_target="nrf52840", openocd_board="nordic_nrf52840_dongle.cfg", openocd_options=[], @@ -132,7 +132,7 @@ app_ldscript="nrf52840_layout.ld", app_address=0x40000, storage_address=0xC0000, - storage_size=0x40000, + storage_size=0x14000, pyocd_target="nrf52840", openocd_board="nordic_nrf52840_dongle.cfg", openocd_options=[], @@ -151,7 +151,7 @@ app_ldscript="nrf52840_layout.ld", app_address=0x40000, storage_address=0xC0000, - storage_size=0x40000, + storage_size=0x14000, pyocd_target="nrf52840", openocd_board="nordic_nrf52840_dongle.cfg", openocd_options=[], diff --git a/examples/erase_storage.rs b/examples/erase_storage.rs index 6d102db8..da0e445b 100644 --- a/examples/erase_storage.rs +++ b/examples/erase_storage.rs @@ -37,10 +37,10 @@ fn is_page_erased(storage: &dyn Storage, page: usize) -> bool { fn main() { led::get(1).flex_unwrap().on().flex_unwrap(); // red on dongle - const NUM_PAGES: usize = 20; // should be at least ctap::storage::NUM_PAGES - let mut storage = new_storage(NUM_PAGES); - writeln!(Console::new(), "Erase {} pages of storage:", NUM_PAGES).unwrap(); - for page in 0..NUM_PAGES { + let mut storage = new_storage().unwrap(); + let num_pages = storage.num_pages(); + writeln!(Console::new(), "Erase {} pages of storage:", num_pages).unwrap(); + for page in 0..num_pages { write!(Console::new(), "- Page {} ", page).unwrap(); if is_page_erased(&storage, page) { writeln!(Console::new(), "skipped (was already erased).").unwrap(); diff --git a/examples/store_latency.rs b/examples/store_latency.rs index 3571cf4d..a1134004 100644 --- a/examples/store_latency.rs +++ b/examples/store_latency.rs @@ -26,7 +26,7 @@ use libtock_drivers::console::Console; use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; use persistent_store::Store; -libtock_core::stack_size! {0x800} +libtock_core::stack_size! {0x2000} fn timestamp(timer: &Timer) -> Timestamp { Timestamp::::from_clock_value(timer.get_current_clock().ok().unwrap()) @@ -40,20 +40,35 @@ fn measure(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration } // Only use one store at a time. -unsafe fn boot_store(num_pages: usize, erase: bool) -> Store { - let mut storage = new_storage(num_pages); +unsafe fn boot_store(erase: bool) -> Store { + use persistent_store::Storage; + let mut storage = new_storage().unwrap(); + let num_pages = storage.num_pages(); if erase { for page in 0..num_pages { - use persistent_store::Storage; storage.erase_page(page).unwrap(); } } Store::new(storage).ok().unwrap() } +#[derive(Debug)] +struct StorageConfig { + page_size: usize, + num_pages: usize, +} + +fn storage_config() -> StorageConfig { + use persistent_store::Storage; + let storage = new_storage().unwrap(); + StorageConfig { + page_size: storage.page_size(), + num_pages: storage.num_pages(), + } +} + #[derive(Default)] struct Stat { - num_pages: usize, key_increment: usize, entry_length: usize, // words boot_ms: f64, @@ -69,7 +84,6 @@ fn compute_latency( word_length: usize, ) -> Stat { let mut stat = Stat { - num_pages, key_increment, entry_length: word_length, ..Default::default() @@ -78,12 +92,12 @@ fn compute_latency( let mut console = Console::new(); writeln!( console, - "\nLatency for num_pages={} key_increment={} word_length={}.", - num_pages, key_increment, word_length + "\nLatency for key_increment={} word_length={}.", + key_increment, word_length ) .unwrap(); - let mut store = unsafe { boot_store(num_pages, true) }; + let mut store = unsafe { boot_store(true) }; let total_capacity = store.capacity().unwrap().total(); assert_eq!(store.capacity().unwrap().used(), 0); assert_eq!(store.lifetime().unwrap().used(), 0); @@ -121,7 +135,7 @@ fn compute_latency( ); // Measure latency of boot. - let (mut store, time) = measure(&timer, || unsafe { boot_store(num_pages, false) }); + let (mut store, time) = measure(&timer, || unsafe { boot_store(false) }); writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap(); stat.boot_ms = time.ms(); @@ -150,19 +164,17 @@ fn compute_latency( fn main() { let mut with_callback = timer::with_callback(|_, _| {}); let timer = with_callback.init().ok().unwrap(); + let config = storage_config(); let mut stats = Vec::new(); - writeln!(Console::new(), "\nRunning 4 tests...").unwrap(); - // Those non-overwritten 50 words entries simulate credentials. - stats.push(compute_latency(&timer, 3, 1, 50)); - stats.push(compute_latency(&timer, 20, 1, 50)); - // Those overwritten 1 word entries simulate counters. - stats.push(compute_latency(&timer, 3, 0, 1)); - stats.push(compute_latency(&timer, 20, 0, 1)); + writeln!(Console::new(), "\nRunning 2 tests...").unwrap(); + // Simulate a store full of credentials (of 50 words). + stats.push(compute_latency(&timer, config.num_pages, 1, 50)); + // Simulate a store full of increments of a single counter. + stats.push(compute_latency(&timer, config.num_pages, 0, 1)); writeln!(Console::new(), "\nDone.\n").unwrap(); const HEADERS: &[&str] = &[ - "Pages", "Overwrite", "Length", "Boot", @@ -173,7 +185,6 @@ fn main() { let mut matrix = vec![HEADERS.iter().map(|x| x.to_string()).collect()]; for stat in stats { matrix.push(vec![ - format!("{}", stat.num_pages), if stat.key_increment == 0 { "yes" } else { "no" }.to_string(), format!("{} words", stat.entry_length), format!("{:.1} ms", stat.boot_ms), @@ -182,14 +193,15 @@ fn main() { format!("{:.1} ms", stat.remove_ms), ]); } + writeln!(Console::new(), "Copy to examples/store_latency.rs:\n").unwrap(); + writeln!(Console::new(), "{:?}", config).unwrap(); write_matrix(matrix); - // Results on nrf52840dk_opensk: - // Pages Overwrite Length Boot Compaction Insert Remove - // 3 no 50 words 5.3 ms 141.9 ms 8.0 ms 3.3 ms - // 20 no 50 words 18.7 ms 148.6 ms 21.0 ms 9.8 ms - // 3 yes 1 words 37.8 ms 100.2 ms 11.3 ms 5.5 ms - // 20 yes 1 words 336.5 ms 100.3 ms 11.5 ms 5.6 ms + // Results for nrf52840dk_opensk: + // StorageConfig { page_size: 4096, num_pages: 20 } + // Overwrite Length Boot Compaction Insert Remove + // no 50 words 16.2 ms 143.8 ms 18.3 ms 8.4 ms + // yes 1 words 303.8 ms 97.9 ms 9.7 ms 4.7 ms } fn align(x: &str, n: usize) { diff --git a/patches/tock/01-persistent-storage.patch b/patches/tock/01-persistent-storage.patch index aed75810..cbaf32a5 100644 --- a/patches/tock/01-persistent-storage.patch +++ b/patches/tock/01-persistent-storage.patch @@ -349,7 +349,7 @@ index 348c746a5..5465c95f4 100644 } } diff --git a/kernel/src/process.rs b/kernel/src/process.rs -index c52754be3..ae6a58341 100644 +index c52754be3..26a7c47d3 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -359,6 +359,15 @@ pub trait ProcessType { @@ -415,7 +415,7 @@ index c52754be3..ae6a58341 100644 + .mpu() + .allocate_region( + storage_location.address as *const u8, -+ storage_location.size, ++ storage_location.unallocated_size, + storage_location.size, + mpu::Permissions::ReadOnly, + &mut mpu_config, @@ -439,10 +439,10 @@ index c52754be3..ae6a58341 100644 // memory space just for kernel and grant state. We need to make // sure we allocate enough memory just for that. diff --git a/kernel/src/sched.rs b/kernel/src/sched.rs -index 10626a2e1..8844bc6c3 100644 +index 10626a2e1..61401b04a 100644 --- a/kernel/src/sched.rs +++ b/kernel/src/sched.rs -@@ -118,6 +118,12 @@ pub enum SchedulingDecision { +@@ -118,6 +118,13 @@ pub enum SchedulingDecision { TrySleep, } @@ -450,12 +450,13 @@ index 10626a2e1..8844bc6c3 100644 +pub struct StorageLocation { + pub address: usize, + pub size: usize, ++ pub unallocated_size: usize, +} + /// Main object for the kernel. Each board will need to create one. pub struct Kernel { /// How many "to-do" items exist at any given time. These include -@@ -127,6 +133,9 @@ pub struct Kernel { +@@ -127,6 +134,9 @@ pub struct Kernel { /// This holds a pointer to the static array of Process pointers. processes: &'static [Option<&'static dyn process::ProcessType>], @@ -465,7 +466,7 @@ index 10626a2e1..8844bc6c3 100644 /// A counter which keeps track of how many process identifiers have been /// created. This is used to create new unique identifiers for processes. process_identifier_max: Cell, -@@ -170,9 +179,17 @@ pub enum StoppedExecutingReason { +@@ -170,9 +180,17 @@ pub enum StoppedExecutingReason { impl Kernel { pub fn new(processes: &'static [Option<&'static dyn process::ProcessType>]) -> Kernel { @@ -483,7 +484,7 @@ index 10626a2e1..8844bc6c3 100644 process_identifier_max: Cell::new(0), grant_counter: Cell::new(0), grants_finalized: Cell::new(false), -@@ -900,4 +917,8 @@ impl Kernel { +@@ -900,4 +918,8 @@ impl Kernel { (return_reason, time_executed_us) } diff --git a/patches/tock/06-upgrade-partitions.patch b/patches/tock/06-upgrade-partitions.patch index 38defec1..93b23c6e 100644 --- a/patches/tock/06-upgrade-partitions.patch +++ b/patches/tock/06-upgrade-partitions.patch @@ -31,10 +31,10 @@ index 5465c95f4..e596648f7 100644 } } diff --git a/kernel/src/sched.rs b/kernel/src/sched.rs -index 031159500..0cbfea929 100644 +index 61401b04a..e9a58c018 100644 --- a/kernel/src/sched.rs +++ b/kernel/src/sched.rs -@@ -118,10 +118,19 @@ pub enum SchedulingDecision { +@@ -118,11 +118,20 @@ pub enum SchedulingDecision { TrySleep, } @@ -50,6 +50,7 @@ index 031159500..0cbfea929 100644 pub struct StorageLocation { pub address: usize, pub size: usize, + pub unallocated_size: usize, + pub storage_type: StorageType, } diff --git a/src/ctap/customization.rs b/src/ctap/customization.rs index 7c28a2fd..6314728a 100644 --- a/src/ctap/customization.rs +++ b/src/ctap/customization.rs @@ -219,23 +219,10 @@ pub const MAX_RP_IDS_LENGTH: usize = 8; /// /// - The storage key CREDENTIALS must fit at least this number of credentials. /// -/// This value has implications on the flash lifetime, please see the -/// documentation for NUM_PAGES below. -pub const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150; - -/// Sets the number of pages used for persistent storage. -/// -/// The number of pages should be at least 3 and at most what the flash can -/// hold. There should be no reason to put a small number here, except that the -/// latency of flash operations is linear in the number of pages. This may -/// improve in the future. Currently, using 20 pages gives between 20ms and -/// 240ms per operation. The rule of thumb is between 1ms and 12ms per -/// additional page. -/// /// Limiting the number of resident keys permits to ensure a minimum number of /// counter increments. /// Let: -/// - P the number of pages (NUM_PAGES) +/// - P the number of pages (NUM_PAGES in the board definition) /// - K the maximum number of resident keys (MAX_SUPPORTED_RESIDENT_KEYS) /// - S the maximum size of a resident key (about 500) /// - C the number of erase cycles (10000) @@ -245,7 +232,7 @@ pub const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150; /// /// With P=20 and K=150, we have I=2M which is enough for 500 increments per day /// for 10 years. -pub const NUM_PAGES: usize = 20; +pub const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150; #[cfg(test)] mod test { diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 9e1127eb..2ee03569 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -18,7 +18,6 @@ use crate::ctap::client_pin::PIN_AUTH_LENGTH; use crate::ctap::customization::{ DEFAULT_MIN_PIN_LENGTH, DEFAULT_MIN_PIN_LENGTH_RP_IDS, ENFORCE_ALWAYS_UV, MAX_LARGE_BLOB_ARRAY_SIZE, MAX_PIN_RETRIES, MAX_RP_IDS_LENGTH, MAX_SUPPORTED_RESIDENT_KEYS, - NUM_PAGES, }; use crate::ctap::data_formats::{ extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource, @@ -68,7 +67,7 @@ impl PersistentStore { /// /// This should be at most one instance of persistent store per program lifetime. pub fn new(rng: &mut impl Rng256) -> PersistentStore { - let storage = new_storage(NUM_PAGES); + let storage = new_storage().ok().unwrap(); let mut store = PersistentStore { store: persistent_store::Store::new(storage).ok().unwrap(), }; diff --git a/src/embedded_flash/mod.rs b/src/embedded_flash/mod.rs index 5744b299..c34049a9 100644 --- a/src/embedded_flash/mod.rs +++ b/src/embedded_flash/mod.rs @@ -28,8 +28,8 @@ mod prod { pub type Storage = SyscallStorage; - pub fn new_storage(num_pages: usize) -> Storage { - Storage::new(num_pages).unwrap() + pub fn new_storage() -> persistent_store::StorageResult { + Storage::new() } pub type UpgradeLocations = SyscallUpgradeStorage; @@ -44,9 +44,11 @@ mod test { pub type Storage = persistent_store::BufferStorage; - pub fn new_storage(num_pages: usize) -> Storage { + pub fn new_storage() -> persistent_store::StorageResult { + // Use the Nordic configuration. const PAGE_SIZE: usize = 0x1000; - let store = vec![0xff; num_pages * PAGE_SIZE].into_boxed_slice(); + const NUM_PAGES: usize = 20; + let store = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); let options = persistent_store::BufferOptions { word_size: 4, page_size: PAGE_SIZE, @@ -54,7 +56,7 @@ mod test { max_page_erases: 10000, strict_mode: true, }; - Storage::new(store, options) + Ok(Storage::new(store, options)) } pub type UpgradeLocations = BufferUpgradeStorage; diff --git a/src/embedded_flash/syscall.rs b/src/embedded_flash/syscall.rs index eac09479..bec385c6 100644 --- a/src/embedded_flash/syscall.rs +++ b/src/embedded_flash/syscall.rs @@ -134,13 +134,11 @@ impl SyscallStorage { /// - 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 { + pub fn new() -> 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, + num_pages: 0, 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(), @@ -156,20 +154,15 @@ impl SyscallStorage { continue; } 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) { + let storage_len = memop(memop_nr::STORAGE_LEN, i)?; + if !syscall.is_page_aligned(storage_ptr) || !syscall.is_page_aligned(storage_len) { return Err(StorageError::CustomError); } - let storage_len = core::cmp::min(num_pages * syscall.page_size, max_storage_len); - num_pages -= storage_len / syscall.page_size; + syscall.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) }