1#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
2#![deny(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5use cache::PrivateCacheImpl;
11use core::{
12 fmt::Debug,
13 ops::{Deref, DerefMut, Range},
14};
15use embedded_storage_async::nor_flash::NorFlash;
16use map::SerializationError;
17
18#[cfg(feature = "alloc")]
19mod alloc_impl;
20#[cfg(feature = "arrayvec")]
21mod arrayvec_impl;
22pub mod cache;
23#[cfg(feature = "heapless-09")]
24mod heapless_09_impl;
25#[cfg(feature = "heapless")]
26mod heapless_impl;
27mod item;
28pub mod map;
29pub mod queue;
30
31#[cfg(any(test, doctest, feature = "_test"))]
32pub mod mock_flash;
34
35const MAX_WORD_SIZE: usize = 32;
40
41pub async fn erase_all<S: NorFlash>(
45 flash: &mut S,
46 flash_range: Range<u32>,
47) -> Result<(), Error<S::Error>> {
48 flash
49 .erase(flash_range.start, flash_range.end)
50 .await
51 .map_err(|e| Error::Storage {
52 value: e,
53 #[cfg(feature = "_test")]
54 backtrace: std::backtrace::Backtrace::capture(),
55 })
56}
57
58pub const fn item_overhead_size<S: NorFlash>() -> u32 {
63 item::ItemHeader::data_address::<S>(0)
64}
65
66#[repr(align(4))]
68pub(crate) struct AlignedBuf<const SIZE: usize>(pub(crate) [u8; SIZE]);
69impl<const SIZE: usize> Deref for AlignedBuf<SIZE> {
70 type Target = [u8];
71 fn deref(&self) -> &Self::Target {
72 &self.0
73 }
74}
75
76impl<const SIZE: usize> DerefMut for AlignedBuf<SIZE> {
77 fn deref_mut(&mut self) -> &mut Self::Target {
78 &mut self.0
79 }
80}
81
82async fn try_general_repair<S: NorFlash>(
83 flash: &mut S,
84 flash_range: Range<u32>,
85 cache: &mut impl PrivateCacheImpl,
86) -> Result<(), Error<S::Error>> {
87 for page_index in get_pages::<S>(flash_range.clone(), 0) {
90 if matches!(
91 get_page_state(flash, flash_range.clone(), cache, page_index).await,
92 Err(Error::Corrupted { .. })
93 ) {
94 open_page(flash, flash_range.clone(), cache, page_index).await?;
95 }
96 }
97
98 #[cfg(fuzzing_repro)]
99 eprintln!("General repair has been called");
100
101 Ok(())
102}
103
104async fn find_first_page<S: NorFlash>(
108 flash: &mut S,
109 flash_range: Range<u32>,
110 cache: &mut impl PrivateCacheImpl,
111 starting_page_index: usize,
112 page_state: PageState,
113) -> Result<Option<usize>, Error<S::Error>> {
114 for page_index in get_pages::<S>(flash_range.clone(), starting_page_index) {
115 if page_state == get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await? {
116 return Ok(Some(page_index));
117 }
118 }
119
120 Ok(None)
121}
122
123fn get_pages<S: NorFlash>(
125 flash_range: Range<u32>,
126 starting_page_index: usize,
127) -> impl DoubleEndedIterator<Item = usize> {
128 let page_count = flash_range.len() / S::ERASE_SIZE;
129 flash_range
130 .step_by(S::ERASE_SIZE)
131 .enumerate()
132 .map(move |(index, _)| (index + starting_page_index) % page_count)
133}
134
135fn next_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
137 let page_count = flash_range.len() / S::ERASE_SIZE;
138 (page_index + 1) % page_count
139}
140
141fn previous_page<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> usize {
143 let page_count = flash_range.len() / S::ERASE_SIZE;
144
145 match page_index.checked_sub(1) {
146 Some(new_page_index) => new_page_index,
147 None => page_count - 1,
148 }
149}
150
151const fn calculate_page_address<S: NorFlash>(flash_range: Range<u32>, page_index: usize) -> u32 {
153 flash_range.start + (S::ERASE_SIZE * page_index) as u32
154}
155const fn calculate_page_end_address<S: NorFlash>(
157 flash_range: Range<u32>,
158 page_index: usize,
159) -> u32 {
160 flash_range.start + (S::ERASE_SIZE * (page_index + 1)) as u32
161}
162#[allow(unused)]
164const fn calculate_page_index<S: NorFlash>(flash_range: Range<u32>, address: u32) -> usize {
165 (address - flash_range.start) as usize / S::ERASE_SIZE
166}
167
168const fn calculate_page_size<S: NorFlash>() -> usize {
169 S::ERASE_SIZE - S::WORD_SIZE * 2
171}
172
173const MARKER: u8 = 0;
175
176async fn get_page_state<S: NorFlash>(
178 flash: &mut S,
179 flash_range: Range<u32>,
180 cache: &mut impl PrivateCacheImpl,
181 page_index: usize,
182) -> Result<PageState, Error<S::Error>> {
183 if let Some(cached_page_state) = cache.get_page_state(page_index) {
184 return Ok(cached_page_state);
185 }
186
187 let page_address = calculate_page_address::<S>(flash_range, page_index);
188 const HALF_MARKER_BITS: u32 = 4;
192
193 let mut buffer = [0; MAX_WORD_SIZE];
194 flash
195 .read(page_address, &mut buffer[..S::READ_SIZE])
196 .await
197 .map_err(|e| Error::Storage {
198 value: e,
199 #[cfg(feature = "_test")]
200 backtrace: std::backtrace::Backtrace::capture(),
201 })?;
202 let start_marked = buffer[..S::READ_SIZE]
203 .iter()
204 .map(|marker_byte| marker_byte.count_zeros())
205 .sum::<u32>()
206 >= HALF_MARKER_BITS;
207
208 flash
209 .read(
210 page_address + (S::ERASE_SIZE - S::READ_SIZE) as u32,
211 &mut buffer[..S::READ_SIZE],
212 )
213 .await
214 .map_err(|e| Error::Storage {
215 value: e,
216 #[cfg(feature = "_test")]
217 backtrace: std::backtrace::Backtrace::capture(),
218 })?;
219 let end_marked = buffer[..S::READ_SIZE]
220 .iter()
221 .map(|marker_byte| marker_byte.count_zeros())
222 .sum::<u32>()
223 >= HALF_MARKER_BITS;
224
225 let discovered_state = match (start_marked, end_marked) {
226 (true, true) => PageState::Closed,
227 (true, false) => PageState::PartialOpen,
228 (false, true) => {
230 return Err(Error::Corrupted {
231 #[cfg(feature = "_test")]
232 backtrace: std::backtrace::Backtrace::capture(),
233 });
234 }
235 (false, false) => PageState::Open,
236 };
237
238 cache.notice_page_state(page_index, discovered_state, false);
240
241 Ok(discovered_state)
242}
243
244async fn open_page<S: NorFlash>(
246 flash: &mut S,
247 flash_range: Range<u32>,
248 cache: &mut impl PrivateCacheImpl,
249 page_index: usize,
250) -> Result<(), Error<S::Error>> {
251 cache.notice_page_state(page_index, PageState::Open, true);
252
253 flash
254 .erase(
255 calculate_page_address::<S>(flash_range.clone(), page_index),
256 calculate_page_end_address::<S>(flash_range.clone(), page_index),
257 )
258 .await
259 .map_err(|e| Error::Storage {
260 value: e,
261 #[cfg(feature = "_test")]
262 backtrace: std::backtrace::Backtrace::capture(),
263 })?;
264
265 Ok(())
266}
267
268async fn close_page<S: NorFlash>(
270 flash: &mut S,
271 flash_range: Range<u32>,
272 cache: &mut impl PrivateCacheImpl,
273 page_index: usize,
274) -> Result<(), Error<S::Error>> {
275 let current_state =
276 partial_close_page::<S>(flash, flash_range.clone(), cache, page_index).await?;
277
278 if current_state != PageState::PartialOpen {
279 return Ok(());
280 }
281
282 cache.notice_page_state(page_index, PageState::Closed, true);
283
284 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
285 flash
287 .write(
288 calculate_page_end_address::<S>(flash_range, page_index) - S::WORD_SIZE as u32,
289 &buffer[..S::WORD_SIZE],
290 )
291 .await
292 .map_err(|e| Error::Storage {
293 value: e,
294 #[cfg(feature = "_test")]
295 backtrace: std::backtrace::Backtrace::capture(),
296 })?;
297
298 Ok(())
299}
300
301async fn partial_close_page<S: NorFlash>(
303 flash: &mut S,
304 flash_range: Range<u32>,
305 cache: &mut impl PrivateCacheImpl,
306 page_index: usize,
307) -> Result<PageState, Error<S::Error>> {
308 let current_state = get_page_state::<S>(flash, flash_range.clone(), cache, page_index).await?;
309
310 if current_state != PageState::Open {
311 return Ok(current_state);
312 }
313
314 let new_state = match current_state {
315 PageState::Closed => PageState::Closed,
316 PageState::PartialOpen => PageState::PartialOpen,
317 PageState::Open => PageState::PartialOpen,
318 };
319
320 cache.notice_page_state(page_index, new_state, true);
321
322 let buffer = AlignedBuf([MARKER; MAX_WORD_SIZE]);
323 flash
325 .write(
326 calculate_page_address::<S>(flash_range, page_index),
327 &buffer[..S::WORD_SIZE],
328 )
329 .await
330 .map_err(|e| Error::Storage {
331 value: e,
332 #[cfg(feature = "_test")]
333 backtrace: std::backtrace::Backtrace::capture(),
334 })?;
335
336 Ok(new_state)
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
342enum PageState {
343 Closed,
345 PartialOpen,
347 Open,
349}
350
351#[allow(dead_code)]
352impl PageState {
353 #[must_use]
357 fn is_closed(&self) -> bool {
358 matches!(self, Self::Closed)
359 }
360
361 #[must_use]
365 fn is_partial_open(&self) -> bool {
366 matches!(self, Self::PartialOpen)
367 }
368
369 #[must_use]
373 fn is_open(&self) -> bool {
374 matches!(self, Self::Open)
375 }
376}
377
378#[non_exhaustive]
380#[derive(Debug)]
381#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
382pub enum Error<S> {
383 Storage {
385 value: S,
387 #[cfg(feature = "_test")]
388 backtrace: std::backtrace::Backtrace,
390 },
391 FullStorage,
393 Corrupted {
396 #[cfg(feature = "_test")]
397 backtrace: std::backtrace::Backtrace,
399 },
400 BufferTooBig,
402 BufferTooSmall(usize),
404 SerializationError(SerializationError),
406 ItemTooBig,
411}
412
413impl<S> From<SerializationError> for Error<S> {
414 fn from(v: SerializationError) -> Self {
415 Self::SerializationError(v)
416 }
417}
418
419impl<S: PartialEq> PartialEq for Error<S> {
420 fn eq(&self, other: &Self) -> bool {
421 match (self, other) {
422 (Self::Storage { value: l_value, .. }, Self::Storage { value: r_value, .. }) => {
423 l_value == r_value
424 }
425 (Self::BufferTooSmall(l0), Self::BufferTooSmall(r0)) => l0 == r0,
426 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
427 }
428 }
429}
430
431impl<S> core::fmt::Display for Error<S>
432where
433 S: core::fmt::Display,
434{
435 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
436 match self {
437 Error::Storage { value, .. } => write!(f, "Storage error: {value}"),
438 Error::FullStorage => write!(f, "Storage is full"),
439 #[cfg(not(feature = "_test"))]
440 Error::Corrupted { .. } => write!(f, "Storage is corrupted"),
441 #[cfg(feature = "_test")]
442 Error::Corrupted { backtrace } => write!(f, "Storage is corrupted\n{backtrace}"),
443 Error::BufferTooBig => write!(f, "A provided buffer was to big to be used"),
444 Error::BufferTooSmall(needed) => write!(
445 f,
446 "A provided buffer was to small to be used. Needed was {needed}"
447 ),
448 Error::SerializationError(value) => write!(f, "Map value error: {value}"),
449 Error::ItemTooBig => write!(f, "The item is too big to fit in the flash"),
450 }
451 }
452}
453
454impl<S> core::error::Error for Error<S> where S: core::fmt::Display + core::fmt::Debug {}
455
456const fn round_up_to_alignment<S: NorFlash>(value: u32) -> u32 {
459 let alignment = S::WORD_SIZE as u32;
460 match value % alignment {
461 0 => value,
462 r => value + (alignment - r),
463 }
464}
465
466const fn round_up_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
469 round_up_to_alignment::<S>(value as u32) as usize
470}
471
472const fn round_down_to_alignment<S: NorFlash>(value: u32) -> u32 {
475 let alignment = S::WORD_SIZE as u32;
476 (value / alignment) * alignment
477}
478
479const fn round_down_to_alignment_usize<S: NorFlash>(value: usize) -> usize {
482 round_down_to_alignment::<S>(value as u32) as usize
483}
484
485trait NorFlashExt {
487 const WORD_SIZE: usize;
489}
490
491impl<S: NorFlash> NorFlashExt for S {
492 const WORD_SIZE: usize = {
493 assert_read_write_sizes(Self::WRITE_SIZE, Self::READ_SIZE);
494
495 if Self::WRITE_SIZE > Self::READ_SIZE {
496 Self::WRITE_SIZE
497 } else {
498 Self::READ_SIZE
499 }
500 };
501}
502
503#[track_caller]
504const fn assert_read_write_sizes(write_size: usize, read_size: usize) {
505 assert!(
506 write_size.is_multiple_of(read_size) || read_size.is_multiple_of(write_size),
507 "Only flash with read and write sizes that are multiple of each other are supported"
508 );
509}
510
511macro_rules! run_with_auto_repair {
512 (function = $function:expr, repair = $repair_function:expr) => {
513 match $function {
514 Err(Error::Corrupted {
515 #[cfg(feature = "_test")]
516 backtrace: _backtrace,
517 ..
518 }) => {
519 #[cfg(all(feature = "_test", fuzzing_repro))]
520 eprintln!(
521 "### Encountered curruption! Repairing now. Originated from:\n{_backtrace:#}"
522 );
523 $repair_function;
524 $function
525 }
526 val => val,
527 }
528 };
529}
530
531pub(crate) use run_with_auto_repair;
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use futures_test::test;
537
538 type MockFlash = mock_flash::MockFlashBase<4, 4, 64>;
539
540 async fn write_aligned(
541 flash: &mut MockFlash,
542 offset: u32,
543 bytes: &[u8],
544 ) -> Result<(), mock_flash::MockFlashError> {
545 let mut buf = AlignedBuf([0; 256]);
546 buf[..bytes.len()].copy_from_slice(bytes);
547 flash.write(offset, &buf[..bytes.len()]).await
548 }
549
550 #[test]
551 async fn test_find_pages() {
552 let mut flash = MockFlash::default();
559 write_aligned(&mut flash, 0x000, &[MARKER, 0, 0, 0])
561 .await
562 .unwrap();
563 write_aligned(&mut flash, 0x100 - 4, &[0, 0, 0, MARKER])
564 .await
565 .unwrap();
566 write_aligned(&mut flash, 0x100, &[MARKER, 0, 0, 0])
568 .await
569 .unwrap();
570 write_aligned(&mut flash, 0x200 - 4, &[0, 0, 0, MARKER])
571 .await
572 .unwrap();
573 write_aligned(&mut flash, 0x200, &[MARKER, 0, 0, 0])
575 .await
576 .unwrap();
577
578 assert_eq!(
579 find_first_page(
580 &mut flash,
581 0x000..0x400,
582 &mut cache::NoCache::new(),
583 0,
584 PageState::Open
585 )
586 .await
587 .unwrap(),
588 Some(3)
589 );
590 assert_eq!(
591 find_first_page(
592 &mut flash,
593 0x000..0x400,
594 &mut cache::NoCache::new(),
595 0,
596 PageState::PartialOpen
597 )
598 .await
599 .unwrap(),
600 Some(2)
601 );
602 assert_eq!(
603 find_first_page(
604 &mut flash,
605 0x000..0x400,
606 &mut cache::NoCache::new(),
607 1,
608 PageState::PartialOpen
609 )
610 .await
611 .unwrap(),
612 Some(2)
613 );
614 assert_eq!(
615 find_first_page(
616 &mut flash,
617 0x000..0x400,
618 &mut cache::NoCache::new(),
619 2,
620 PageState::PartialOpen
621 )
622 .await
623 .unwrap(),
624 Some(2)
625 );
626 assert_eq!(
627 find_first_page(
628 &mut flash,
629 0x000..0x400,
630 &mut cache::NoCache::new(),
631 3,
632 PageState::Open
633 )
634 .await
635 .unwrap(),
636 Some(3)
637 );
638 assert_eq!(
639 find_first_page(
640 &mut flash,
641 0x000..0x200,
642 &mut cache::NoCache::new(),
643 0,
644 PageState::PartialOpen
645 )
646 .await
647 .unwrap(),
648 None
649 );
650
651 assert_eq!(
652 find_first_page(
653 &mut flash,
654 0x000..0x400,
655 &mut cache::NoCache::new(),
656 0,
657 PageState::Closed
658 )
659 .await
660 .unwrap(),
661 Some(0)
662 );
663 assert_eq!(
664 find_first_page(
665 &mut flash,
666 0x000..0x400,
667 &mut cache::NoCache::new(),
668 1,
669 PageState::Closed
670 )
671 .await
672 .unwrap(),
673 Some(1)
674 );
675 assert_eq!(
676 find_first_page(
677 &mut flash,
678 0x000..0x400,
679 &mut cache::NoCache::new(),
680 2,
681 PageState::Closed
682 )
683 .await
684 .unwrap(),
685 Some(0)
686 );
687 assert_eq!(
688 find_first_page(
689 &mut flash,
690 0x000..0x400,
691 &mut cache::NoCache::new(),
692 3,
693 PageState::Closed
694 )
695 .await
696 .unwrap(),
697 Some(0)
698 );
699 assert_eq!(
700 find_first_page(
701 &mut flash,
702 0x200..0x400,
703 &mut cache::NoCache::new(),
704 0,
705 PageState::Closed
706 )
707 .await
708 .unwrap(),
709 None
710 );
711 }
712
713 #[test]
714 async fn read_write_sizes() {
715 assert_read_write_sizes(1, 1);
716 assert_read_write_sizes(1, 4);
717 assert_read_write_sizes(4, 4);
718 assert_read_write_sizes(4, 1);
719 }
720}