diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a03c46..ed764fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ (DD-MM-YY) -## Unreleased +## 0.8.1 07-02-24 + +- Added new PagePointerCache that caches more than the PageStateCache. See the readme for more details. ## 0.8.0 05-12-24 diff --git a/Cargo.toml b/Cargo.toml index 0bd421a..20838e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sequential-storage" -version = "0.8.0" +version = "0.8.1" edition = "2021" license = "MIT OR Apache-2.0" description = "A crate for storing data in flash with minimal erase cycles." diff --git a/README.md b/README.md index 1fdac2e..5e7defc 100644 --- a/README.md +++ b/README.md @@ -73,16 +73,20 @@ Instead, we can optionally store some state in ram. These numbers are taken from the test cases in the cache module: -| Name | Map # flash reads | Map flash bytes read | Queue # flash reads | Queue flash bytes read | -| -------------: | ----------------: | -------------------: | ------------------: | ---------------------: | -| NoCache | 100% | 100% | 100% | 100% | -| PageStateCache | 77% | 97% | 51% | 90% | +| Name | RAM bytes | Map # flash reads | Map flash bytes read | Queue # flash reads | Queue flash bytes read | +| ---------------: | ------------: | ----------------: | -------------------: | ------------------: | ---------------------: | +| NoCache | 0 | 100% | 100% | 100% | 100% | +| PageStateCache | 1 * num pages | 77% | 97% | 51% | 90% | +| PagePointerCache | 9 * num pages | 69% | 89% | 35% | 61% | #### Takeaways - PageStateCache - Mostly tackles number of reads - Very cheap in RAM, so easy win +- PagePointerCache + - Very efficient for the queue + - Minimum cache level that makes a dent in the map ## Inner workings diff --git a/fuzz/fuzz_targets/map.rs b/fuzz/fuzz_targets/map.rs index 1d5d957..e7c9386 100644 --- a/fuzz/fuzz_targets/map.rs +++ b/fuzz/fuzz_targets/map.rs @@ -5,18 +5,28 @@ use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use rand::SeedableRng; use sequential_storage::{ + cache::{CacheImpl, NoCache, PagePointerCache, PageStateCache}, map::{MapError, StorageItem}, mock_flash::{MockFlashBase, MockFlashError, WriteCountCheck}, }; use std::{collections::HashMap, ops::Range}; -fuzz_target!(|data: Input| fuzz(data)); +const PAGES: usize = 4; +const WORD_SIZE: usize = 4; +const WORDS_PER_PAGE: usize = 256; + +fuzz_target!(|data: Input| match data.cache_type { + CacheType::NoCache => fuzz(data, NoCache::new()), + CacheType::PageStateCache => fuzz(data, PageStateCache::::new()), + CacheType::PagePointerCache => fuzz(data, PagePointerCache::::new()), +}); #[derive(Arbitrary, Debug, Clone)] struct Input { seed: u64, fuel: u16, ops: Vec, + cache_type: CacheType, } #[derive(Arbitrary, Debug, Clone)] @@ -86,11 +96,14 @@ impl StorageItem for TestItem { } } -fn fuzz(ops: Input) { - const PAGES: usize = 4; - const WORD_SIZE: usize = 4; - const WORDS_PER_PAGE: usize = 256; +#[derive(Arbitrary, Debug, Clone)] +enum CacheType { + NoCache, + PageStateCache, + PagePointerCache, +} +fn fuzz(ops: Input, mut cache: impl CacheImpl) { let mut flash = MockFlashBase::::new( WriteCountCheck::OnceOnly, Some(ops.fuel as u32), @@ -98,8 +111,6 @@ fn fuzz(ops: Input) { ); const FLASH_RANGE: Range = 0x000..0x1000; - let mut cache = sequential_storage::cache::NoCache::new(); - let mut map = HashMap::new(); #[repr(align(4))] struct AlignedBuf([u8; 260]); diff --git a/fuzz/fuzz_targets/queue.rs b/fuzz/fuzz_targets/queue.rs index fe02bba..e916b99 100644 --- a/fuzz/fuzz_targets/queue.rs +++ b/fuzz/fuzz_targets/queue.rs @@ -5,19 +5,29 @@ use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use rand::{Rng, SeedableRng}; use sequential_storage::{ + cache::{CacheImpl, NoCache, PagePointerCache, PageStateCache}, mock_flash::{MockFlashBase, MockFlashError, WriteCountCheck}, Error, }; use std::{collections::VecDeque, ops::Range}; const MAX_VALUE_SIZE: usize = u8::MAX as usize; -fuzz_target!(|data: Input| fuzz(data)); +const PAGES: usize = 4; +const WORD_SIZE: usize = 4; +const WORDS_PER_PAGE: usize = 256; + +fuzz_target!(|data: Input| match data.cache_type { + CacheType::NoCache => fuzz(data, NoCache::new()), + CacheType::PageStateCache => fuzz(data, PageStateCache::::new()), + CacheType::PagePointerCache => fuzz(data, PagePointerCache::::new()), +}); #[derive(Arbitrary, Debug, Clone)] struct Input { seed: u64, fuel: u16, ops: Vec, + cache_type: CacheType, } #[derive(Arbitrary, Debug, Clone)] @@ -34,14 +44,17 @@ struct PushOp { value_len: u8, } +#[derive(Arbitrary, Debug, Clone)] +enum CacheType { + NoCache, + PageStateCache, + PagePointerCache, +} + #[repr(align(4))] struct AlignedBuf([u8; MAX_VALUE_SIZE + 1]); -fn fuzz(ops: Input) { - const PAGES: usize = 4; - const WORD_SIZE: usize = 4; - const WORDS_PER_PAGE: usize = 256; - +fn fuzz(ops: Input, mut cache: impl CacheImpl) { let mut flash = MockFlashBase::::new( WriteCountCheck::Twice, Some(ops.fuel as u32), @@ -49,8 +62,6 @@ fn fuzz(ops: Input) { ); const FLASH_RANGE: Range = 0x000..0x1000; - let mut cache = sequential_storage::cache::NoCache::new(); - let mut order = VecDeque::new(); let mut buf = AlignedBuf([0; MAX_VALUE_SIZE + 1]); @@ -67,7 +78,9 @@ fn fuzz(ops: Input) { #[cfg(fuzzing_repro)] eprintln!("{}", flash.print_items()); #[cfg(fuzzing_repro)] - eprintln!("=== OP: {op:?} ==="); + eprintln!("{:?}", cache); + #[cfg(fuzzing_repro)] + eprintln!("\n=== OP: {op:?} ===\n"); match &mut op { Op::Push(op) => { diff --git a/src/cache.rs b/src/cache.rs index e65140a..411a598 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,33 +1,42 @@ //! Module implementing all things cache related -use crate::PageState; +use core::{fmt::Debug, num::NonZeroU32, ops::Range}; + +use embedded_storage_async::nor_flash::NorFlash; + +use crate::{ + calculate_page_address, calculate_page_index, item::ItemHeader, NorFlashExt, PageState, +}; /// Trait implemented by all cache types #[allow(private_bounds)] -pub trait CacheImpl: PrivateCacheImpl {} +pub trait CacheImpl: PrivateCacheImpl + Debug {} impl CacheImpl for &mut T {} -impl CacheImpl for Cache {} +impl CacheImpl for Cache {} pub(crate) trait PrivateCacheImpl { type PSC: PageStatesCache; + type PPC: PagePointersCache; - fn inner(&mut self) -> &mut Cache; + fn inner(&mut self) -> &mut Cache; } impl PrivateCacheImpl for &mut T { type PSC = T::PSC; + type PPC = T::PPC; - fn inner(&mut self) -> &mut Cache { + fn inner(&mut self) -> &mut Cache { T::inner(self) } } -impl PrivateCacheImpl for Cache { +impl PrivateCacheImpl for Cache { type PSC = PSC; + type PPC = PPC; - fn inner(&mut self) -> &mut Cache { + fn inner(&mut self) -> &mut Cache { self } } @@ -36,19 +45,21 @@ impl PrivateCacheImpl for Cache { /// /// This type of cache doesn't have to be kept around and may be constructed on every api call. /// You could simply pass `&mut NoCache::new()` every time. -pub struct NoCache(Cache); +#[derive(Debug)] +pub struct NoCache(Cache); impl NoCache { /// Construct a new instance pub const fn new() -> Self { - Self(Cache::new(UncachedPageSates)) + Self(Cache::new(UncachedPageSates, UncachedPagePointers)) } } impl PrivateCacheImpl for NoCache { type PSC = UncachedPageSates; + type PPC = UncachedPagePointers; - fn inner(&mut self) -> &mut Cache { + fn inner(&mut self) -> &mut Cache { &mut self.0 } } @@ -66,77 +77,200 @@ impl CacheImpl for NoCache {} /// `Create cache 1` -> `use 1` -> `create cache 2` -> `use 2` -> `❌ use 1 ❌` /// /// Make sure the page count is correct. If the number is lower than the actual amount, the code will panic at some point. -pub struct PageStateCache(Cache>); +#[derive(Debug)] +pub struct PageStateCache( + Cache, UncachedPagePointers>, +); impl PageStateCache { /// Construct a new instance pub const fn new() -> Self { - Self(Cache::new(CachedPageStates::new())) + Self(Cache::new(CachedPageStates::new(), UncachedPagePointers)) } } impl PrivateCacheImpl for PageStateCache { type PSC = CachedPageStates; + type PPC = UncachedPagePointers; - fn inner(&mut self) -> &mut Cache { + fn inner(&mut self) -> &mut Cache { &mut self.0 } } impl CacheImpl for PageStateCache {} +/// A cache object that keeps track of the page states and some pointers to the items in the page. +/// +/// This cache has to be kept around and passed to *every* api call to the same memory region until the cache gets discarded. +/// +/// Valid usecase: +/// `Create cache 1` -> `use 1` -> `use 1` -> `create cache 2` -> `use 2` -> `use 2` +/// +/// Invalid usecase: +/// `Create cache 1` -> `use 1` -> `create cache 2` -> `use 2` -> `❌ use 1 ❌` +/// +/// Make sure the page count is correct. If the number is lower than the actual amount, the code will panic at some point. #[derive(Debug)] -pub(crate) struct Cache { +pub struct PagePointerCache( + Cache, CachedPagePointers>, +); + +impl PagePointerCache { + /// Construct a new instance + pub const fn new() -> Self { + Self(Cache::new( + CachedPageStates::new(), + CachedPagePointers::new(), + )) + } +} + +impl PrivateCacheImpl for PagePointerCache { + type PSC = CachedPageStates; + type PPC = CachedPagePointers; + + fn inner(&mut self) -> &mut Cache { + &mut self.0 + } +} + +impl CacheImpl for PagePointerCache {} + +/// The cache struct that is behind it all. +/// +/// It manages the cache state in a generic way. For every field it can be chosen to have it be cached or not. +#[derive(Debug)] +pub(crate) struct Cache { + /// Managed from the library code. + /// + /// When true, the cache and/or flash has changed and things might not be fully + /// consistent if there's an early return due to error. dirty: bool, page_states: PSC, + page_pointers: PPC, } -impl Cache { - pub(crate) const fn new(page_states: PSC) -> Self { +impl Cache { + pub(crate) const fn new(page_states: PSC, page_pointers: PPC) -> Self { Self { dirty: false, page_states, + page_pointers, } } + /// True if the cache might be inconsistent pub(crate) fn is_dirty(&self) -> bool { self.dirty } + /// Mark the cache as potentially inconsistent with reality pub(crate) fn mark_dirty(&mut self) { self.dirty = true; } + /// Mark the cache as being consistent with reality pub(crate) fn unmark_dirty(&mut self) { self.dirty = false; } + /// Wipe the cache pub(crate) fn invalidate_cache_state(&mut self) { self.dirty = false; self.page_states.invalidate_cache_state(); + self.page_pointers.invalidate_cache_state(); } + /// Get the cache state of the requested page pub(crate) fn get_page_state(&self, page_index: usize) -> Option { self.page_states.get_page_state(page_index) } - pub(crate) fn notice_page_state(&mut self, page_index: usize, new_state: PageState) { + /// Let the cache know a page state changed + /// + /// The dirty flag should be true if the page state is actually going to be changed. + /// Keep it false if we're only discovering the state. + pub(crate) fn notice_page_state( + &mut self, + page_index: usize, + new_state: PageState, + dirty: bool, + ) { + if dirty { + self.mark_dirty(); + } + self.page_states.notice_page_state(page_index, new_state); + self.page_pointers.notice_page_state(page_index, new_state); + } + + /// Get the cached address of the first non-erased item in the requested page. + /// + /// This is purely for the items that get erased from start to end. + pub(crate) fn first_item_after_erased(&self, page_index: usize) -> Option { + self.page_pointers.first_item_after_erased(page_index) + } + + /// Get the cached address of the first free unwritten item in the requested page. + pub(crate) fn first_item_after_written(&self, page_index: usize) -> Option { + self.page_pointers.first_item_after_written(page_index) + } + + /// Let the cache know that an item has been written to flash + pub(crate) fn notice_item_written( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ) { self.mark_dirty(); - self.page_states.notice_page_state(page_index, new_state) + self.page_pointers + .notice_item_written::(flash_range, item_address, item_header) + } + + /// Let the cache know that an item has been erased from flash + pub(crate) fn notice_item_erased( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ) { + self.mark_dirty(); + self.page_pointers + .notice_item_erased::(flash_range, item_address, item_header) } } -pub(crate) trait PageStatesCache { +pub(crate) trait PageStatesCache: Debug { fn get_page_state(&self, page_index: usize) -> Option; fn notice_page_state(&mut self, page_index: usize, new_state: PageState); fn invalidate_cache_state(&mut self); } -#[derive(Debug)] pub(crate) struct CachedPageStates { pages: [Option; PAGE_COUNT], } +impl Debug for CachedPageStates { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[")?; + for (i, val) in self.pages.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + + if let Some(val) = val { + write!(f, "{val:?}")?; + } else { + write!(f, "?")?; + } + } + write!(f, "]")?; + + Ok(()) + } +} + impl CachedPageStates { pub const fn new() -> Self { Self { @@ -172,6 +306,170 @@ impl PageStatesCache for UncachedPageSates { fn invalidate_cache_state(&mut self) {} } +pub(crate) trait PagePointersCache: Debug { + fn first_item_after_erased(&self, page_index: usize) -> Option; + fn first_item_after_written(&self, page_index: usize) -> Option; + + fn notice_item_written( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ); + fn notice_item_erased( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ); + + fn notice_page_state(&mut self, page_index: usize, new_state: PageState); + fn invalidate_cache_state(&mut self); +} + +// Use NoneZeroU32 because we never store 0's in here (because of the first page marker) +// and so Option can make use of the niche so we save bytes +pub(crate) struct CachedPagePointers { + after_erased_pointers: [Option; PAGE_COUNT], + after_written_pointers: [Option; PAGE_COUNT], +} + +impl Debug for CachedPagePointers { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{{ after_erased_pointers: [")?; + for (i, val) in self.after_erased_pointers.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + + if let Some(val) = val { + write!(f, "{:?}", val.get())?; + } else { + write!(f, "?")?; + } + } + write!(f, "], after_written_pointers: [")?; + for (i, val) in self.after_written_pointers.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + + if let Some(val) = val { + write!(f, "{:?}", val.get())?; + } else { + write!(f, "?")?; + } + } + write!(f, "] }}")?; + + Ok(()) + } +} + +impl CachedPagePointers { + pub const fn new() -> Self { + Self { + after_erased_pointers: [None; PAGE_COUNT], + after_written_pointers: [None; PAGE_COUNT], + } + } +} + +impl PagePointersCache for CachedPagePointers { + fn first_item_after_erased(&self, page_index: usize) -> Option { + self.after_erased_pointers[page_index].map(|val| val.get()) + } + + fn first_item_after_written(&self, page_index: usize) -> Option { + self.after_written_pointers[page_index].map(|val| val.get()) + } + + fn notice_item_written( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ) { + let page_index = calculate_page_index::(flash_range, item_address); + + let next_item_address = item_header.next_item_address::(item_address); + + // We only care about the furthest written item, so discard if this is an earlier item + if let Some(first_item_after_written) = self.first_item_after_written(page_index) { + if next_item_address <= first_item_after_written { + return; + } + } + + self.after_written_pointers[page_index] = NonZeroU32::new(next_item_address); + } + + fn notice_item_erased( + &mut self, + flash_range: Range, + item_address: u32, + item_header: &ItemHeader, + ) { + let page_index = calculate_page_index::(flash_range.clone(), item_address); + + // Either the item we point to or the first item on the page + let next_unerased_item = self.first_item_after_erased(page_index).unwrap_or_else(|| { + calculate_page_address::(flash_range, page_index) + S::WORD_SIZE as u32 + }); + + if item_address == next_unerased_item { + self.after_erased_pointers[page_index] = + NonZeroU32::new(item_header.next_item_address::(item_address)); + } + } + + fn notice_page_state(&mut self, page_index: usize, new_state: PageState) { + if new_state.is_open() { + // This page was erased + self.after_erased_pointers[page_index] = None; + self.after_written_pointers[page_index] = None; + } + } + + fn invalidate_cache_state(&mut self) { + self.after_erased_pointers = [None; PAGE_COUNT]; + self.after_written_pointers = [None; PAGE_COUNT]; + } +} + +#[derive(Debug, Default)] +pub(crate) struct UncachedPagePointers; + +impl PagePointersCache for UncachedPagePointers { + fn first_item_after_erased(&self, _page_index: usize) -> Option { + None + } + + fn first_item_after_written(&self, _page_index: usize) -> Option { + None + } + + fn notice_item_written( + &mut self, + _flash_range: Range, + _item_address: u32, + _item_header: &ItemHeader, + ) { + } + + fn notice_item_erased( + &mut self, + _flash_range: Range, + _item_address: u32, + _item_header: &ItemHeader, + ) { + } + + fn notice_page_state(&mut self, _page_index: usize, _new_state: PageState) {} + + fn invalidate_cache_state(&mut self) {} +} + #[cfg(test)] mod queue_tests { use core::ops::Range; @@ -216,6 +514,20 @@ mod queue_tests { ); } + #[test] + async fn page_pointer_cache() { + assert_eq!( + run_test(&mut PagePointerCache::::new()).await, + FlashStatsResult { + erases: 146, + reads: 211172, + writes: 6299, + bytes_read: 1699320, + bytes_written: 53299 + } + ); + } + async fn run_test(mut cache: impl CacheImpl) -> FlashStatsResult { let mut flash = mock_flash::MockFlashBase::::new(WriteCountCheck::Twice, None, true); @@ -299,9 +611,23 @@ mod map_tests { run_test(&mut PageStateCache::::new()).await, FlashStatsResult { erases: 198, - reads: 172831, + reads: 171543, + writes: 5201, + bytes_read: 1718356, + bytes_written: 50401 + } + ); + } + + #[test] + async fn page_pointer_cache() { + assert_eq!( + run_test(&mut PagePointerCache::::new()).await, + FlashStatsResult { + erases: 198, + reads: 153667, writes: 5201, - bytes_read: 1719644, + bytes_read: 1575348, bytes_written: 50401 } ); diff --git a/src/item.rs b/src/item.rs index 6c136af..2a8410f 100644 --- a/src/item.rs +++ b/src/item.rs @@ -27,10 +27,10 @@ use core::ops::Range; use embedded_storage_async::nor_flash::{MultiwriteNorFlash, NorFlash}; use crate::{ - cache::{Cache, PageStatesCache}, - calculate_page_address, calculate_page_end_address, get_page_state, round_down_to_alignment, - round_down_to_alignment_usize, round_up_to_alignment, round_up_to_alignment_usize, AlignedBuf, - Error, NorFlashExt, PageState, MAX_WORD_SIZE, + cache::{Cache, PagePointersCache, PageStatesCache}, + calculate_page_address, calculate_page_end_address, calculate_page_index, get_page_state, + round_down_to_alignment, round_down_to_alignment_usize, round_up_to_alignment, + round_up_to_alignment_usize, AlignedBuf, Error, NorFlashExt, PageState, MAX_WORD_SIZE, }; #[derive(Debug)] @@ -163,9 +163,12 @@ impl ItemHeader { pub async fn erase_data( mut self, flash: &mut S, + flash_range: Range, + cache: &mut Cache, address: u32, ) -> Result> { self.crc = None; + cache.notice_item_erased::(flash_range.clone(), address, &self); self.write(flash, address).await?; Ok(self) } @@ -206,6 +209,8 @@ impl<'d> Item<'d> { pub async fn write_new( flash: &mut S, + flash_range: Range, + cache: &mut Cache, address: u32, data: &'d [u8], ) -> Result> { @@ -214,17 +219,20 @@ impl<'d> Item<'d> { crc: Some(adapted_crc32(data)), }; - Self::write_raw(&header, data, flash, address).await?; + Self::write_raw(flash, flash_range, cache, &header, data, address).await?; Ok(header) } async fn write_raw( + flash: &mut S, + flash_range: Range, + cache: &mut Cache, header: &ItemHeader, data: &[u8], - flash: &mut S, address: u32, ) -> Result<(), Error> { + cache.notice_item_written::(flash_range, address, header); header.write(flash, address).await?; let (data_block, data_left) = data.split_at(round_down_to_alignment_usize::(data.len())); @@ -261,9 +269,19 @@ impl<'d> Item<'d> { pub async fn write( &self, flash: &mut S, + flash_range: Range, + cache: &mut Cache, address: u32, ) -> Result<(), Error> { - Self::write_raw(&self.header, self.data(), flash, address).await + Self::write_raw( + flash, + flash_range, + cache, + &self.header, + self.data(), + address, + ) + .await } } @@ -284,13 +302,30 @@ impl<'d> core::fmt::Debug for Item<'d> { /// - `end_address` is exclusive. pub async fn find_next_free_item_spot( flash: &mut S, + flash_range: Range, + cache: &mut Cache, start_address: u32, end_address: u32, data_length: u32, ) -> Result, Error> { - let (_, free_item_address) = ItemHeaderIter::new(start_address, end_address) - .traverse(flash, |_, _| true) - .await?; + let page_index = calculate_page_index::(flash_range, start_address); + + let free_item_address = match cache.first_item_after_written(page_index) { + Some(free_item_address) => free_item_address, + None => { + ItemHeaderIter::new( + cache + .first_item_after_erased(page_index) + .unwrap_or(0) + .max(start_address), + end_address, + ) + .traverse(flash, |_, _| true) + .await? + .1 + } + }; + if let Some(available) = ItemHeader::available_data_bytes::(end_address - free_item_address) { if available >= data_length { @@ -405,7 +440,7 @@ fn crc32_with_initial(data: &[u8], initial: u32) -> u32 { pub async fn is_page_empty( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, page_index: usize, page_state: Option, ) -> Result> { @@ -422,13 +457,16 @@ pub async fn is_page_empty( calculate_page_end_address::(flash_range.clone(), page_index) - S::WORD_SIZE as u32; - Ok( - ItemHeaderIter::new(page_data_start_address, page_data_end_address) - .traverse(flash, |header, _| header.crc.is_none()) - .await? - .0 - .is_none(), + Ok(ItemHeaderIter::new( + cache + .first_item_after_erased(page_index) + .unwrap_or(page_data_start_address), + page_data_end_address, ) + .traverse(flash, |header, _| header.crc.is_none()) + .await? + .0 + .is_none()) } PageState::PartialOpen => Ok(false), PageState::Open => Ok(true), diff --git a/src/lib.rs b/src/lib.rs index 158c3a3..1a706d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ // - flash erase size is quite big, aka, this is a paged flash // - flash write size is quite small, so it writes words and not full pages -use cache::{Cache, PageStatesCache}; +use cache::{Cache, PagePointersCache, PageStatesCache}; use core::{ fmt::Debug, ops::{Deref, DerefMut, Range}, @@ -85,7 +85,7 @@ async fn try_general_repair( async fn find_first_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, starting_page_index: usize, page_state: PageState, ) -> Result, Error> { @@ -150,7 +150,7 @@ const MARKER: u8 = 0; async fn get_page_state( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, page_index: usize, ) -> Result> { if let Some(cached_page_state) = cache.get_page_state(page_index) { @@ -208,7 +208,8 @@ async fn get_page_state( (false, false) => PageState::Open, }; - cache.notice_page_state(page_index, discovered_state); + // Not dirty because nothing changed and nothing can be inconsistent + cache.notice_page_state(page_index, discovered_state, false); Ok(discovered_state) } @@ -217,10 +218,10 @@ async fn get_page_state( async fn open_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, page_index: usize, ) -> Result<(), Error> { - cache.notice_page_state(page_index, PageState::Open); + cache.notice_page_state(page_index, PageState::Open, true); flash .erase( @@ -241,7 +242,7 @@ async fn open_page( async fn close_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, page_index: usize, ) -> Result<(), Error> { let current_state = @@ -251,7 +252,7 @@ async fn close_page( return Ok(()); } - cache.notice_page_state(page_index, PageState::Closed); + cache.notice_page_state(page_index, PageState::Closed, true); let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]); // Close the end marker @@ -274,7 +275,7 @@ async fn close_page( async fn partial_close_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, page_index: usize, ) -> Result> { let current_state = get_page_state::(flash, flash_range.clone(), cache, page_index).await?; @@ -289,7 +290,7 @@ async fn partial_close_page( PageState::Open => PageState::PartialOpen, }; - cache.notice_page_state(page_index, new_state); + cache.notice_page_state(page_index, new_state, true); let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]); // Close the start marker diff --git a/src/map.rs b/src/map.rs index 67aeb23..a747389 100644 --- a/src/map.rs +++ b/src/map.rs @@ -141,6 +141,10 @@ pub async fn fetch_item( data_buffer: &mut [u8], search_key: I::Key, ) -> Result, MapError> { + if cache.inner().is_dirty() { + cache.inner().invalidate_cache_state(); + } + Ok( fetch_item_with_location(flash, flash_range, cache.inner(), data_buffer, search_key) .await? @@ -153,7 +157,7 @@ pub async fn fetch_item( async fn fetch_item_with_location( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, data_buffer: &mut [u8], search_key: I::Key, ) -> Result, MapError> { @@ -164,10 +168,6 @@ async fn fetch_item_with_location( assert!(S::ERASE_SIZE >= S::WORD_SIZE * 3); assert!(S::WORD_SIZE <= MAX_WORD_SIZE); - if cache.is_dirty() { - cache.invalidate_cache_state(); - } - // We need to find the page we were last using. This should be the only partial open page. let mut last_used_page = find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen).await?; @@ -323,6 +323,8 @@ pub async fn store_item( let free_spot_address = find_next_free_item_spot( flash, + flash_range.clone(), + cache, page_data_start_address, page_data_end_address, item_data_length as u32, @@ -331,8 +333,14 @@ pub async fn store_item( match free_spot_address { Some(free_spot_address) => { - Item::write_new(flash, free_spot_address, &data_buffer[..item_data_length]) - .await?; + Item::write_new( + flash, + flash_range.clone(), + cache, + free_spot_address, + &data_buffer[..item_data_length], + ) + .await?; cache.unmark_dirty(); return Ok(()); @@ -521,7 +529,7 @@ impl PartialEq for MapError { async fn migrate_items( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, data_buffer: &mut [u8], source_page: usize, target_page: usize, @@ -559,7 +567,8 @@ async fn migrate_items( .read_item(flash, data_buffer, item_address, u32::MAX) .await? .unwrap()?; - item.write(flash, next_page_write_address).await?; + item.write(flash, flash_range.clone(), cache, next_page_write_address) + .await?; next_page_write_address = item.header.next_item_address::(next_page_write_address); } } diff --git a/src/queue.rs b/src/queue.rs index 7f87294..552d87f 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -114,6 +114,8 @@ pub async fn push( let mut next_address = find_next_free_item_spot( flash, + flash_range.clone(), + cache, page_data_start_address, page_data_end_address, data.len() as u32, @@ -160,7 +162,14 @@ pub async fn push( } } - Item::write_new(flash, next_address.unwrap(), data).await?; + Item::write_new( + flash, + flash_range.clone(), + cache, + next_address.unwrap(), + data, + ) + .await?; cache.unmark_dirty(); Ok(()) @@ -269,7 +278,15 @@ impl<'d, S: MultiwriteNorFlash, CI: CacheImpl> PopIterator<'d, S, CI> { let (header, data_buffer) = item.destruct(); let ret = &mut data_buffer[..header.length as usize]; - match header.erase_data(self.iter.flash, item_address).await { + match header + .erase_data( + self.iter.flash, + self.iter.flash_range.clone(), + self.iter.cache.inner(), + item_address, + ) + .await + { Ok(_) => { self.iter.cache.inner().unmark_dirty(); Ok(Some(ret)) @@ -354,11 +371,15 @@ impl<'d, S: NorFlash, CI: CacheImpl> QueueIterator<'d, S, CI> { cache.inner().invalidate_cache_state(); } + let oldest_page = find_oldest_page(flash, flash_range.clone(), cache.inner()).await?; + // We start at the start of the oldest page - let current_address = calculate_page_address::( - flash_range.clone(), - find_oldest_page(flash, flash_range.clone(), cache.inner()).await?, - ) + S::WORD_SIZE as u32; + let current_address = match cache.inner().first_item_after_erased(oldest_page) { + Some(address) => address, + None => { + calculate_page_address::(flash_range.clone(), oldest_page) + S::WORD_SIZE as u32 + } + }; Ok(Self { flash, @@ -423,6 +444,7 @@ impl<'d, S: NorFlash, CI: CacheImpl> QueueIterator<'d, S, CI> { // Search for the first item with data let mut it = ItemHeaderIter::new(current_address, page_data_end_address); + // No need to worry about cache here since that has been dealt with at the creation of this iterator if let (Some(found_item_header), found_item_address) = it .traverse(self.flash, |header, _| header.crc.is_none()) .await? @@ -529,10 +551,20 @@ pub async fn find_max_fit( let page_data_end_address = calculate_page_end_address::(flash_range.clone(), current_page) - S::WORD_SIZE as u32; - let next_item_address = ItemHeaderIter::new(page_data_start_address, page_data_end_address) - .traverse(flash, |_, _| true) - .await? - .1; + let next_item_address = match cache.first_item_after_written(current_page) { + Some(next_item_address) => next_item_address, + None => { + ItemHeaderIter::new( + cache + .first_item_after_erased(current_page) + .unwrap_or(page_data_start_address), + page_data_end_address, + ) + .traverse(flash, |_, _| true) + .await? + .1 + } + }; cache.unmark_dirty(); Ok(ItemHeader::available_data_bytes::( @@ -543,7 +575,7 @@ pub async fn find_max_fit( async fn find_youngest_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, ) -> Result> { let last_used_page = find_first_page(flash, flash_range.clone(), cache, 0, PageState::PartialOpen).await?; @@ -569,7 +601,7 @@ async fn find_youngest_page( async fn find_oldest_page( flash: &mut S, flash_range: Range, - cache: &mut Cache, + cache: &mut Cache, ) -> Result> { let youngest_page = find_youngest_page(flash, flash_range.clone(), cache).await?;