atsamd_hal/peripherals/nvm/
smart_eeprom.rs

1//! # SmartEEPROM
2//!
3//! SmartEEPROM is a feature of NVM controller that simulates a RAM-like memory
4//! within a flash. As bits in flash cannot switch from 0 to 1 because of its
5//! properties (whole page of memory has to erased and data has to be recopied),
6//! SmartEEPROM introduces an indirection mechanism that handles this issue via
7//! notion of virtual pages and it handles physical page reallocation and
8//! erasing automatically underneath.
9//!
10//! From a user perspective, SmartEEPROM behaves just like a piece of memory in
11//! RAM but it is non-volatile. Data does not get lost between resets/power
12//! cycles.
13//!
14//! From technical standpoint, NVM controller sacrifices last
15//! `2*8192*Nvmctrl.SEESTAT.SBLK` bytes of flash (in an inactive bank). Memory
16//! access through flash address space will cause HardFault. All accesses has to
17//! be conducted through SmartEEPROM specific address space.
18//!
19//! Prerequisites:
20//! Both `Nvmctrl.SEESTAT.{SBLK,PSZ}` (block size, virtual page size) are being
21//! populated from proper bits in NVM controller user page on power-on-reset. By
22//! default, `SBLK` property is set to `0`, effectively disabling SmartEEPROM.
23//!
24//! One of possible safe ways to change user page content is to use `OpenOCD`
25//! custom commands. `atsame5x`'s `OpenOCD` driver supports `atsame5 userpage`
26//! command. To access it from GDB, it has to be preceded with a `monitor`
27//! clause.
28//!
29//! To access [`SmartEeprom`] struct, call [`Nvm::smart_eeprom`] method to
30//! retrieve its instance.
31
32use core::marker::PhantomData;
33
34use super::Nvm;
35use crate::pac::{nvmctrl::ctrlb::Cmdselect, Nvmctrl};
36use crate::typelevel::Sealed;
37
38/// Struct representing a SmartEEPROM instance.
39///
40/// It is generic over:
41/// - a lifetime of a stored [`Nvm`] reference
42/// - current state ([`Locked`]/[`Unlocked`])
43pub struct SmartEeprom<'a, T: SmartEepromState> {
44    nvm: &'a mut Nvm,
45    virtual_size: usize,
46    __: PhantomData<T>,
47}
48
49/// Trait generalizing over a state of an SmartEEPROM
50pub trait SmartEepromState: Sealed {}
51
52/// Type-level enum variant representing a locked state of SmartEEPROM. In that
53/// state, only read operations are permitted
54pub enum Locked {}
55impl SmartEepromState for Locked {}
56impl Sealed for Locked {}
57/// Type-level enum variant representing an unlocked state of SmartEEPROM. In
58/// that state, both read and write operations are permitted
59pub enum Unlocked {}
60impl SmartEepromState for Unlocked {}
61impl Sealed for Unlocked {}
62
63/// Enum representing possible failure modes of SmartEEPROM while its state is
64/// being retrieved from HW registers.
65#[derive(Debug)]
66pub enum SmartEepromRetrievalFailure {
67    /// SmartEEPROM is disabled and user page is misconfigured. [`More details
68    /// in module-level documentation`](self).
69    Disabled,
70    /// Support for disabled automatic page reallocation is not implemented.
71    DisabledAutomaticPageReallocationNotSupported,
72    /// Support for buffered writes to NVM is not implemented.
73    BufferedWritesNotSupported,
74    /// `SBLK` must be in range `1..=10`. `SBLK` is represented by 4 bits in a
75    /// user page which means that it can be between `0` and `15`. Documentation
76    /// does not cover cases for `11..=15`, therefore API considers them
77    /// unsupported.
78    InvalidBlockCount {
79        /// Currently set, unsupported `SBLK` value.
80        sblk: u32,
81    },
82}
83
84/// Enum encapsulating different modes SmartEEPROM can be in.
85pub enum SmartEepromMode<'a> {
86    /// SmartEEPROM is locked
87    Locked(SmartEeprom<'a, Locked>),
88    /// SmartEEPROM is unlocked
89    Unlocked(SmartEeprom<'a, Unlocked>),
90}
91
92/// Type alias for locally used [`Result`] type.
93pub type Result<'a> = core::result::Result<SmartEepromMode<'a>, SmartEepromRetrievalFailure>;
94
95#[inline]
96fn wait_if_busy() {
97    // Workaround: Cannot access `Nvmctrl` through `self.nvm.nvm` because of double
98    // borrowing in iterator for [`SmartEeprom::set`]. This should be safe though.
99    let nvmctrl = unsafe { &*Nvmctrl::ptr() };
100    while nvmctrl.seestat().read().busy().bit_is_set() {}
101}
102
103impl<'a> SmartEepromMode<'a> {
104    /// Retrieve [`SmartEeprom`] instance using information found in relevant HW
105    /// registers.
106    pub(super) fn retrieve(nvm: &'a mut Nvm) -> Result<'a> {
107        use SmartEepromMode as Mode;
108        use SmartEepromRetrievalFailure::*;
109        if nvm.nvm.seecfg().read().aprdis().bit_is_set() {
110            return Err(DisabledAutomaticPageReallocationNotSupported);
111        }
112        if nvm.nvm.seecfg().read().wmode().is_buffered() {
113            return Err(BufferedWritesNotSupported);
114        }
115        let sblk = nvm.nvm.seestat().read().sblk().bits() as u32;
116        let psz = nvm.nvm.seestat().read().psz().bits() as u32;
117        let virtual_size = match (sblk, psz) {
118            (0, _) => return Err(Disabled),
119            (sblk @ 11..=u32::MAX, _) => return Err(InvalidBlockCount { sblk }),
120            (_, 8..=u32::MAX) => {
121                unreachable!("`Nvmctrl.SEESTAT.PSZ` value is represented with 3 bits in user page")
122            }
123            others => Self::map_sblk_psz_to_virtual_size(others),
124        };
125        if nvm.nvm.seestat().read().lock().bit_is_set() {
126            Ok(Mode::Locked(SmartEeprom {
127                nvm,
128                virtual_size,
129                __: PhantomData::<Locked>,
130            }))
131        } else {
132            Ok(Mode::Unlocked(SmartEeprom {
133                nvm,
134                virtual_size,
135                __: PhantomData::<Unlocked>,
136            }))
137        }
138    }
139
140    fn map_sblk_psz_to_virtual_size(sblk_psz_pair: (u32, u32)) -> usize {
141        match sblk_psz_pair {
142            (_, 0) => 512,
143            (_, 1) => 1024,
144            (_, 2) => 2048,
145            (_, 3) => 4096,
146            (1, _) => 4096,
147            (_, 4) => 8192,
148            (2, _) => 8192,
149            (_, 5) => 16384,
150            (3 | 4, _) => 16384,
151            (_, 6) => 32768,
152            (5..=8, _) => 32768,
153            (_, 7) => 65536,
154            _ => unreachable!(),
155        }
156    }
157}
158
159impl<'a, T: SmartEepromState> SmartEeprom<'a, T> {
160    const SEEPROM_ADDR: *mut usize = 0x44000000 as _;
161
162    /// Returns an immutable slice to SmartEEPROM mapped address space.
163    ///
164    /// [`Underlying pointed type`](SmartEepromPointableSize) can be either
165    /// [`u8`], [`u16`] or [`u32`].
166    ///
167    /// # Safety
168    ///
169    /// `Nvmctrl.SEESTAT.BUSY` register must be 0 before memory access can be
170    /// performed.
171    pub unsafe fn get_slice<TP: SmartEepromPointableSize>(&self) -> &[TP] {
172        core::slice::from_raw_parts_mut(
173            Self::SEEPROM_ADDR as _,
174            self.virtual_size / core::mem::size_of::<TP>(),
175        )
176    }
177
178    /// Retrieves data stored in SmartEEPROM at `offset` location and copies it
179    /// to `buffer`.
180    ///
181    /// Note:
182    /// `offset_in_bytes == sizeof::<TP>() * offset`
183    pub fn get<TP: SmartEepromPointableSize>(&self, offset: usize, buffer: &mut [TP]) {
184        let slice = unsafe { self.get_slice() };
185        buffer
186            .iter_mut()
187            .zip(slice.iter().skip(offset))
188            .for_each(|(target, source)| {
189                wait_if_busy();
190                *target = *source
191            });
192    }
193
194    /// Returns an  iterator over SmartEEPROM address space.
195    pub fn iter<TP: SmartEepromPointableSize>(&'a self) -> SmartEepromIter<'a, TP> {
196        SmartEepromIter {
197            iter: unsafe { self.get_slice().iter() },
198        }
199    }
200}
201
202/// Trait generalizing over primitive types that are permitted to be used as
203/// slice backing types
204pub trait SmartEepromPointableSize: Sealed + Copy {}
205
206impl SmartEepromPointableSize for u8 {}
207impl SmartEepromPointableSize for u16 {}
208impl SmartEepromPointableSize for u32 {}
209
210impl<'a> SmartEeprom<'a, Unlocked> {
211    /// Returns a mutable slice to SmartEEPROM mapped address space.
212    ///
213    /// [`Underlying pointed type`](SmartEepromPointableSize) can be either
214    /// [`u8`], [`u16`] or [`u32`].
215    ///
216    /// # Safety
217    ///
218    /// `Nvmctrl.SEESTAT.BUSY` register must be 0 before memory access can be
219    /// performed.
220    pub unsafe fn get_mut_slice<TP: SmartEepromPointableSize>(&mut self) -> &mut [TP] {
221        core::slice::from_raw_parts_mut(
222            Self::SEEPROM_ADDR as _,
223            self.virtual_size / core::mem::size_of::<TP>(),
224        )
225    }
226
227    /// Copies data in a `buffer` to SmartEEPROM at `offset` location
228    ///
229    /// Note:
230    /// `offset_in_bytes == sizeof::<TP>() * offset`
231    pub fn set<TP: SmartEepromPointableSize>(&mut self, offset: usize, buffer: &[TP]) {
232        let slice = unsafe { self.get_mut_slice() };
233        buffer
234            .iter()
235            .zip(slice.iter_mut().skip(offset))
236            .for_each(|(source, target)| {
237                wait_if_busy();
238                *target = *source
239            });
240    }
241
242    /// Returns a mutable iterator over SmartEEPROM address space.
243    pub fn iter_mut<TP: SmartEepromPointableSize>(&'a mut self) -> SmartEepromIterMut<'a, TP> {
244        SmartEepromIterMut {
245            iter: unsafe { self.get_mut_slice().iter_mut() },
246        }
247    }
248
249    /// Locks SmartEEPROM, allowing only to perform read operations
250    pub fn lock(self) -> SmartEeprom<'a, Locked> {
251        // Panic case should never happen as we wait for STATUS.READY
252        self.nvm
253            .command_sync(Cmdselect::Lsee)
254            .expect("SmartEEPROM locking failed");
255        let Self {
256            nvm, virtual_size, ..
257        } = self;
258        SmartEeprom {
259            nvm,
260            virtual_size,
261            __: PhantomData,
262        }
263    }
264}
265
266impl<'a> SmartEeprom<'a, Locked> {
267    /// Unlocks SmartEEPROM, allowing to perform both read and write operations
268    pub fn unlock(self) -> SmartEeprom<'a, Unlocked> {
269        // Panic case should never happen as we wait for STATUS.READY
270        self.nvm
271            .command_sync(Cmdselect::Usee)
272            .expect("SmartEEPROM unlocking failed");
273        let Self {
274            nvm, virtual_size, ..
275        } = self;
276        SmartEeprom {
277            nvm,
278            virtual_size,
279            __: PhantomData,
280        }
281    }
282}
283
284/// A type representing an immutable iterator over SmartEEPROM address space
285pub struct SmartEepromIter<'a, TP: SmartEepromPointableSize> {
286    iter: core::slice::Iter<'a, TP>,
287}
288
289impl<'a, TP: SmartEepromPointableSize> Iterator for SmartEepromIter<'a, TP> {
290    type Item = &'a TP;
291    fn next(&mut self) -> Option<Self::Item> {
292        wait_if_busy();
293        self.iter.next()
294    }
295}
296
297impl<TP: SmartEepromPointableSize> DoubleEndedIterator for SmartEepromIter<'_, TP> {
298    fn next_back(&mut self) -> Option<Self::Item> {
299        wait_if_busy();
300        self.iter.next_back()
301    }
302}
303
304/// A type representing a mutable iterator over SmartEEPROM address space
305pub struct SmartEepromIterMut<'a, TP: SmartEepromPointableSize> {
306    iter: core::slice::IterMut<'a, TP>,
307}
308
309impl<'a, TP: SmartEepromPointableSize> Iterator for SmartEepromIterMut<'a, TP> {
310    type Item = &'a mut TP;
311    fn next(&mut self) -> Option<Self::Item> {
312        wait_if_busy();
313        self.iter.next()
314    }
315}
316
317impl<TP: SmartEepromPointableSize> DoubleEndedIterator for SmartEepromIterMut<'_, TP> {
318    fn next_back(&mut self) -> Option<Self::Item> {
319        wait_if_busy();
320        self.iter.next_back()
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    #[test]
328    fn test_if_virtual_size_is_mapped_correctly() {
329        // For some reason, doing
330        // `use SmartEepromMode::map_sblk_psz_to_virtual_size as f;`
331        // is not permitted - 🤷
332        fn f(sblk: u32, psz: u32) -> usize {
333            SmartEepromMode::map_sblk_psz_to_virtual_size((sblk, psz))
334        }
335        assert_eq!(f(1, 0), 512);
336        assert_eq!(f(1, 1), 1024);
337        assert_eq!(f(1, 2), 2048);
338        assert_eq!(f(1, 3), 4096);
339        assert_eq!(f(1, 4), 4096);
340        assert_eq!(f(1, 5), 4096);
341        assert_eq!(f(1, 6), 4096);
342        assert_eq!(f(1, 7), 4096);
343        assert_eq!(f(2, 0), 512);
344        assert_eq!(f(2, 1), 1024);
345        assert_eq!(f(2, 2), 2048);
346        assert_eq!(f(2, 3), 4096);
347        assert_eq!(f(2, 4), 8192);
348        assert_eq!(f(2, 5), 8192);
349        assert_eq!(f(2, 6), 8192);
350        assert_eq!(f(2, 7), 8192);
351        assert_eq!(f(3, 0), 512);
352        for i in 3..=4 {
353            assert_eq!(f(i, 1), 1024);
354            assert_eq!(f(i, 2), 2048);
355            assert_eq!(f(i, 3), 4096);
356            assert_eq!(f(i, 4), 8192);
357            assert_eq!(f(i, 5), 16384);
358            assert_eq!(f(i, 6), 16384);
359            assert_eq!(f(i, 7), 16384);
360        }
361        for i in 5..=8 {
362            assert_eq!(f(i, 0), 512);
363            assert_eq!(f(i, 1), 1024);
364            assert_eq!(f(i, 2), 2048);
365            assert_eq!(f(i, 3), 4096);
366            assert_eq!(f(i, 4), 8192);
367            assert_eq!(f(i, 5), 16384);
368            assert_eq!(f(i, 6), 32768);
369            assert_eq!(f(i, 7), 32768);
370        }
371        for i in 9..=10 {
372            assert_eq!(f(i, 0), 512);
373            assert_eq!(f(i, 1), 1024);
374            assert_eq!(f(i, 2), 2048);
375            assert_eq!(f(i, 3), 4096);
376            assert_eq!(f(i, 4), 8192);
377            assert_eq!(f(i, 5), 16384);
378            assert_eq!(f(i, 6), 32768);
379            assert_eq!(f(i, 7), 65536);
380        }
381    }
382}