Skip to main content

sequential_storage/cache/
mod.rs

1//! Module implementing all things cache related
2
3use core::marker::PhantomData;
4use core::{fmt::Debug, ops::Range};
5
6use embedded_storage_async::nor_flash::NorFlash;
7
8use crate::cache::key_pointers::KeyPointersCache;
9use crate::cache::page_pointers::SealedPagePointersCache;
10use crate::cache::page_states::SealedPageStatesCache;
11use crate::{PageState, item::ItemHeader};
12
13use key_pointers::SealedKeyPointersCache;
14use page_pointers::PagePointersCache;
15use page_states::PageStatesCache;
16
17pub mod key_pointers;
18pub mod page_pointers;
19pub mod page_states;
20
21mod tests;
22
23/// Trait so we can collapse generics
24pub(crate) trait SealedCacheImpl {
25    /// True if the cache might be inconsistent
26    fn is_dirty(&mut self) -> bool;
27    /// Mark the cache as potentially inconsistent with reality
28    fn mark_dirty(&mut self);
29    /// Mark the cache as being consistent with reality
30    fn unmark_dirty(&mut self);
31
32    /// Get the cache state of the requested page
33    fn get_page_state(&mut self, page_index: usize) -> Option<PageState>;
34    /// Let the cache know a page state changed
35    ///
36    /// The dirty flag should be true if the page state is actually going to be changed.
37    /// Keep it false if we're only discovering the state.
38    fn notice_page_state(&mut self, page_index: usize, new_state: PageState, dirty: bool);
39
40    /// Get the cached address of the first non-erased item in the requested page.
41    ///
42    /// This is purely for the items that get erased from start to end.
43    fn first_item_after_erased(&mut self, page_index: usize) -> Option<u32>;
44    /// Get the cached address of the first free unwritten item in the requested page.
45    fn first_item_after_written(&mut self, page_index: usize) -> Option<u32>;
46    /// Let the cache know that an item has been written to flash
47    fn notice_item_written<S: NorFlash>(
48        &mut self,
49        flash_range: Range<u32>,
50        item_address: u32,
51        item_header: &ItemHeader,
52    );
53    /// Let the cache know that an item has been erased from flash
54    fn notice_item_erased<S: NorFlash>(
55        &mut self,
56        flash_range: Range<u32>,
57        item_address: u32,
58        item_header: &ItemHeader,
59    );
60    /// Erase all cache information. Something was found that means the cache doesn't hold up anymore.
61    fn invalidate_cache_state(&mut self);
62}
63pub(crate) trait SealedKeyCacheImpl<KEY> {
64    /// Get the location of the item with the given key
65    fn key_location(&mut self, key: &KEY) -> Option<u32>;
66    /// Let the cache know that an item was found with the given key at the given address
67    fn notice_key_location(&mut self, key: &KEY, item_address: u32, dirty: bool);
68    /// The item with the given key was erased
69    fn notice_key_erased(&mut self, key: &KEY);
70}
71
72#[allow(private_bounds)]
73/// Trait for collapsing multiple generics into one.
74/// Wherever this trait is used, pass an instance of [Cache].
75pub trait CacheImpl<KEY>: SealedCacheImpl + SealedKeyCacheImpl<KEY> {}
76
77/// A cache object of which the various things it keeps track of can be configured..
78///
79/// This cache has to be kept around and passed to *every* api call to the same memory region until the cache gets discarded.
80///
81/// Valid usecase:\
82/// `Create cache 1` -> `use 1` -> `use 1` -> `create cache 2` -> `use 2` -> `use 2`
83///
84/// Invalid usecase:\
85/// `Create cache 1` -> `use 1` -> `create cache 2` -> `use 2` -> `❌ use 1 ❌`
86///
87/// Make sure the page count is correct when it has to be provided.
88/// If the number is lower than the actual amount, the code will panic.
89#[derive(Debug)]
90#[cfg_attr(feature = "defmt", derive(defmt::Format))]
91pub struct Cache<
92    PAGE: PageStatesCache,
93    POINTERS: PagePointersCache,
94    KEYS: KeyPointersCache<KEY>,
95    KEY = (),
96> {
97    dirt_tracker: DirtTracker,
98    page_states: PAGE,
99    page_pointers: POINTERS,
100    key_pointers: KEYS,
101    _phantom: PhantomData<KEY>,
102}
103
104impl<PAGE: PageStatesCache, POINTERS: PagePointersCache, KEYS: KeyPointersCache<KEY>, KEY>
105    Cache<PAGE, POINTERS, KEYS, KEY>
106{
107    /// Create a new cache object from the given subcaches
108    pub fn new(page_states: PAGE, page_pointers: POINTERS, key_pointers: KEYS) -> Self {
109        Self {
110            dirt_tracker: DirtTracker::new(),
111            page_states,
112            page_pointers,
113            key_pointers,
114            _phantom: PhantomData,
115        }
116    }
117}
118
119impl<KEY> Cache<Uncached, Uncached, Uncached, KEY> {
120    /// Create a new cache object that doesn't cache anything
121    pub const fn new_uncached() -> Self {
122        Self {
123            dirt_tracker: DirtTracker::new(),
124            page_states: Uncached,
125            page_pointers: Uncached,
126            key_pointers: Uncached,
127            _phantom: PhantomData,
128        }
129    }
130}
131
132impl<PAGE: PageStatesCache, POINTERS: PagePointersCache, KEYS: KeyPointersCache<KEY>, KEY>
133    SealedCacheImpl for Cache<PAGE, POINTERS, KEYS, KEY>
134{
135    /// True if the cache might be inconsistent
136    fn is_dirty(&mut self) -> bool {
137        self.dirt_tracker.is_dirty()
138    }
139
140    /// Mark the cache as potentially inconsistent with reality
141    fn mark_dirty(&mut self) {
142        self.dirt_tracker.mark_dirty();
143    }
144
145    /// Mark the cache as being consistent with reality
146    fn unmark_dirty(&mut self) {
147        self.dirt_tracker.unmark_dirty();
148    }
149
150    /// Get the cache state of the requested page
151    fn get_page_state(&mut self, page_index: usize) -> Option<PageState> {
152        self.page_states.get_page_state(page_index)
153    }
154
155    /// Let the cache know a page state changed
156    ///
157    /// The dirty flag should be true if the page state is actually going to be changed.
158    /// Keep it false if we're only discovering the state.
159    fn notice_page_state(&mut self, page_index: usize, new_state: PageState, dirty: bool) {
160        if dirty {
161            self.mark_dirty();
162        }
163        self.page_states.notice_page_state(page_index, new_state);
164        self.page_pointers.notice_page_state(page_index, new_state);
165    }
166
167    /// Get the cached address of the first non-erased item in the requested page.
168    ///
169    /// This is purely for the items that get erased from start to end.
170    fn first_item_after_erased(&mut self, page_index: usize) -> Option<u32> {
171        self.page_pointers.first_item_after_erased(page_index)
172    }
173
174    /// Get the cached address of the first free unwritten item in the requested page.
175    fn first_item_after_written(&mut self, page_index: usize) -> Option<u32> {
176        self.page_pointers.first_item_after_written(page_index)
177    }
178
179    /// Let the cache know that an item has been written to flash
180    fn notice_item_written<S: NorFlash>(
181        &mut self,
182        flash_range: Range<u32>,
183        item_address: u32,
184        item_header: &ItemHeader,
185    ) {
186        self.mark_dirty();
187        self.page_pointers
188            .notice_item_written::<S>(flash_range, item_address, item_header);
189    }
190
191    /// Let the cache know that an item has been erased from flash
192    fn notice_item_erased<S: NorFlash>(
193        &mut self,
194        flash_range: Range<u32>,
195        item_address: u32,
196        item_header: &ItemHeader,
197    ) {
198        self.mark_dirty();
199        self.page_pointers
200            .notice_item_erased::<S>(flash_range, item_address, item_header);
201    }
202
203    fn invalidate_cache_state(&mut self) {
204        self.dirt_tracker.unmark_dirty();
205        self.page_states.invalidate_cache_state();
206        self.page_pointers.invalidate_cache_state();
207        self.key_pointers.invalidate_cache_state();
208    }
209}
210
211impl<PAGE: PageStatesCache, POINTERS: PagePointersCache, KEYS: KeyPointersCache<KEY>, KEY>
212    SealedKeyCacheImpl<KEY> for Cache<PAGE, POINTERS, KEYS, KEY>
213{
214    fn key_location(&mut self, key: &KEY) -> Option<u32> {
215        self.key_pointers.key_location(key)
216    }
217
218    fn notice_key_location(&mut self, key: &KEY, item_address: u32, dirty: bool) {
219        if dirty {
220            self.mark_dirty();
221        }
222        self.key_pointers.notice_key_location(key, item_address);
223    }
224
225    fn notice_key_erased(&mut self, key: &KEY) {
226        self.mark_dirty();
227        self.key_pointers.notice_key_erased(key);
228    }
229}
230
231impl<PAGE: PageStatesCache, POINTERS: PagePointersCache, KEYS: KeyPointersCache<KEY>, KEY>
232    CacheImpl<KEY> for Cache<PAGE, POINTERS, KEYS, KEY>
233{
234}
235
236#[derive(Debug)]
237#[cfg_attr(feature = "defmt", derive(defmt::Format))]
238pub(crate) struct DirtTracker {
239    /// Managed from the library code.
240    ///
241    /// When true, the cache and/or flash has changed and things might not be fully
242    /// consistent if there's an early return due to error.
243    dirty: bool,
244}
245
246impl DirtTracker {
247    pub const fn new() -> Self {
248        DirtTracker { dirty: false }
249    }
250
251    /// True if the cache might be inconsistent
252    pub fn is_dirty(&self) -> bool {
253        self.dirty
254    }
255
256    /// Mark the cache as potentially inconsistent with reality
257    pub fn mark_dirty(&mut self) {
258        self.dirty = true;
259    }
260
261    /// Mark the cache as being consistent with reality
262    pub fn unmark_dirty(&mut self) {
263        self.dirty = false;
264    }
265}
266
267/// A cache object implementing no cache.
268#[derive(Debug)]
269#[cfg_attr(feature = "defmt", derive(defmt::Format))]
270pub struct Uncached;
271
272impl SealedPageStatesCache for Uncached {
273    fn get_page_state(&self, _page_index: usize) -> Option<PageState> {
274        None
275    }
276    fn notice_page_state(&mut self, _page_index: usize, _new_state: PageState) {}
277    fn invalidate_cache_state(&mut self) {}
278}
279impl PageStatesCache for Uncached {}
280
281impl SealedPagePointersCache for Uncached {
282    fn first_item_after_erased(&self, _page_index: usize) -> Option<u32> {
283        None
284    }
285    fn first_item_after_written(&self, _page_index: usize) -> Option<u32> {
286        None
287    }
288    fn notice_item_written<S: NorFlash>(
289        &mut self,
290        _flash_range: Range<u32>,
291        _item_address: u32,
292        _item_header: &ItemHeader,
293    ) {
294    }
295    fn notice_item_erased<S: NorFlash>(
296        &mut self,
297        _flash_range: Range<u32>,
298        _item_address: u32,
299        _item_header: &ItemHeader,
300    ) {
301    }
302    fn notice_page_state(&mut self, _page_index: usize, _new_state: PageState) {}
303    fn invalidate_cache_state(&mut self) {}
304}
305impl PagePointersCache for Uncached {}
306
307impl<KEY> SealedKeyPointersCache<KEY> for Uncached {
308    fn key_location(&self, _key: &KEY) -> Option<u32> {
309        None
310    }
311    fn notice_key_location(&mut self, _key: &KEY, _item_address: u32) {}
312    fn notice_key_erased(&mut self, _key: &KEY) {}
313    fn invalidate_cache_state(&mut self) {}
314}
315impl<KEY> KeyPointersCache<KEY> for Uncached {}