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, nvmctrl::ctrlb::Cmdselect};
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        unsafe {
173            core::slice::from_raw_parts_mut(
174                Self::SEEPROM_ADDR as _,
175                self.virtual_size / core::mem::size_of::<TP>(),
176            )
177        }
178    }
179
180    /// Retrieves data stored in SmartEEPROM at `offset` location and copies it
181    /// to `buffer`.
182    ///
183    /// Note:
184    /// `offset_in_bytes == sizeof::<TP>() * offset`
185    pub fn get<TP: SmartEepromPointableSize>(&self, offset: usize, buffer: &mut [TP]) {
186        let slice = unsafe { self.get_slice() };
187        buffer
188            .iter_mut()
189            .zip(slice.iter().skip(offset))
190            .for_each(|(target, source)| {
191                wait_if_busy();
192                *target = *source
193            });
194    }
195
196    /// Returns an  iterator over SmartEEPROM address space.
197    pub fn iter<TP: SmartEepromPointableSize>(&'a self) -> SmartEepromIter<'a, TP> {
198        SmartEepromIter {
199            iter: unsafe { self.get_slice().iter() },
200        }
201    }
202}
203
204/// Trait generalizing over primitive types that are permitted to be used as
205/// slice backing types
206pub trait SmartEepromPointableSize: Sealed + Copy {}
207
208impl SmartEepromPointableSize for u8 {}
209impl SmartEepromPointableSize for u16 {}
210impl SmartEepromPointableSize for u32 {}
211
212impl<'a> SmartEeprom<'a, Unlocked> {
213    /// Returns a mutable slice to SmartEEPROM mapped address space.
214    ///
215    /// [`Underlying pointed type`](SmartEepromPointableSize) can be either
216    /// [`u8`], [`u16`] or [`u32`].
217    ///
218    /// # Safety
219    ///
220    /// `Nvmctrl.SEESTAT.BUSY` register must be 0 before memory access can be
221    /// performed.
222    pub unsafe fn get_mut_slice<TP: SmartEepromPointableSize>(&mut self) -> &mut [TP] {
223        unsafe {
224            core::slice::from_raw_parts_mut(
225                Self::SEEPROM_ADDR as _,
226                self.virtual_size / core::mem::size_of::<TP>(),
227            )
228        }
229    }
230
231    /// Copies data in a `buffer` to SmartEEPROM at `offset` location
232    ///
233    /// Note:
234    /// `offset_in_bytes == sizeof::<TP>() * offset`
235    pub fn set<TP: SmartEepromPointableSize>(&mut self, offset: usize, buffer: &[TP]) {
236        let slice = unsafe { self.get_mut_slice() };
237        buffer
238            .iter()
239            .zip(slice.iter_mut().skip(offset))
240            .for_each(|(source, target)| {
241                wait_if_busy();
242                *target = *source
243            });
244    }
245
246    /// Returns a mutable iterator over SmartEEPROM address space.
247    pub fn iter_mut<TP: SmartEepromPointableSize>(&'a mut self) -> SmartEepromIterMut<'a, TP> {
248        SmartEepromIterMut {
249            iter: unsafe { self.get_mut_slice().iter_mut() },
250        }
251    }
252
253    /// Locks SmartEEPROM, allowing only to perform read operations
254    pub fn lock(self) -> SmartEeprom<'a, Locked> {
255        // Panic case should never happen as we wait for STATUS.READY
256        self.nvm
257            .command_sync(Cmdselect::Lsee)
258            .expect("SmartEEPROM locking failed");
259        let Self {
260            nvm, virtual_size, ..
261        } = self;
262        SmartEeprom {
263            nvm,
264            virtual_size,
265            __: PhantomData,
266        }
267    }
268}
269
270impl<'a> SmartEeprom<'a, Locked> {
271    /// Unlocks SmartEEPROM, allowing to perform both read and write operations
272    pub fn unlock(self) -> SmartEeprom<'a, Unlocked> {
273        // Panic case should never happen as we wait for STATUS.READY
274        self.nvm
275            .command_sync(Cmdselect::Usee)
276            .expect("SmartEEPROM unlocking failed");
277        let Self {
278            nvm, virtual_size, ..
279        } = self;
280        SmartEeprom {
281            nvm,
282            virtual_size,
283            __: PhantomData,
284        }
285    }
286}
287
288/// A type representing an immutable iterator over SmartEEPROM address space
289pub struct SmartEepromIter<'a, TP: SmartEepromPointableSize> {
290    iter: core::slice::Iter<'a, TP>,
291}
292
293impl<'a, TP: SmartEepromPointableSize> Iterator for SmartEepromIter<'a, TP> {
294    type Item = &'a TP;
295    fn next(&mut self) -> Option<Self::Item> {
296        wait_if_busy();
297        self.iter.next()
298    }
299}
300
301impl<TP: SmartEepromPointableSize> DoubleEndedIterator for SmartEepromIter<'_, TP> {
302    fn next_back(&mut self) -> Option<Self::Item> {
303        wait_if_busy();
304        self.iter.next_back()
305    }
306}
307
308/// A type representing a mutable iterator over SmartEEPROM address space
309pub struct SmartEepromIterMut<'a, TP: SmartEepromPointableSize> {
310    iter: core::slice::IterMut<'a, TP>,
311}
312
313impl<'a, TP: SmartEepromPointableSize> Iterator for SmartEepromIterMut<'a, TP> {
314    type Item = &'a mut TP;
315    fn next(&mut self) -> Option<Self::Item> {
316        wait_if_busy();
317        self.iter.next()
318    }
319}
320
321impl<TP: SmartEepromPointableSize> DoubleEndedIterator for SmartEepromIterMut<'_, TP> {
322    fn next_back(&mut self) -> Option<Self::Item> {
323        wait_if_busy();
324        self.iter.next_back()
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    #[test]
332    fn test_if_virtual_size_is_mapped_correctly() {
333        // For some reason, doing
334        // `use SmartEepromMode::map_sblk_psz_to_virtual_size as f;`
335        // is not permitted - 🤷
336        fn f(sblk: u32, psz: u32) -> usize {
337            SmartEepromMode::map_sblk_psz_to_virtual_size((sblk, psz))
338        }
339        assert_eq!(f(1, 0), 512);
340        assert_eq!(f(1, 1), 1024);
341        assert_eq!(f(1, 2), 2048);
342        assert_eq!(f(1, 3), 4096);
343        assert_eq!(f(1, 4), 4096);
344        assert_eq!(f(1, 5), 4096);
345        assert_eq!(f(1, 6), 4096);
346        assert_eq!(f(1, 7), 4096);
347        assert_eq!(f(2, 0), 512);
348        assert_eq!(f(2, 1), 1024);
349        assert_eq!(f(2, 2), 2048);
350        assert_eq!(f(2, 3), 4096);
351        assert_eq!(f(2, 4), 8192);
352        assert_eq!(f(2, 5), 8192);
353        assert_eq!(f(2, 6), 8192);
354        assert_eq!(f(2, 7), 8192);
355        assert_eq!(f(3, 0), 512);
356        for i in 3..=4 {
357            assert_eq!(f(i, 1), 1024);
358            assert_eq!(f(i, 2), 2048);
359            assert_eq!(f(i, 3), 4096);
360            assert_eq!(f(i, 4), 8192);
361            assert_eq!(f(i, 5), 16384);
362            assert_eq!(f(i, 6), 16384);
363            assert_eq!(f(i, 7), 16384);
364        }
365        for i in 5..=8 {
366            assert_eq!(f(i, 0), 512);
367            assert_eq!(f(i, 1), 1024);
368            assert_eq!(f(i, 2), 2048);
369            assert_eq!(f(i, 3), 4096);
370            assert_eq!(f(i, 4), 8192);
371            assert_eq!(f(i, 5), 16384);
372            assert_eq!(f(i, 6), 32768);
373            assert_eq!(f(i, 7), 32768);
374        }
375        for i in 9..=10 {
376            assert_eq!(f(i, 0), 512);
377            assert_eq!(f(i, 1), 1024);
378            assert_eq!(f(i, 2), 2048);
379            assert_eq!(f(i, 3), 4096);
380            assert_eq!(f(i, 4), 8192);
381            assert_eq!(f(i, 5), 16384);
382            assert_eq!(f(i, 6), 32768);
383            assert_eq!(f(i, 7), 65536);
384        }
385    }
386}