1
#![cfg_attr(docsrs, procmacros::doc_replace)]
2
//! # Internal SPI Flash (FLASH)
3
//!
4
//! ## Overview
5
//!
6
//! Provides blocking access to the attached SPI flash memory. The driver owns
7
//! the virtual [`FLASH`] peripheral.
8
//!
9
//! Write and erase are NOR flash operations: bits change only from 1 to 0
10
//! without an erase. The driver does not perform read-modify-write.
11
//! [`Flash::read`] and [`Flash::write`] take word slices (`&[u32]`) and a
12
//! 4-byte-aligned byte offset. The buffer must reside in DRAM.
13
//! [`Flash::erase`] operates on 4096-byte sectors.
14
//!
15
//! [`Flash::read_encrypted`] and [`Flash::write_encrypted`] provide transparent
16
//! flash encryption. Encrypted writes require a 16-byte-aligned address and a
17
//! length that is a multiple of 16 bytes (4 words). The destination must already
18
//! be erased, and bytes outside the requested range are not programmed.
19
//! Encrypted reads return plaintext if flash encryption is disabled.
20
//!
21
//! For more information, see the
22
#![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", chip!(), "/api-reference/peripherals/spi_flash/index.html)")]
23
//! ## Configuration
24
//!
25
//! On dual-core chips, [`Config`] selects the multi-core strategy (default:
26
//! automatically park the other core).
27
//!
28
//! ## Usage
29
//!
30
//! Construct [`Flash`] from the virtual [`FLASH`] peripheral. `offset` is a
31
//! flash byte address, not a word index. The driver does not implement traits
32
//! from `embedded-storage`; higher layers provide partition management and
33
//! storage trait implementations.
34
//!
35
//! ## Examples
36
//!
37
//! ### Read a word-aligned range
38
//!
39
//! ```rust, no_run
40
//! # {before_snippet}
41
//! use esp_hal::flash::{Config, Flash};
42
//!
43
//! let mut flash = Flash::new(peripherals.FLASH, Config::default())?;
44
//! let mut buf = [0u32; 8];
45
//! flash.read(0x10_020, &mut buf)?;
46
//! # {after_snippet}
47
//! ```
48
//!
49
//! ## Implementation State
50
#![cfg_attr(
51
    esp32,
52
    doc = "- On ESP32, a second-stage bootloader must identify the flash chip; the ROM does not.
53
  [`Flash::new`] fails with [`ConfigError::UnknownFlashChip`] if identification is missing
54
  (including the ROM placeholder ID `0x001540EF`, a valid W25Q16 ID on other chips)."
55
)]
56
//! - Driver bounds checks use the JEDEC density from the ROM-cached device ID. ROM operations also
57
//!   check the ROM-cached chip size, which a bootloader can set from the application image header.
58
//!   An operation within [`Flash`] capacity can still fail in the ROM if the ROM-cached size is
59
//!   smaller.
60
//! - Operations do not check whether the target flash range is mapped. Writing or erasing a mapped
61
//!   section (such as `.text` or `.rodata`) can corrupt running code or immutable data.
62
//! - Each [`Flash::read`] or [`Flash::write`] call executes a single ROM operation. The flash cache
63
//!   remains disabled (and the other core remains parked) for the entire transfer. Split large
64
//!   operations in higher-level code if latency is critical.
65
//! - On dual-core chips, the default strategy stalls the other core around every operation,
66
//!   including reads. The other core can freeze while holding a lock or executing an interrupt
67
//!   handler.
68

            
69
use core::marker::PhantomData;
70

            
71
use procmacros::{BuilderLite, ram};
72
#[cfg(xtensa)]
73
use xtensa_lx::interrupt::free;
74

            
75
#[cfg(not(xtensa))]
76
use crate::interrupt::free;
77
use crate::{Blocking, DriverMode, peripherals::FLASH, soc::is_slice_in_dram};
78

            
79
mod cache;
80
mod mmu;
81
mod rom;
82

            
83
/// Word size required by the read and write paths, in bytes.
84
const WORD_SIZE: u32 = 4;
85
/// Program page size in bytes.
86
const PAGE_SIZE: u32 = 256;
87
/// Erase sector size in bytes.
88
const SECTOR_SIZE: u32 = 4096;
89
/// Erase block size in bytes.
90
const BLOCK_SIZE: u32 = 65536;
91
/// Flash-encryption AES block size, and the public encrypted-write alignment.
92
const ENCRYPT_BLOCK_SIZE: u32 = 16;
93
/// ESP32 ROM encrypted-write row (two AES blocks that share a tweak).
94
#[cfg(esp32)]
95
const ESP32_ENCRYPT_ROW: u32 = 32;
96

            
97
/// Flash driver configuration.
98
#[instability::unstable]
99
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, BuilderLite)]
100
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
101
#[non_exhaustive]
102
pub struct Config {
103
    /// Strategy for the other core during flash operations.
104
    ///
105
    /// Default: [`MultiCoreStrategy::AutoPark`].
106
    ///
107
    /// [`MultiCoreStrategy::ignore`] is safe only when the other core cannot
108
    /// fetch from flash during the operation.
109
    #[cfg(multi_core)]
110
    #[builder_lite(unstable)]
111
    multi_core_strategy: MultiCoreStrategy,
112
}
113

            
114
/// Strategy for the other core during flash operations.
115
#[cfg(multi_core)]
116
#[instability::unstable]
117
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
118
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
119
#[non_exhaustive]
120
pub enum MultiCoreStrategy {
121
    /// Returns [`Error::OtherCoreRunning`] if the other core is running.
122
    Error,
123
    /// Parks the other core for the operation and unparks it afterward.
124
    ///
125
    /// The stall can freeze the other core at any instruction. The other core can hold a
126
    /// lock or execute an interrupt handler. Parking uses CPU-control
127
    /// registers even if the application already owns
128
    /// [`crate::peripherals::CPU_CTRL`].
129
    #[default]
130
    AutoPark,
131
    /// Does not check or park the other core.
132
    ///
133
    /// Construct with [`Self::ignore`].
134
    Ignore(IgnoreMarker),
135
}
136

            
137
/// Marker so [`MultiCoreStrategy::Ignore`] can only be built via
138
/// [`MultiCoreStrategy::ignore`].
139
#[cfg(multi_core)]
140
#[doc(hidden)]
141
#[instability::unstable]
142
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
144
pub struct IgnoreMarker {
145
    _private: (),
146
}
147

            
148
#[cfg(multi_core)]
149
impl MultiCoreStrategy {
150
    /// Creates a strategy that does not check or park the other core.
151
    ///
152
    /// # Safety
153
    ///
154
    /// The other core must not fetch instructions or data from flash during any
155
    /// operation, including reads. Flash-backed caches are unavailable while
156
    /// an operation runs.
157
    #[instability::unstable]
158
    pub const unsafe fn ignore() -> Self {
159
        Self::Ignore(IgnoreMarker { _private: () })
160
    }
161
}
162

            
163
/// Error constructing or configuring the flash driver.
164
#[instability::unstable]
165
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
167
#[non_exhaustive]
168
pub enum ConfigError {
169
    /// The attached flash chip cannot be identified, or its size is not
170
    /// recognized.
171
    ///
172
    /// This includes no-response JEDEC sentinels, an unrecognized density
173
    /// byte, and the ESP32 ROM placeholder ID `0x001540EF` (the ROM does not
174
    /// execute `RDID`; this value is a valid W25Q16 ID on other chips).
175
    UnknownFlashChip,
176
}
177

            
178
impl core::error::Error for ConfigError {}
179

            
180
impl core::fmt::Display for ConfigError {
181
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182
        match self {
183
            Self::UnknownFlashChip => {
184
                write!(
185
                    f,
186
                    "Flash chip could not be identified, or its size is not recognized"
187
                )
188
            }
189
        }
190
    }
191
}
192

            
193
/// Flash operation error.
194
#[instability::unstable]
195
3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
197
#[allow(clippy::enum_variant_names, reason = "matches ROM result / issue 6203")]
198
#[non_exhaustive]
199
pub enum Error {
200
    /// I/O error reported by the ROM.
201
    ///
202
    /// Includes a ROM-side size check against its cached chip size when that
203
    /// is smaller than [`Flash::capacity`].
204
    IoError,
205
    /// Operation timed out.
206
    IoTimeout,
207
    /// Address or length is not aligned for this operation.
208
    ///
209
    /// [`Flash::read`], [`Flash::read_encrypted`], and [`Flash::write`] require a
210
    /// 4-byte-aligned flash offset. [`Flash::write_encrypted`] requires a
211
    /// 16-byte address and a word count that is a multiple of 4 (16 bytes).
212
    /// [`Flash::erase`] requires a 4096-byte range.
213
    NotAligned,
214
    /// Address range exceeds [`Flash::capacity`], or `from > to`.
215
    ///
216
    /// Capacity is the JEDEC density, not the ROM cached chip size. An address
217
    /// range within this limit can still fail in the ROM if the cached size is
218
    /// smaller (often the image-header size on ESP32).
219
    OutOfBounds,
220
    /// Not supported in the current environment.
221
    ///
222
    /// Returned when the buffer is not in DRAM, when
223
    /// [`Flash::write_encrypted`] is called while flash encryption is disabled,
224
    /// or when [`Flash::read_encrypted`] cannot allocate an MMU entry.
225
    NotSupported,
226
    /// The other core is running and the configured strategy is
227
    /// [`MultiCoreStrategy::Error`].
228
    #[cfg(multi_core)]
229
    OtherCoreRunning,
230
    /// Unexpected ROM status code (logged).
231
    Unknown,
232
}
233

            
234
impl core::error::Error for Error {}
235

            
236
impl core::fmt::Display for Error {
237
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
238
        match self {
239
            Self::IoError => write!(f, "Flash I/O error"),
240
            Self::IoTimeout => write!(f, "Flash I/O timed out"),
241
            Self::NotAligned => write!(f, "Flash address or length is not aligned"),
242
            Self::OutOfBounds => write!(f, "Flash range is out of bounds"),
243
            Self::NotSupported => write!(f, "Flash operation is not supported"),
244
            #[cfg(multi_core)]
245
            Self::OtherCoreRunning => write!(f, "The other core is running"),
246
            Self::Unknown => write!(f, "Unknown flash error"),
247
        }
248
    }
249
}
250

            
251
/// Flash chip identification and geometry.
252
///
253
/// `chip_id` contains the manufacturer ID in bits 23:16. Geometry is fixed at
254
/// 256-byte pages, 4096-byte sectors, and 64 KiB blocks; it is not probed from
255
/// the chip.
256
#[instability::unstable]
257
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
258
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
259
#[non_exhaustive]
260
pub struct ChipInfo {
261
    /// JEDEC ID with manufacturer ID in bits 23:16.
262
    pub chip_id: u32,
263
    /// JEDEC-decoded physical capacity in bytes.
264
    ///
265
    /// See [`Flash::capacity`].
266
    pub capacity: usize,
267
    /// Erase sector size in bytes.
268
    pub sector_size: u32,
269
    /// Erase block size in bytes.
270
    pub block_size: u32,
271
    /// Program page size in bytes.
272
    pub page_size: u32,
273
}
274

            
275
/// Internal SPI flash driver.
276
///
277
/// Constructible only in [`Blocking`] mode.
278
#[instability::unstable]
279
#[derive(Debug)]
280
pub struct Flash<'d, Dm: DriverMode> {
281
    _flash: FLASH<'d>,
282
    capacity: usize,
283
    chip_id: u32,
284
    unlocked: bool,
285
    #[cfg(multi_core)]
286
    multi_core_strategy: MultiCoreStrategy,
287
    _mode: PhantomData<Dm>,
288
}
289

            
290
impl<'d> Flash<'d, Blocking> {
291
    /// Program page size in bytes.
292
    #[instability::unstable]
293
    pub const PAGE_SIZE: u32 = 256;
294
    /// Erase sector size in bytes.
295
    #[instability::unstable]
296
    pub const SECTOR_SIZE: u32 = 4096;
297
    /// Erase block size in bytes.
298
    #[instability::unstable]
299
    pub const BLOCK_SIZE: u32 = 65536;
300

            
301
    /// Creates a new flash driver from the [`FLASH`] peripheral.
302
    ///
303
    /// Capacity is determined from the JEDEC density in the ROM-cached device
304
    /// ID, not the image-header size or the ROM cached chip size used by
305
    /// read, write, and erase operations.
306
    ///
307
    /// # Errors
308
    ///
309
    /// - [`ConfigError::UnknownFlashChip`] if the attached flash chip cannot be identified or its
310
    ///   size is not recognized. On ESP32, this error also occurs when a second-stage bootloader
311
    ///   has not identified the chip (including the ROM placeholder ID `0x001540EF`).
312
    #[instability::unstable]
313
14
    pub fn new(flash: FLASH<'d>, config: Config) -> Result<Self, ConfigError> {
314
        let raw_id = rom::cached_device_id();
315
        let capacity = rom::capacity_from_cached_id(raw_id)?;
316
        let chip_id = raw_id & 0x00FF_FFFF;
317
        #[cfg(not(multi_core))]
318
        let _ = config;
319

            
320
14
        Ok(Self {
321
            _flash: flash,
322
            capacity,
323
            chip_id,
324
            unlocked: false,
325
            #[cfg(multi_core)]
326
            multi_core_strategy: config.multi_core_strategy,
327
            _mode: PhantomData,
328
        })
329
14
    }
330

            
331
    /// Applies a new configuration.
332
    ///
333
    /// Updates the multi-core strategy on dual-core chips. On single-core
334
    /// chips, this is a no-op. The return type is reserved for future
335
    /// configuration options.
336
    #[instability::unstable]
337
    pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
338
        #[cfg(multi_core)]
339
        {
340
            self.multi_core_strategy = config.multi_core_strategy;
341
        }
342
        let _ = config;
343
        Ok(())
344
    }
345

            
346
    /// Returns the JEDEC-decoded flash capacity in bytes.
347
    ///
348
    /// Driver bounds checks use this value. ROM read, write, and erase operations
349
    /// also check the ROM-cached chip size, which a bootloader can set from the
350
    /// application image header.
351
    #[instability::unstable]
352
    pub fn capacity(&self) -> usize {
353
        self.capacity
354
    }
355

            
356
    /// Returns chip identification and geometry.
357
    ///
358
    /// `chip_id` contains the manufacturer ID in bits 23:16. `capacity` is the
359
    /// JEDEC density; see [`Self::capacity`]. Geometry is fixed, not probed
360
    /// from the chip.
361
    #[instability::unstable]
362
    pub fn chip_info(&self) -> ChipInfo {
363
        ChipInfo {
364
            chip_id: self.chip_id,
365
            capacity: self.capacity,
366
            sector_size: SECTOR_SIZE,
367
            block_size: BLOCK_SIZE,
368
            page_size: PAGE_SIZE,
369
        }
370
    }
371

            
372
    /// Reads words into `data` starting at byte address `offset`.
373
    ///
374
    /// `offset` is a flash byte address and must be a multiple of 4. `data`
375
    /// must reside in DRAM, not in flash, IRAM, RTC memory, or PSRAM. An empty
376
    /// `data` slice only checks that `offset` is within [`Self::capacity`].
377
    ///
378
    /// # Errors
379
    ///
380
    /// - [`Error::NotAligned`] if `data` is non-empty and `offset` is not a multiple of 4.
381
    /// - [`Error::OutOfBounds`] if the range exceeds [`Self::capacity`].
382
    /// - [`Error::NotSupported`] if `data` is not in DRAM.
383
    #[cfg_attr(
384
        multi_core,
385
        doc = "- [`Error::OtherCoreRunning`] on dual-core chips with [`MultiCoreStrategy::Error`]."
386
    )]
387
    /// - [`Error::IoError`], [`Error::IoTimeout`], or [`Error::Unknown`] if the ROM reports an I/O
388
    ///   error.
389
    #[instability::unstable]
390
    #[ram]
391
548
    pub fn read(&mut self, offset: u32, data: &mut [u32]) -> Result<(), Error> {
392
        let Some(len) = byte_len(data) else {
393
            return Err(Error::OutOfBounds);
394
        };
395
548
        if data.is_empty() {
396
            return self.check_bounds(offset, 0);
397
        }
398

            
399
        self.check_word_offset(offset)?;
400
        self.check_bounds(offset, len)?;
401
        check_buffer(data)?;
402
541
        self.with_guard(None, |_| rom::read(offset, data))
403
541
    }
404

            
405
    /// Writes `data` starting at byte address `offset`.
406
    ///
407
    /// The target flash range must already be erased. `offset` is a flash byte
408
    /// address and must be a multiple of 4. `data` must reside in DRAM, not in
409
    /// flash, IRAM, RTC memory, or PSRAM. An empty `data` slice only checks
410
    /// that `offset` is within [`Self::capacity`].
411
    ///
412
    /// # Errors
413
    ///
414
    /// - [`Error::NotAligned`] if `data` is non-empty and `offset` is not a multiple of 4.
415
    /// - [`Error::OutOfBounds`] if the range exceeds [`Self::capacity`].
416
    /// - [`Error::NotSupported`] if `data` is not in DRAM.
417
    #[cfg_attr(
418
        multi_core,
419
        doc = "- [`Error::OtherCoreRunning`] on dual-core chips with [`MultiCoreStrategy::Error`]."
420
    )]
421
    /// - [`Error::IoError`], [`Error::IoTimeout`], or [`Error::Unknown`] if the ROM reports an I/O
422
    ///   error.
423
    ///
424
    /// # Safety
425
    ///
426
    /// The programmed range must not be mapped for instruction fetch or as
427
    /// immutable data. Writing a mapped `.text` or `.rodata` page overwrites
428
    /// the running image or mutates data the compiler treats as immutable.
429
    #[instability::unstable]
430
    #[ram]
431
13
    pub unsafe fn write(&mut self, offset: u32, data: &[u32]) -> Result<(), Error> {
432
        let Some(len) = byte_len(data) else {
433
            return Err(Error::OutOfBounds);
434
        };
435
13
        if data.is_empty() {
436
            return self.check_bounds(offset, 0);
437
        }
438

            
439
        self.check_word_offset(offset)?;
440
        self.check_bounds(offset, len)?;
441
        check_buffer(data)?;
442
7
        self.ensure_unlocked()?;
443
7
        self.with_guard(Some((offset, len as u32)), |_| rom::write(offset, data))
444
13
    }
445

            
446
    /// Erases flash sectors in the range `[from, to)`.
447
    ///
448
    /// `from` and `to` must be multiples of [`Self::SECTOR_SIZE`] (4096 bytes);
449
    /// there is no byte-granular erase. `to == capacity` is allowed. When
450
    /// `from == to`, this function only checks that `from` is within
451
    /// [`Self::capacity`].
452
    ///
453
    /// # Errors
454
    ///
455
    /// - [`Error::NotAligned`] if `from != to` and `from` or `to` is not a multiple of 4096.
456
    /// - [`Error::OutOfBounds`] if `from > to` or the range exceeds [`Self::capacity`].
457
    #[cfg_attr(
458
        multi_core,
459
        doc = "- [`Error::OtherCoreRunning`] on dual-core chips with [`MultiCoreStrategy::Error`]."
460
    )]
461
    /// - [`Error::IoError`], [`Error::IoTimeout`], or [`Error::Unknown`] if the ROM reports an I/O
462
    ///   error.
463
    ///
464
    /// # Safety
465
    ///
466
    /// The erased range must not be mapped for instruction fetch or as
467
    /// immutable data. Erasing a mapped `.text` or `.rodata` page destroys the
468
    /// running image or mutates data the compiler treats as immutable.
469
    #[instability::unstable]
470
    #[ram]
471
14
    pub unsafe fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> {
472
        let Some(len) = to.checked_sub(from) else {
473
            return Err(Error::OutOfBounds);
474
        };
475
        let len = len as usize;
476
13
        if len == 0 {
477
            return self.check_bounds(from, 0);
478
        }
479

            
480
        self.check_alignment(SECTOR_SIZE, from, len)?;
481
        self.check_bounds(from, len)?;
482
9
        self.ensure_unlocked()?;
483
9
        self.erase_range(from, to)
484
4
    }
485

            
486
    /// Reads decrypted words into `data` starting at byte address `offset`.
487
    ///
488
    /// Uses a temporary MMU mapping to read decrypted data through the cache.
489
    /// If flash encryption is not enabled, this returns plaintext.
490
    ///
491
    /// `offset` is a flash byte address and must be a multiple of 4. `data`
492
    /// must reside in DRAM, not in flash, IRAM, RTC memory, or PSRAM. An empty
493
    /// `data` slice only checks that `offset` is within [`Self::capacity`].
494
    ///
495
    /// # Errors
496
    ///
497
    /// - [`Error::NotAligned`] if `data` is non-empty and `offset` is not a multiple of 4.
498
    /// - [`Error::OutOfBounds`] if the range exceeds [`Self::capacity`].
499
    /// - [`Error::NotSupported`] if `data` is not in DRAM or no MMU entry is available.
500
    #[cfg_attr(
501
        multi_core,
502
        doc = "- [`Error::OtherCoreRunning`] on dual-core chips with [`MultiCoreStrategy::Error`]."
503
    )]
504
    #[instability::unstable]
505
516
    pub fn read_encrypted(&mut self, offset: u32, data: &mut [u32]) -> Result<(), Error> {
506
        let Some(len) = byte_len(data) else {
507
            return Err(Error::OutOfBounds);
508
        };
509
516
        if data.is_empty() {
510
            return self.check_bounds(offset, 0);
511
        }
512

            
513
        self.check_word_offset(offset)?;
514
        self.check_bounds(offset, len)?;
515
        check_buffer(data)?;
516
        self.with_mmu_guard(|_| mmu::read_flash_encrypted(offset, data))
517
516
    }
518

            
519
    /// Writes `data` with transparent flash encryption.
520
    ///
521
    /// `offset` must be a multiple of 16, and `data.len()` must be a multiple
522
    /// of 4 words (16 bytes). The destination must already be erased, and bytes
523
    /// outside the requested range are not programmed. `data` must reside in
524
    /// DRAM, not in flash, IRAM, RTC memory, or PSRAM.
525
    ///
526
    /// On ESP32, encrypted writes operate in 32-byte rows. If a boundary is not
527
    /// 32-byte aligned, the driver preserves the adjacent 16-byte block by
528
    /// re-encrypting its existing plaintext to the same ciphertext. The neighbor
529
    /// block therefore does not require prior erasing.
530
    #[cfg_attr(
531
        esp32c5,
532
        doc = " Encrypted writes may fail at higher CPU frequencies (240 MHz)."
533
    )]
534
    /// An empty `data` slice only checks that `offset` is within [`Self::capacity`].
535
    ///
536
    /// # Errors
537
    ///
538
    /// - [`Error::NotSupported`] if flash encryption is disabled. The eFuse gate is checked before
539
    ///   the arguments are.
540
    /// - [`Error::NotAligned`] if `data` is non-empty and `offset` is not a multiple of 16, or
541
    ///   `data.len()` is not a multiple of 4 words (16 bytes).
542
    /// - [`Error::OutOfBounds`] if the range exceeds [`Self::capacity`].
543
    /// - [`Error::NotSupported`] if `data` is not in DRAM, or on ESP32 if reading a row neighbor
544
    ///   fails because no MMU entry is available.
545
    #[cfg_attr(
546
        multi_core,
547
        doc = "- [`Error::OtherCoreRunning`] on dual-core chips with [`MultiCoreStrategy::Error`]."
548
    )]
549
    /// - [`Error::IoError`], [`Error::IoTimeout`], or [`Error::Unknown`] if the ROM reports an I/O
550
    ///   error.
551
    ///
552
    /// # Safety
553
    ///
554
    /// The programmed range must not be mapped for instruction fetch or as
555
    /// immutable data. Writing a mapped `.text` or `.rodata` page overwrites
556
    /// the running image, or mutates `static` or `.rodata` data the compiler
557
    /// treats as immutable.
558
    #[instability::unstable]
559
    #[ram]
560
8
    pub unsafe fn write_encrypted(&mut self, offset: u32, data: &[u32]) -> Result<(), Error> {
561
        #[cfg(not(__test_flash))]
562
        if !crate::efuse::flash_encryption() {
563
            return Err(Error::NotSupported);
564
        }
565

            
566
        let Some(len) = byte_len(data) else {
567
            return Err(Error::OutOfBounds);
568
        };
569
8
        if data.is_empty() {
570
            return self.check_bounds(offset, 0);
571
        }
572

            
573
        self.check_alignment(ENCRYPT_BLOCK_SIZE, offset, len)?;
574
        self.check_bounds(offset, len)?;
575
        check_buffer(data)?;
576
3
        self.ensure_unlocked()?;
577

            
578
        #[cfg(esp32)]
579
        let neighbors = self.esp32_row_neighbors(offset, len)?;
580
        // On ESP32 the programmed range can extend one 16-byte block past each
581
        // end; `with_guard` flushes the whole cache there, so the range only
582
        // needs to cover the request.
583
3
        self.with_guard(Some((offset, len as u32)), |_| {
584
3
            write_encrypted_rows(
585
3
                offset,
586
                words_as_bytes(data),
587
                #[cfg(esp32)]
588
                &neighbors,
589
            )
590
        })
591
8
    }
592
}
593

            
594
#[inline(always)]
595
fn byte_len(data: &[u32]) -> Option<usize> {
596
    data.len().checked_mul(WORD_SIZE as usize)
597
}
598

            
599
#[inline(always)]
600
fn words_as_bytes(data: &[u32]) -> &[u8] {
601
    // SAFETY: inspecting the in-memory bytes of `u32` words.
602
    unsafe { core::slice::from_raw_parts(data.as_ptr().cast(), size_of_val(data)) }
603
}
604

            
605
#[inline(always)]
606
fn check_buffer(buf: &[u32]) -> Result<(), Error> {
607
541
    if !is_slice_in_dram(buf) {
608
        return Err(Error::NotSupported);
609
    }
610
    Ok(())
611
}
612

            
613
/// Word-aligned staging buffer. The ROM encrypts in place.
614
#[repr(C, align(4))]
615
struct EncryptRow([u8; XTS_AES_BLOCK_MAX]);
616

            
617
#[ram]
618
3
fn write_encrypted_rows(
619
    offset: u32,
620
    data: &[u8],
621
    #[cfg(esp32)] neighbors: &Esp32Neighbors,
622
) -> Result<(), Error> {
623
3
    let mut row = EncryptRow([0; XTS_AES_BLOCK_MAX]);
624
    let mut i = 0;
625
3
    while i < data.len() {
626
3
        let row_addr = offset + i as u32;
627
3
        let (program_addr, program_len, consumed) =
628
            cfg_select! {
629
                esp32 => prepare_esp32_row(&mut row.0, row_addr, &data[i..], neighbors),
630
3
                _ => prepare_xts_row(&mut row.0, row_addr, &data[i..]),
631
            };
632
        rom::write_encrypted(program_addr, row.0.as_mut_ptr().cast(), program_len)?;
633
        i += consumed;
634
    }
635
    Ok(())
636
3
}
637

            
638
#[cfg(esp32)]
639
struct Esp32Neighbors {
640
    pre: [u32; 4],
641
    post: [u32; 4],
642
}
643

            
644
/// Fill a 32-byte ESP32 ROM row. Returns `(program_addr, program_len, consumed)`.
645
///
646
/// Mirrors the ESP32 arm of ESP-IDF `esp_flash_write_encrypted`: a row is two
647
/// AES blocks sharing an address-derived tweak, so a 16-byte write must carry
648
/// the decrypted neighbor block along and re-encrypt it to the same ciphertext.
649
#[cfg(esp32)]
650
#[ram]
651
fn prepare_esp32_row(
652
    buf: &mut [u8; XTS_AES_BLOCK_MAX],
653
    row_addr: u32,
654
    remaining: &[u8],
655
    neighbors: &Esp32Neighbors,
656
) -> (u32, u32, usize) {
657
    const BLOCK: usize = ENCRYPT_BLOCK_SIZE as usize;
658
    const ROW: usize = ESP32_ENCRYPT_ROW as usize;
659

            
660
    if !row_addr.is_multiple_of(ESP32_ENCRYPT_ROW) {
661
        buf[..BLOCK].copy_from_slice(words_as_bytes(&neighbors.pre));
662
        buf[BLOCK..ROW].copy_from_slice(&remaining[..BLOCK]);
663
        (row_addr - ENCRYPT_BLOCK_SIZE, ESP32_ENCRYPT_ROW, BLOCK)
664
    } else if remaining.len() == BLOCK {
665
        buf[..BLOCK].copy_from_slice(&remaining[..BLOCK]);
666
        buf[BLOCK..ROW].copy_from_slice(words_as_bytes(&neighbors.post));
667
        (row_addr, ESP32_ENCRYPT_ROW, BLOCK)
668
    } else {
669
        buf[..ROW].copy_from_slice(&remaining[..ROW]);
670
        (row_addr, ESP32_ENCRYPT_ROW, ROW)
671
    }
672
}
673

            
674
/// Largest row the flash-encryption hardware accepts, matching IDF's
675
/// `SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX`.
676
#[cfg(any(esp32, esp32c2, esp32c3))]
677
const XTS_AES_BLOCK_MAX: usize = 32;
678
#[cfg(not(any(esp32, esp32c2, esp32c3)))]
679
const XTS_AES_BLOCK_MAX: usize = 64;
680

            
681
/// Pick the largest aligned row, as ESP-IDF does for ESP32-S2 and later.
682
#[cfg(not(esp32))]
683
#[ram]
684
3
fn prepare_xts_row(
685
    buf: &mut [u8; XTS_AES_BLOCK_MAX],
686
    row_addr: u32,
687
    remaining: &[u8],
688
) -> (u32, u32, usize) {
689
    let row_size =
690
        if XTS_AES_BLOCK_MAX >= 64 && row_addr.is_multiple_of(64) && remaining.len() >= 64 {
691
            64
692
3
        } else if row_addr.is_multiple_of(32) && remaining.len() >= 32 {
693
            32
694
        } else {
695
            16
696
        };
697
    buf[..row_size].copy_from_slice(&remaining[..row_size]);
698
3
    (row_addr, row_size as u32, row_size)
699
3
}
700

            
701
impl Flash<'_, Blocking> {
702
    #[inline(always)]
703
    fn check_word_offset(&self, offset: u32) -> Result<(), Error> {
704
544
        if !offset.is_multiple_of(WORD_SIZE) {
705
            return Err(Error::NotAligned);
706
        }
707
        Ok(())
708
    }
709

            
710
    #[inline(always)]
711
    fn check_alignment(&self, align: u32, offset: u32, length: usize) -> Result<(), Error> {
712
11
        if !offset.is_multiple_of(align) || !length.is_multiple_of(align as usize) {
713
            return Err(Error::NotAligned);
714
        }
715
        Ok(())
716
    }
717

            
718
    #[inline(always)]
719
    fn check_bounds(&self, offset: u32, length: usize) -> Result<(), Error> {
720
        let offset = offset as usize;
721
543
        if length > self.capacity || offset > self.capacity - length {
722
            return Err(Error::OutOfBounds);
723
        }
724
        Ok(())
725
4
    }
726

            
727
    /// Park and disable interrupts without suspending the cache.
728
    ///
729
    /// Encrypted reads map flash through the MMU and copy with the cache on, so
730
    /// neither this guard nor anything it calls has to live in RAM.
731
    fn with_mmu_guard<R>(
732
        &mut self,
733
        f: impl FnOnce(&mut Self) -> Result<R, Error>,
734
    ) -> Result<R, Error> {
735
        free(|| {
736
            let _park = ParkGuard::enter(self)?;
737
            f(self)
738
        })
739
    }
740

            
741
    /// Plaintext of row halves the write will touch but not replace.
742
    ///
743
    /// Read before the cache-off write path: [`Self::read_encrypted`] needs the
744
    /// cache on. Re-encrypting this plaintext in [`prepare_esp32_row`] reproduces
745
    /// the ciphertext already in flash, so those neighbors need no separate erase.
746
    #[cfg(esp32)]
747
    fn esp32_row_neighbors(&mut self, offset: u32, len: usize) -> Result<Esp32Neighbors, Error> {
748
        let mut neighbors = Esp32Neighbors {
749
            pre: [0; 4],
750
            post: [0; 4],
751
        };
752
        if !offset.is_multiple_of(ESP32_ENCRYPT_ROW) {
753
            self.read_encrypted(offset - ENCRYPT_BLOCK_SIZE, &mut neighbors.pre)?;
754
        }
755
        let end = offset + len as u32;
756
        if !end.is_multiple_of(ESP32_ENCRYPT_ROW) {
757
            self.read_encrypted(end, &mut neighbors.post)?;
758
        }
759
        Ok(neighbors)
760
    }
761

            
762
    #[ram]
763
541
    fn with_guard<R>(
764
        &mut self,
765
        invalidate: Option<(u32, u32)>,
766
        f: impl FnOnce(&mut Self) -> Result<R, Error>,
767
    ) -> Result<R, Error> {
768
541
        free(|| {
769
            let _park = ParkGuard::enter(self)?;
770
541
            let cache = cache::CacheGuard::suspend();
771
10
            let result = f(self);
772
            cfg_select! {
773
                esp32 => {
774
                    // Address-based invalidate is unavailable; flush while still off.
775
                    if invalidate.is_some() {
776
                        cache.flush_while_off();
777
                    }
778
                    drop(cache);
779
                }
780
                _ => {
781
541
                    drop(cache);
782
541
                    if let Some((start, len)) = invalidate {
783
                        mmu::invalidate_mapped(start, len);
784
                    }
785
                }
786
            }
787
            result
788
        })
789
541
    }
790

            
791
    #[ram]
792
    fn ensure_unlocked(&mut self) -> Result<(), Error> {
793
19
        if self.unlocked {
794
            return Ok(());
795
        }
796
8
        self.with_guard(None, |this| {
797
            rom::unlock()?;
798
8
            this.unlocked = true;
799
            Ok(())
800
        })
801
11
    }
802

            
803
    #[ram]
804
9
    fn erase_range(&mut self, from: u32, to: u32) -> Result<(), Error> {
805
        let mut address = from;
806
9
        while address < to && !address.is_multiple_of(BLOCK_SIZE) {
807
10
            self.with_guard(Some((address, SECTOR_SIZE)), |_| {
808
10
                rom::erase_sector(address / SECTOR_SIZE)
809
            })?;
810
10
            address += SECTOR_SIZE;
811
        }
812

            
813
9
        while (to - address) >= BLOCK_SIZE {
814
            self.with_guard(Some((address, BLOCK_SIZE)), |_| {
815
                rom::erase_block(address / BLOCK_SIZE)
816
            })?;
817
            address += BLOCK_SIZE;
818
        }
819

            
820
9
        while address < to {
821
            self.with_guard(Some((address, SECTOR_SIZE)), |_| {
822
                rom::erase_sector(address / SECTOR_SIZE)
823
            })?;
824
            address += SECTOR_SIZE;
825
        }
826

            
827
        Ok(())
828
9
    }
829
}
830

            
831
/// Parks the other core according to [`MultiCoreStrategy`], unparking on drop.
832
struct ParkGuard {
833
    #[cfg(multi_core)]
834
    parked: Option<crate::system::Cpu>,
835
}
836

            
837
impl ParkGuard {
838
    #[ram]
839
    fn enter(_flash: &Flash<'_, Blocking>) -> Result<Self, Error> {
840
        cfg_select! {
841
            multi_core => {
842
                let parked = match _flash.multi_core_strategy {
843
                    MultiCoreStrategy::Error => {
844
                        for other in crate::system::Cpu::other() {
845
                            if crate::system::is_running(other) {
846
                                return Err(Error::OtherCoreRunning);
847
                            }
848
                        }
849
                        None
850
                    }
851
                    MultiCoreStrategy::AutoPark => {
852
                        let mut cpu_ctrl = crate::system::CpuControl::new(unsafe {
853
                            crate::peripherals::CPU_CTRL::steal()
854
                        });
855
                        let mut parked = None;
856
                        for other in crate::system::Cpu::other() {
857
                            if crate::system::is_running(other) {
858
                                unsafe { cpu_ctrl.park_core(other) };
859
                                parked = Some(other);
860
                            }
861
                        }
862
                        parked
863
                    }
864
                    MultiCoreStrategy::Ignore(_) => None,
865
                };
866
                Ok(Self { parked })
867
            }
868
            _ => Ok(Self {}),
869
        }
870
    }
871
}
872

            
873
impl Drop for ParkGuard {
874
    #[ram]
875
    fn drop(&mut self) {
876
        cfg_select! {
877
            multi_core => {
878
                if let Some(core) = self.parked {
879
                    let mut cpu_ctrl = crate::system::CpuControl::new(unsafe {
880
                        crate::peripherals::CPU_CTRL::steal()
881
                    });
882
                    cpu_ctrl.unpark_core(core);
883
                }
884
            }
885
            _ => {}
886
        }
887
    }
888
}