Skip to main content

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.
69///
70/// This only implements traits from [`embedded_storage`].
71/// If you need to use this struct where traits from [`embedded_storage_async`] are needed, you can
72/// use [`embassy_embedded_hal::adapter::BlockingAsync`] or
73/// [`embassy_embedded_hal::adapter::YieldingAsync`] wrappers.
74///
75/// [`embedded_storage`]: https://docs.rs/embedded-storage/latest/embedded_storage/
76/// [`embedded_storage_async`]: https://docs.rs/embedded-storage-async/latest/embedded_storage_async/
77/// [`embassy_embedded_hal::adapter::BlockingAsync`]: https://docs.rs/embassy-embedded-hal/latest/embassy_embedded_hal/adapter/struct.BlockingAsync.html
78/// [`embassy_embedded_hal::adapter::YieldingAsync`]: https://docs.rs/embassy-embedded-hal/latest/embassy_embedded_hal/adapter/struct.YieldingAsync.html
79pub struct FlashStorage<'d> {
80    pub(crate) capacity: usize,
81    unlocked: bool,
82    #[cfg(multi_core)]
83    pub(crate) multi_core_strategy: MultiCoreStrategy,
84    _flash: Flash<'d>,
85}
86
87impl<'d> FlashStorage<'d> {
88    /// Flash word size in bytes.
89    pub const WORD_SIZE: u32 = 4;
90    /// Flash sector size in bytes.
91    pub const SECTOR_SIZE: u32 = 4096;
92    /// Flash block size in bytes.
93    pub const BLOCK_SIZE: u32 = 65536;
94
95    /// Create a new flash storage instance.
96    ///
97    /// # Panics
98    ///
99    /// Panics if called more than once.
100    pub fn new(flash: Flash<'d>) -> Self {
101        // get the flash size from the bootloader
102        // we might want to find a better way for this (e.g. read the chip ID)
103        // since this assumed the ESP-IDF bootloader
104        const ADDR: u32 = if cfg!(feature = "esp32c5") {
105            0x2000
106        } else if cfg!(any(feature = "esp32", feature = "esp32s2")) {
107            0x1000
108        } else {
109            0x0000
110        };
111
112        let mut storage = Self {
113            capacity: 0,
114            unlocked: false,
115            #[cfg(multi_core)]
116            multi_core_strategy: MultiCoreStrategy::Error,
117            _flash: flash,
118        };
119
120        let mut buffer = crate::buffer::FlashWordBuffer::uninit();
121        storage.internal_read(ADDR, buffer.as_bytes_mut()).unwrap();
122
123        let buffer = unsafe { buffer.assume_init_bytes() };
124        let mb = match buffer[3] & 0xf0 {
125            0x00 => 1,
126            0x10 => 2,
127            0x20 => 4,
128            0x30 => 8,
129            0x40 => 16,
130            0x50 => 32,
131            _ => 0,
132        };
133        storage.capacity = mb * 1024 * 1024;
134
135        storage
136    }
137
138    #[inline(always)]
139    pub(crate) fn check_alignment<const ALIGN: u32>(
140        &self,
141        offset: u32,
142        length: usize,
143    ) -> Result<(), FlashStorageError> {
144        let offset = offset as usize;
145        if offset % ALIGN as usize != 0 || length % ALIGN as usize != 0 {
146            return Err(FlashStorageError::NotAligned);
147        }
148        Ok(())
149    }
150
151    #[inline(always)]
152    pub(crate) fn check_bounds(&self, offset: u32, length: usize) -> Result<(), FlashStorageError> {
153        let offset = offset as usize;
154        if length > self.capacity || offset > self.capacity - length {
155            return Err(FlashStorageError::OutOfBounds);
156        }
157        Ok(())
158    }
159
160    pub(crate) fn internal_read(
161        &mut self,
162        offset: u32,
163        bytes: &mut [MaybeUninit<u8>],
164    ) -> Result<(), FlashStorageError> {
165        check_rc(chip_specific::spiflash_read(
166            offset,
167            bytes.as_mut_ptr() as *mut u32,
168            bytes.len() as u32,
169        ))
170    }
171
172    #[inline(always)]
173    fn unlock_once(&mut self) -> Result<(), FlashStorageError> {
174        if !self.unlocked {
175            if chip_specific::spiflash_unlock() != 0 {
176                return Err(FlashStorageError::CantUnlock);
177            }
178            self.unlocked = true;
179        }
180        Ok(())
181    }
182
183    pub(crate) fn internal_erase_sector(&mut self, sector: u32) -> Result<(), FlashStorageError> {
184        #[cfg(multi_core)]
185        let unpark = self.multi_core_strategy.pre_write()?;
186
187        self.unlock_once()?;
188        check_rc(chip_specific::spiflash_erase_sector(sector))?;
189
190        #[cfg(multi_core)]
191        self.multi_core_strategy.post_write(unpark);
192
193        Ok(())
194    }
195
196    pub(crate) fn internal_erase_block(&mut self, block: u32) -> Result<(), FlashStorageError> {
197        #[cfg(multi_core)]
198        let unpark = self.multi_core_strategy.pre_write()?;
199
200        self.unlock_once()?;
201        check_rc(chip_specific::spiflash_erase_block(block))?;
202
203        #[cfg(multi_core)]
204        self.multi_core_strategy.post_write(unpark);
205
206        Ok(())
207    }
208
209    pub(crate) fn internal_write(
210        &mut self,
211        offset: u32,
212        bytes: &[u8],
213    ) -> Result<(), FlashStorageError> {
214        #[cfg(multi_core)]
215        let unpark = self.multi_core_strategy.pre_write()?;
216
217        self.unlock_once()?;
218        check_rc(chip_specific::spiflash_write(
219            offset,
220            bytes.as_ptr() as *const u32,
221            bytes.len() as u32,
222        ))?;
223
224        #[cfg(multi_core)]
225        self.multi_core_strategy.post_write(unpark);
226
227        Ok(())
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))]
235#[cfg(multi_core)]
236pub(crate) enum MultiCoreStrategy {
237    /// Flash writes simply fail if the second core is active while attempting a write (default
238    /// behavior).
239    Error,
240    /// Auto park the other core before writing. Un-park it when writing is complete.
241    AutoPark,
242    /// Ignore that the other core is active.
243    /// This is useful if the second core is known to not fetch instructions from the flash for the
244    /// duration of the write. This is unsafe to use.
245    Ignore,
246}
247
248#[cfg(multi_core)]
249impl<'d> FlashStorage<'d> {
250    /// Enable auto parking of the second core before writing to flash.
251    /// The other core will be automatically un-parked when the write is complete.
252    pub fn multicore_auto_park(mut self) -> FlashStorage<'d> {
253        self.multi_core_strategy = MultiCoreStrategy::AutoPark;
254        self
255    }
256
257    /// Do not check if the second core is active before writing to flash.
258    ///
259    /// # Safety
260    /// Only enable this if you are sure that the second core is not fetching instructions from the
261    /// flash during the write.
262    pub unsafe fn multicore_ignore(mut self) -> FlashStorage<'d> {
263        self.multi_core_strategy = MultiCoreStrategy::Ignore;
264        self
265    }
266}
267
268#[cfg(multi_core)]
269impl MultiCoreStrategy {
270    /// Perform checks/Prepare for a flash write according to the current strategy.
271    ///
272    /// # Returns
273    /// * `True` if the other core needs to be un-parked by post_write
274    /// * `False` otherwise
275    pub(crate) fn pre_write(&self) -> Result<bool, FlashStorageError> {
276        let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
277        match self {
278            MultiCoreStrategy::Error => {
279                for other_cpu in Cpu::other() {
280                    if is_running(other_cpu) {
281                        return Err(FlashStorageError::OtherCoreRunning);
282                    }
283                }
284                Ok(false)
285            }
286            MultiCoreStrategy::AutoPark => {
287                for other_cpu in Cpu::other() {
288                    if is_running(other_cpu) {
289                        unsafe { cpu_ctrl.park_core(other_cpu) };
290                        return Ok(true);
291                    }
292                }
293                Ok(false)
294            }
295            MultiCoreStrategy::Ignore => Ok(false),
296        }
297    }
298
299    /// Perform post-write actions.
300    ///
301    /// # Returns
302    /// * `True` if the other core needs to be un-parked by post_write
303    /// * `False` otherwise
304    pub(crate) fn post_write(&self, unpark: bool) {
305        let mut cpu_ctrl = CpuControl::new(unsafe { CPU_CTRL::steal() });
306        if let MultiCoreStrategy::AutoPark = self {
307            if unpark {
308                for other_cpu in Cpu::other() {
309                    cpu_ctrl.unpark_core(other_cpu);
310                }
311            }
312        }
313    }
314}