esp_storage/
common.rs

1use core::mem::MaybeUninit;
2
3#[cfg(multi_core)]
4use esp_hal::peripherals::CPU_CTRL;
5#[cfg(not(feature = "emulation"))]
6pub use esp_hal::peripherals::FLASH as Flash;
7#[cfg(multi_core)]
8use esp_hal::system::Cpu;
9#[cfg(multi_core)]
10use esp_hal::system::CpuControl;
11#[cfg(multi_core)]
12use esp_hal::system::is_running;
13
14use crate::chip_specific;
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18/// Flash storage error.
19pub enum FlashStorageError {
20    /// I/O error.
21    IoError,
22    /// I/O operation timed out.
23    IoTimeout,
24    /// Flash could not be unlocked for writing.
25    CantUnlock,
26    /// Address or length not aligned to required boundary.
27    NotAligned,
28    /// Address or length out of bounds.
29    OutOfBounds,
30    /// Cannot write to flash as more than one core is running.
31    /// Either manually suspend the other core, or use one of the available strategies:
32    /// * [`FlashStorage::multicore_auto_park`]
33    /// * [`FlashStorage::multicore_ignore`]
34    #[cfg(multi_core)]
35    OtherCoreRunning,
36    /// Other error with the given error code.
37    Other(i32),
38}
39
40#[inline(always)]
41/// Check return code from flash operations.
42pub fn check_rc(rc: i32) -> Result<(), FlashStorageError> {
43    match rc {
44        0 => Ok(()),
45        1 => Err(FlashStorageError::IoError),
46        2 => Err(FlashStorageError::IoTimeout),
47        _ => Err(FlashStorageError::Other(rc)),
48    }
49}
50
51#[cfg(feature = "emulation")]
52#[derive(Debug)]
53pub struct Flash<'d> {
54    _phantom: core::marker::PhantomData<&'d ()>,
55}
56
57#[cfg(feature = "emulation")]
58impl<'d> Flash<'d> {
59    pub fn new() -> Self {
60        Flash {
61            _phantom: core::marker::PhantomData,
62        }
63    }
64}
65
66#[derive(Debug)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68/// Flash storage abstraction.
69pub struct FlashStorage<'d> {
70    pub(crate) capacity: usize,
71    unlocked: bool,
72    #[cfg(multi_core)]
73    pub(crate) multi_core_strategy: MultiCoreStrategy,
74    _flash: Flash<'d>,
75}
76
77impl<'d> FlashStorage<'d> {
78    /// Flash word size in bytes.
79    pub const WORD_SIZE: u32 = 4;
80    /// Flash sector size in bytes.
81    pub const SECTOR_SIZE: u32 = 4096;
82
83    /// Create a new flash storage instance.
84    ///
85    /// # Panics
86    ///
87    /// Panics if called more than once.
88    pub fn new(flash: Flash<'d>) -> Self {
89        #[cfg(not(any(feature = "esp32", feature = "esp32s2")))]
90        const ADDR: u32 = 0x0000;
91        #[cfg(any(feature = "esp32", feature = "esp32s2"))]
92        const ADDR: u32 = 0x1000;
93
94        let mut storage = Self {
95            capacity: 0,
96            unlocked: false,
97            #[cfg(multi_core)]
98            multi_core_strategy: MultiCoreStrategy::Error,
99            _flash: flash,
100        };
101
102        let mut buffer = crate::buffer::FlashWordBuffer::uninit();
103        storage.internal_read(ADDR, buffer.as_bytes_mut()).unwrap();
104
105        let buffer = unsafe { buffer.assume_init_bytes() };
106        let mb = match buffer[3] & 0xf0 {
107            0x00 => 1,
108            0x10 => 2,
109            0x20 => 4,
110            0x30 => 8,
111            0x40 => 16,
112            0x50 => 32,
113            _ => 0,
114        };
115        storage.capacity = mb * 1024 * 1024;
116
117        storage
118    }
119
120    #[inline(always)]
121    pub(crate) fn check_alignment<const ALIGN: u32>(
122        &self,
123        offset: u32,
124        length: usize,
125    ) -> Result<(), FlashStorageError> {
126        let offset = offset as usize;
127        if offset % ALIGN as usize != 0 || length % ALIGN as usize != 0 {
128            return Err(FlashStorageError::NotAligned);
129        }
130        Ok(())
131    }
132
133    #[inline(always)]
134    pub(crate) fn check_bounds(&self, offset: u32, length: usize) -> Result<(), FlashStorageError> {
135        let offset = offset as usize;
136        if length > self.capacity || offset > self.capacity - length {
137            return Err(FlashStorageError::OutOfBounds);
138        }
139        Ok(())
140    }
141
142    pub(crate) fn internal_read(
143        &mut self,
144        offset: u32,
145        bytes: &mut [MaybeUninit<u8>],
146    ) -> Result<(), FlashStorageError> {
147        check_rc(chip_specific::spiflash_read(
148            offset,
149            bytes.as_mut_ptr() as *mut u32,
150            bytes.len() as u32,
151        ))
152    }
153
154    #[inline(always)]
155    fn unlock_once(&mut self) -> Result<(), FlashStorageError> {
156        if !self.unlocked {
157            if chip_specific::spiflash_unlock() != 0 {
158                return Err(FlashStorageError::CantUnlock);
159            }
160            self.unlocked = true;
161        }
162        Ok(())
163    }
164
165    pub(crate) fn internal_erase(&mut self, sector: u32) -> Result<(), FlashStorageError> {
166        #[cfg(multi_core)]
167        let unpark = self.multi_core_strategy.pre_write()?;
168
169        self.unlock_once()?;
170        check_rc(chip_specific::spiflash_erase_sector(sector))?;
171
172        #[cfg(multi_core)]
173        self.multi_core_strategy.post_write(unpark);
174
175        Ok(())
176    }
177
178    pub(crate) fn internal_write(
179        &mut self,
180        offset: u32,
181        bytes: &[u8],
182    ) -> Result<(), FlashStorageError> {
183        #[cfg(multi_core)]
184        let unpark = self.multi_core_strategy.pre_write()?;
185
186        self.unlock_once()?;
187        check_rc(chip_specific::spiflash_write(
188            offset,
189            bytes.as_ptr() as *const u32,
190            bytes.len() as u32,
191        ))?;
192
193        #[cfg(multi_core)]
194        self.multi_core_strategy.post_write(unpark);
195
196        Ok(())
197    }
198}
199
200/// Strategy to use on a multi core system where writing to the flash needs exclusive access from
201/// one core.
202#[derive(Clone, Copy, PartialEq, Eq, Debug)]
203#[cfg_attr(feature = "defmt", derive(defmt::Format))]
204#[cfg(multi_core)]
205pub(crate) enum MultiCoreStrategy {
206    /// Flash writes simply fail if the second core is active while attempting a write (default
207    /// behavior).
208    Error,
209    /// Auto park the other core before writing. Un-park it when writing is complete.
210    AutoPark,
211    /// Ignore that the other core is active.
212    /// This is useful if the second core is known to not fetch instructions from the flash for the
213    /// duration of the write. This is unsafe to use.
214    Ignore,
215}
216
217#[cfg(multi_core)]
218impl<'d> FlashStorage<'d> {
219    /// Enable auto parking of the second core before writing to flash.
220    /// The other core will be automatically un-parked when the write is complete.
221    pub fn multicore_auto_park(mut self) -> FlashStorage<'d> {
222        self.multi_core_strategy = MultiCoreStrategy::AutoPark;
223        self
224    }
225
226    /// Do not check if the second core is active before writing to flash.
227    ///
228    /// # Safety
229    /// Only enable this if you are sure that the second core is not fetching instructions from the
230    /// flash during the write.
231    pub unsafe fn multicore_ignore(mut self) -> FlashStorage<'d> {
232        self.multi_core_strategy = MultiCoreStrategy::Ignore;
233        self
234    }
235}
236
237#[cfg(multi_core)]
238impl MultiCoreStrategy {
239    /// Perform checks/Prepare for a flash write according to the current strategy.
240    ///
241    /// # Returns
242    /// * `True` if the other core needs to be un-parked by post_write
243    /// * `False` otherwise
244    pub(crate) fn pre_write(&self) -> Result<bool, FlashStorageError> {
245        let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
246        match self {
247            MultiCoreStrategy::Error => {
248                for other_cpu in Cpu::other() {
249                    if is_running(other_cpu) {
250                        return Err(FlashStorageError::OtherCoreRunning);
251                    }
252                }
253                Ok(false)
254            }
255            MultiCoreStrategy::AutoPark => {
256                for other_cpu in Cpu::other() {
257                    if is_running(other_cpu) {
258                        unsafe { cpu_ctrl.park_core(other_cpu) };
259                        return Ok(true);
260                    }
261                }
262                Ok(false)
263            }
264            MultiCoreStrategy::Ignore => Ok(false),
265        }
266    }
267
268    /// Perform post-write actions.
269    ///
270    /// # Returns
271    /// * `True` if the other core needs to be un-parked by post_write
272    /// * `False` otherwise
273    pub(crate) fn post_write(&self, unpark: bool) {
274        let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
275        if let MultiCoreStrategy::AutoPark = self {
276            if unpark {
277                for other_cpu in Cpu::other() {
278                    cpu_ctrl.unpark_core(other_cpu);
279                }
280            }
281        }
282    }
283}