Skip to main content

sequential_storage/cache/
page_pointers.rs

1//! Implementation of page pointer caching
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6use core::{fmt::Debug, num::NonZeroU32, ops::Range};
7
8use embedded_storage_async::nor_flash::NorFlash;
9
10use crate::{
11    NorFlashExt, PageState, calculate_page_address, calculate_page_index, item::ItemHeader,
12};
13
14pub(crate) trait SealedPagePointersCache {
15    fn first_item_after_erased(&self, page_index: usize) -> Option<u32>;
16    fn first_item_after_written(&self, page_index: usize) -> Option<u32>;
17
18    fn notice_item_written<S: NorFlash>(
19        &mut self,
20        flash_range: Range<u32>,
21        item_address: u32,
22        item_header: &ItemHeader,
23    );
24    fn notice_item_erased<S: NorFlash>(
25        &mut self,
26        flash_range: Range<u32>,
27        item_address: u32,
28        item_header: &ItemHeader,
29    );
30
31    fn notice_page_state(&mut self, page_index: usize, new_state: PageState);
32    fn invalidate_cache_state(&mut self);
33}
34
35/// A cache that caches which parts of the pages are written to already
36#[allow(private_bounds)]
37pub trait PagePointersCache: SealedPagePointersCache {}
38
39/// A cache that stores page pointers in an array.
40#[derive(Debug)]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42pub struct ArrayPagePointers<const PAGE_COUNT: usize> {
43    after_erased_pointers: [Option<NonZeroU32>; PAGE_COUNT],
44    after_written_pointers: [Option<NonZeroU32>; PAGE_COUNT],
45}
46
47impl<const PAGE_COUNT: usize> ArrayPagePointers<PAGE_COUNT> {
48    /// Create a new empty cache
49    pub const fn new() -> Self {
50        Self {
51            after_erased_pointers: [None; _],
52            after_written_pointers: [None; _],
53        }
54    }
55
56    fn as_tracked_mut(&mut self) -> TrackedPagePointers<'_> {
57        TrackedPagePointers {
58            after_erased_pointers: &mut self.after_erased_pointers,
59            after_written_pointers: &mut self.after_written_pointers,
60        }
61    }
62}
63
64impl<const PAGE_COUNT: usize> Default for ArrayPagePointers<PAGE_COUNT> {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl<const PAGE_COUNT: usize> SealedPagePointersCache for ArrayPagePointers<PAGE_COUNT> {
71    fn first_item_after_erased(&self, page_index: usize) -> Option<u32> {
72        self.after_erased_pointers
73            .get(page_index)
74            .copied()
75            .flatten()
76            .map(NonZeroU32::get)
77    }
78
79    fn first_item_after_written(&self, page_index: usize) -> Option<u32> {
80        self.after_written_pointers
81            .get(page_index)
82            .copied()
83            .flatten()
84            .map(NonZeroU32::get)
85    }
86
87    fn notice_item_written<S: NorFlash>(
88        &mut self,
89        flash_range: Range<u32>,
90        item_address: u32,
91        item_header: &ItemHeader,
92    ) {
93        self.as_tracked_mut()
94            .notice_item_written::<S>(flash_range, item_address, item_header);
95    }
96
97    fn notice_item_erased<S: NorFlash>(
98        &mut self,
99        flash_range: Range<u32>,
100        item_address: u32,
101        item_header: &ItemHeader,
102    ) {
103        self.as_tracked_mut()
104            .notice_item_erased::<S>(flash_range, item_address, item_header);
105    }
106
107    fn notice_page_state(&mut self, page_index: usize, new_state: PageState) {
108        self.as_tracked_mut()
109            .notice_page_state(page_index, new_state);
110    }
111
112    fn invalidate_cache_state(&mut self) {
113        self.as_tracked_mut().invalidate_cache_state();
114    }
115}
116impl<const PAGE_COUNT: usize> PagePointersCache for ArrayPagePointers<PAGE_COUNT> {}
117
118#[cfg(feature = "alloc")]
119/// A cache that stores page pointers in a vec.
120#[derive(Debug)]
121#[cfg_attr(feature = "defmt", derive(defmt::Format))]
122pub struct HeapPagePointers {
123    after_erased_pointers: alloc::vec::Vec<Option<NonZeroU32>>,
124    after_written_pointers: alloc::vec::Vec<Option<NonZeroU32>>,
125}
126
127#[cfg(feature = "alloc")]
128impl HeapPagePointers {
129    /// Create a new empty cache
130    pub fn new(page_count: usize) -> Self {
131        Self {
132            after_erased_pointers: alloc::vec![None; page_count],
133            after_written_pointers: alloc::vec![None; page_count],
134        }
135    }
136
137    fn as_tracked(&mut self) -> TrackedPagePointers<'_> {
138        TrackedPagePointers {
139            after_erased_pointers: &mut self.after_erased_pointers,
140            after_written_pointers: &mut self.after_written_pointers,
141        }
142    }
143}
144
145#[cfg(feature = "alloc")]
146impl SealedPagePointersCache for HeapPagePointers {
147    fn first_item_after_erased(&self, page_index: usize) -> Option<u32> {
148        self.after_erased_pointers
149            .get(page_index)
150            .copied()
151            .flatten()
152            .map(NonZeroU32::get)
153    }
154
155    fn first_item_after_written(&self, page_index: usize) -> Option<u32> {
156        self.after_written_pointers
157            .get(page_index)
158            .copied()
159            .flatten()
160            .map(NonZeroU32::get)
161    }
162
163    fn notice_item_written<S: NorFlash>(
164        &mut self,
165        flash_range: Range<u32>,
166        item_address: u32,
167        item_header: &ItemHeader,
168    ) {
169        self.as_tracked()
170            .notice_item_written::<S>(flash_range, item_address, item_header);
171    }
172
173    fn notice_item_erased<S: NorFlash>(
174        &mut self,
175        flash_range: Range<u32>,
176        item_address: u32,
177        item_header: &ItemHeader,
178    ) {
179        self.as_tracked()
180            .notice_item_erased::<S>(flash_range, item_address, item_header);
181    }
182
183    fn notice_page_state(&mut self, page_index: usize, new_state: PageState) {
184        self.as_tracked().notice_page_state(page_index, new_state);
185    }
186
187    fn invalidate_cache_state(&mut self) {
188        self.as_tracked().invalidate_cache_state();
189    }
190}
191#[cfg(feature = "alloc")]
192impl PagePointersCache for HeapPagePointers {}
193
194// Use NonZeroU32 because we never store 0's in here (because of the first page marker)
195// and so Option can make use of the niche so we save bytes
196pub(crate) struct TrackedPagePointers<'a> {
197    after_erased_pointers: &'a mut [Option<NonZeroU32>],
198    after_written_pointers: &'a mut [Option<NonZeroU32>],
199}
200
201impl SealedPagePointersCache for TrackedPagePointers<'_> {
202    fn first_item_after_erased(&self, page_index: usize) -> Option<u32> {
203        self.after_erased_pointers
204            .get(page_index)
205            .copied()
206            .flatten()
207            .map(NonZeroU32::get)
208    }
209
210    fn first_item_after_written(&self, page_index: usize) -> Option<u32> {
211        self.after_written_pointers
212            .get(page_index)
213            .copied()
214            .flatten()
215            .map(NonZeroU32::get)
216    }
217
218    fn notice_item_written<S: NorFlash>(
219        &mut self,
220        flash_range: Range<u32>,
221        item_address: u32,
222        item_header: &ItemHeader,
223    ) {
224        let page_index = calculate_page_index::<S>(flash_range, item_address);
225
226        let next_item_address = item_header.next_item_address::<S>(item_address);
227
228        // We only care about the furthest written item, so discard if this is an earlier item
229        if let Some(first_item_after_written) = self.first_item_after_written(page_index) {
230            if next_item_address <= first_item_after_written {
231                return;
232            }
233        }
234
235        if let Some(after_written_pointer) = self.after_written_pointers.get_mut(page_index) {
236            *after_written_pointer = NonZeroU32::new(next_item_address);
237        }
238    }
239
240    fn notice_item_erased<S: NorFlash>(
241        &mut self,
242        flash_range: Range<u32>,
243        item_address: u32,
244        item_header: &ItemHeader,
245    ) {
246        let page_index = calculate_page_index::<S>(flash_range.clone(), item_address);
247
248        // Either the item we point to or the first item on the page
249        let next_unerased_item = self.first_item_after_erased(page_index).unwrap_or_else(|| {
250            calculate_page_address::<S>(flash_range, page_index) + S::WORD_SIZE as u32
251        });
252
253        if item_address == next_unerased_item {
254            if let Some(after_erased_pointer) = self.after_erased_pointers.get_mut(page_index) {
255                *after_erased_pointer =
256                    NonZeroU32::new(item_header.next_item_address::<S>(item_address));
257            }
258        }
259    }
260
261    fn notice_page_state(&mut self, page_index: usize, new_state: PageState) {
262        if new_state.is_open() {
263            // This page was erased
264            if let Some(after_erased_pointer) = self.after_erased_pointers.get_mut(page_index) {
265                *after_erased_pointer = None;
266            }
267            if let Some(after_written_pointer) = self.after_written_pointers.get_mut(page_index) {
268                *after_written_pointer = None;
269            }
270        }
271    }
272
273    fn invalidate_cache_state(&mut self) {
274        for pointers in self.after_erased_pointers.iter_mut() {
275            *pointers = None;
276        }
277        for pointers in self.after_written_pointers.iter_mut() {
278            *pointers = None;
279        }
280    }
281}