Skip to main content

esp_storage/
common.rs

1use core::mem::MaybeUninit;
2
3#[cfg(not(feature = "emulation"))]
4pub use esp_hal::peripherals::FLASH as Flash;
5
6#[cfg(multi_core)]
7esp_hal::if_unstable_hal! {
8    use esp_hal::peripherals::CPU_CTRL;
9    use esp_hal::system::Cpu;
10    use esp_hal::system::CpuControl;
11    use esp_hal::system::is_running;
12}
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    /// Operation not supported (e.g. no free MMU entry).
31    NotSupported,
32    /// Cannot write to flash as more than one core is running.
33    /// Either manually suspend the other core, or use one of the available strategies:
34    /// * [`FlashStorage::multicore_auto_park`]
35    /// * [`FlashStorage::multicore_ignore`]
36    #[cfg(multi_core)]
37    OtherCoreRunning,
38    /// Other error with the given error code.
39    Other(i32),
40}
41
42#[inline(always)]
43/// Check return code from flash operations.
44pub fn check_rc(rc: i32) -> Result<(), FlashStorageError> {
45    match rc {
46        0 => Ok(()),
47        1 => Err(FlashStorageError::IoError),
48        2 => Err(FlashStorageError::IoTimeout),
49        _ => Err(FlashStorageError::Other(rc)),
50    }
51}
52
53#[cfg(feature = "emulation")]
54#[derive(Debug)]
55pub struct Flash<'d> {
56    _phantom: core::marker::PhantomData<&'d ()>,
57}
58
59#[cfg(feature = "emulation")]
60impl<'d> Flash<'d> {
61    pub fn new() -> Self {
62        Flash {
63            _phantom: core::marker::PhantomData,
64        }
65    }
66}
67
68#[derive(Debug)]
69#[cfg_attr(feature = "defmt", derive(defmt::Format))]
70/// Flash storage abstraction.
71///
72/// This type can read and write any location on the SPI flash chip. For
73/// application data, it is recommended to reserve a dedicated
74#[cfg_attr(
75    not(feature = "emulation"),
76    doc = concat!(
77        "[partition](https://docs.espressif.com/projects/esp-idf/en/latest/",
78        esp_metadata_generated::chip!(),
79        "/api-guides/partition-tables.html)"
80    )
81)]
82#[cfg_attr(
83    feature = "emulation",
84    doc = "[partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html)"
85)]
86/// instead of writing to arbitrary addresses. For partition-table helpers, see
87/// `esp-bootloader-esp-idf`.
88///
89/// Use [`FlashStorage::write_nor`] and [`FlashStorage::erase`] for low-level
90/// NOR flash semantics, or [`FlashStorage::write`] for read-modify-write with
91/// automatic sector erasure.
92///
93/// See the [crate-level documentation](crate#buffer-alignment-and-stack-usage)
94/// for stack usage.
95pub struct FlashStorage<'d> {
96    pub(crate) capacity: usize,
97    unlocked: bool,
98    pub(crate) multi_core_strategy: MultiCoreStrategy,
99    _flash: Flash<'d>,
100}
101
102impl<'d> FlashStorage<'d> {
103    /// Flash word size in bytes.
104    pub const WORD_SIZE: u32 = 4;
105    /// Flash sector size in bytes.
106    pub const SECTOR_SIZE: u32 = 4096;
107    /// Flash block size in bytes.
108    pub const BLOCK_SIZE: u32 = 65536;
109
110    /// Create a new flash storage instance.
111    ///
112    /// # Panics
113    ///
114    /// Panics if called more than once.
115    pub fn new(flash: Flash<'d>) -> Self {
116        Self {
117            capacity: chip_specific::get_flash_size() as usize,
118            unlocked: false,
119            multi_core_strategy: cfg_select! {
120                multi_core => MultiCoreStrategy::Error,
121                _ => MultiCoreStrategy::Ignore,
122            },
123            _flash: flash,
124        }
125    }
126
127    #[inline(always)]
128    pub(crate) fn check_alignment<const ALIGN: u32>(
129        &self,
130        offset: u32,
131        length: usize,
132    ) -> Result<(), FlashStorageError> {
133        let offset = offset as usize;
134        if !offset.is_multiple_of(ALIGN as usize) || !length.is_multiple_of(ALIGN as usize) {
135            return Err(FlashStorageError::NotAligned);
136        }
137        Ok(())
138    }
139
140    #[inline(always)]
141    pub(crate) fn check_bounds(&self, offset: u32, length: usize) -> Result<(), FlashStorageError> {
142        let offset = offset as usize;
143        if length > self.capacity || offset > self.capacity - length {
144            return Err(FlashStorageError::OutOfBounds);
145        }
146        Ok(())
147    }
148
149    pub(crate) fn internal_read(
150        &mut self,
151        offset: u32,
152        bytes: &mut [MaybeUninit<u8>],
153    ) -> Result<(), FlashStorageError> {
154        check_rc(chip_specific::spiflash_read(
155            offset,
156            bytes.as_mut_ptr() as *mut u32,
157            bytes.len() as u32,
158        ))
159    }
160
161    #[inline(always)]
162    fn unlock_once(&mut self) -> Result<(), FlashStorageError> {
163        if !self.unlocked {
164            if chip_specific::spiflash_unlock() != 0 {
165                return Err(FlashStorageError::CantUnlock);
166            }
167            self.unlocked = true;
168        }
169        Ok(())
170    }
171
172    pub(crate) fn internal_erase_sector(&mut self, sector: u32) -> Result<(), FlashStorageError> {
173        self.multi_core_strategy.with(|| {
174            self.unlock_once()?;
175            check_rc(chip_specific::spiflash_erase_sector(sector))
176        })
177    }
178
179    pub(crate) fn internal_erase_block(&mut self, block: u32) -> Result<(), FlashStorageError> {
180        self.multi_core_strategy.with(|| {
181            self.unlock_once()?;
182            check_rc(chip_specific::spiflash_erase_block(block))
183        })
184    }
185
186    pub(crate) fn internal_write(
187        &mut self,
188        offset: u32,
189        bytes: &[u8],
190    ) -> Result<(), FlashStorageError> {
191        self.multi_core_strategy.with(|| {
192            self.unlock_once()?;
193            check_rc(chip_specific::spiflash_write(
194                offset,
195                bytes.as_ptr() as *const u32,
196                bytes.len() as u32,
197            ))
198        })
199    }
200
201    pub(crate) fn internal_read_encrypted(
202        &mut self,
203        offset: u32,
204        bytes: &mut [MaybeUninit<u8>],
205    ) -> Result<(), FlashStorageError> {
206        // SAFETY: `read_flash_encrypted` fully initializes every byte in `bytes`.
207        let initialized = unsafe {
208            core::slice::from_raw_parts_mut(bytes.as_mut_ptr().cast::<u8>(), bytes.len())
209        };
210        // Reading encrypted flash changes the flash MMU and the cache, which the other core must
211        // not use while this happens.
212        self.multi_core_strategy
213            .with(|| chip_specific::read_flash_encrypted(offset, initialized))
214    }
215
216    pub(crate) fn internal_write_encrypted(
217        &mut self,
218        offset: u32,
219        bytes: &[u8],
220    ) -> Result<(), FlashStorageError> {
221        self.multi_core_strategy.with(|| {
222            check_rc(chip_specific::spiflash_write_encrypted(
223                offset,
224                bytes.as_ptr() as *mut u32,
225                bytes.len() as u32,
226            ))
227        })
228    }
229}
230
231/// Strategy to use on a multi core system where writing to the flash needs exclusive access from
232/// one core.
233#[derive(Clone, Copy, PartialEq, Eq, Debug)]
234#[cfg_attr(feature = "defmt", derive(defmt::Format))]
235pub(crate) enum MultiCoreStrategy {
236    /// Flash writes simply fail if the second core is active while attempting a write (default
237    /// behavior).
238    #[cfg(multi_core)]
239    Error,
240
241    /// Auto park the other core before writing. Un-park it when writing is complete.
242    #[cfg(multi_core)]
243    AutoPark,
244
245    /// Ignore that the other core is active.
246    /// This is useful if the second core is known to not fetch instructions from the flash for the
247    /// duration of the write. This is unsafe to use.
248    Ignore,
249}
250
251#[cfg(multi_core)]
252impl<'d> FlashStorage<'d> {
253    /// Enable auto parking of the second core before writing to flash.
254    /// The other core will be automatically un-parked when the write is complete.
255    pub fn multicore_auto_park(mut self) -> FlashStorage<'d> {
256        self.multi_core_strategy = MultiCoreStrategy::AutoPark;
257        self
258    }
259
260    /// Do not check if the second core is active before writing to flash.
261    ///
262    /// # Safety
263    /// Only enable this if you are sure that the second core is not fetching instructions from the
264    /// flash during the write.
265    pub unsafe fn multicore_ignore(mut self) -> FlashStorage<'d> {
266        self.multi_core_strategy = MultiCoreStrategy::Ignore;
267        self
268    }
269}
270
271impl MultiCoreStrategy {
272    /// Perform checks/Prepare for a flash write according to the current strategy.
273    ///
274    /// # Returns
275    /// * `True` if the other core needs to be un-parked by post_write
276    /// * `False` otherwise
277    pub(crate) fn pre_write(&self) -> Result<bool, FlashStorageError> {
278        match self {
279            #[cfg(multi_core)]
280            MultiCoreStrategy::Error => {
281                esp_hal::if_unstable_hal! {
282                    for other_cpu in Cpu::other() {
283                        if is_running(other_cpu) {
284                            return Err(FlashStorageError::OtherCoreRunning);
285                        }
286                    }
287                }
288                Ok(false)
289            }
290
291            #[cfg(multi_core)]
292            MultiCoreStrategy::AutoPark => {
293                esp_hal::if_unstable_hal! {
294                    let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
295                    for other_cpu in Cpu::other() {
296                        if is_running(other_cpu) {
297                            unsafe { cpu_ctrl.park_core(other_cpu) };
298                            return Ok(true);
299                        }
300                    }
301                }
302                Ok(false)
303            }
304
305            MultiCoreStrategy::Ignore => Ok(false),
306        }
307    }
308
309    /// Perform post-write actions.
310    ///
311    /// # Returns
312    /// * `True` if the other core needs to be un-parked by post_write
313    /// * `False` otherwise
314    pub(crate) fn post_write(&self, unpark: bool) {
315        cfg_select! {
316            multi_core => {
317                if let MultiCoreStrategy::AutoPark = self
318                    && unpark
319                {
320                    esp_hal::if_unstable_hal! {
321                        let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
322                        for other_cpu in Cpu::other() {
323                            cpu_ctrl.unpark_core(other_cpu);
324                        }
325                    }
326                }
327            }
328            _ => {
329                let _ = unpark;
330            }
331        }
332    }
333
334    /// Run a flash write operation, handling multi-core synchronization.
335    ///
336    /// # Returns
337    /// * `Ok` with the result of the operation
338    /// * `Err` if the operation fails
339    pub(crate) fn with<R>(
340        self,
341        f: impl FnOnce() -> Result<R, FlashStorageError>,
342    ) -> Result<R, FlashStorageError> {
343        let unpark = self.pre_write()?;
344
345        let result = f();
346
347        self.post_write(unpark);
348        result
349    }
350}