1
//! Flash MMU access.
2
//!
3
//! Everything that has to know which flash is mapped where: dropping the cache
4
//! lines of a written flash range ([`invalidate_mapped`]), and the temporary
5
//! mappings that let [`read_flash_encrypted`] read decrypted flash through the
6
//! cache. Suspending the caches themselves is [`super::cache`]'s job.
7
//!
8
//! Nothing here runs with the cache suspended, so these helpers live in flash.
9
//! The two ESP32 exceptions "writing the MMU table and flushing the cache"
10
//! are confined to [`esp32_cache`].
11

            
12
use core::{ops::Range, ptr};
13

            
14
use super::Error;
15

            
16
/// Guard for a temporarily mapped flash MMU page.
17
struct FlashMmapGuard {
18
    entry_id: u32,
19
    vaddr: *const u8,
20
    #[cfg_attr(
21
        esp32,
22
        expect(dead_code, reason = "the ESP32 can only flush the whole cache")
23
    )]
24
    page_size: u32,
25
    /// When `false` the entry was already mapped and must not be invalidated.
26
    owned: bool,
27
}
28

            
29
impl FlashMmapGuard {
30
    /// A slot this driver mapped itself, and has to unmap again.
31
    fn new_owned(entry_id: u32, vaddr: *const u8, page_size: u32) -> Self {
32
        Self {
33
            entry_id,
34
            vaddr,
35
            page_size,
36
            owned: true,
37
        }
38
    }
39
}
40

            
41
/// Map a flash physical address to a virtual address for encrypted reading.
42
///
43
/// The returned mapping is ready to be read through: a temporary slot can
44
/// retain cache lines from its previous mapping, so the complete mapping is
45
/// invalidated before this function returns.
46
fn map_flash_page(paddr: u32) -> Result<FlashMmapGuard, Error> {
47
    let page_size = mmu_page_size();
48
    let page_paddr = paddr & !(page_size - 1);
49

            
50
    if let Some(entry_id) = find_existing_entry(page_paddr) {
51
        let vaddr = entry_id_to_vaddr(entry_id).ok_or(Error::NotSupported)?;
52
        invalidate_cache(vaddr as u32, page_size);
53
        return Ok(FlashMmapGuard {
54
            entry_id,
55
            vaddr,
56
            page_size,
57
            owned: false,
58
        });
59
    }
60

            
61
    // The virtual address is resolved before the entry is written, so a slot
62
    // without one is never left behind mapped.
63
    cfg_select! {
64
        esp32s2 => {
65
            let entry_id = s2::alloc_entry().ok_or(Error::NotSupported)?;
66
            let vaddr = s2::map_entry(entry_id, page_paddr)?;
67
            Ok(FlashMmapGuard::new_owned(entry_id, vaddr, page_size))
68
        }
69
        esp32 => {
70
            let entry_id = find_free_entry().ok_or(Error::NotSupported)?;
71
            let vaddr = entry_id_to_vaddr(entry_id).ok_or(Error::NotSupported)?;
72
            // Writing the entry and making it visible to the cache must happen
73
            // in one cache-off window. The register is looked up before that
74
            // window: the bounds check of the lookup can call into flash.
75
            esp32_cache::map_entry(table::entry(entry_id), page_paddr);
76
            Ok(FlashMmapGuard::new_owned(entry_id, vaddr, page_size))
77
        }
78
        _ => {
79
            let entry_id = find_free_entry().ok_or(Error::NotSupported)?;
80
            let vaddr = entry_id_to_vaddr(entry_id).ok_or(Error::NotSupported)?;
81
            write_flash_entry(entry_id, page_paddr);
82
            invalidate_cache(vaddr as u32, page_size);
83
            Ok(FlashMmapGuard::new_owned(entry_id, vaddr, page_size))
84
        }
85
    }
86
}
87

            
88
/// Unmap a temporarily mapped flash page and invalidate the cache.
89
fn unmap_flash_page(guard: FlashMmapGuard) {
90
    cfg_select! {
91
        esp32 => {
92
            esp32_cache::unmap_entry(guard.owned.then(|| table::entry(guard.entry_id)));
93
        }
94
        _ => {
95
            invalidate_cache(guard.vaddr as u32, guard.page_size);
96
514
            if guard.owned {
97
                set_entry_invalid(guard.entry_id);
98
            }
99
        }
100
    }
101
}
102

            
103
/// Read decrypted flash bytes via temporary MMU mappings.
104
///
105
/// The cache must stay on. Callers park the other core and disable interrupts.
106
pub(super) fn read_flash_encrypted(offset: u32, data: &mut [u32]) -> Result<(), Error> {
107
    if data.is_empty() {
108
        return Ok(());
109
    }
110

            
111
    let bytes = unsafe {
112
        core::slice::from_raw_parts_mut(
113
            data.as_mut_ptr().cast::<u8>(),
114
            data.len() * super::WORD_SIZE as usize,
115
        )
116
    };
117

            
118
    let page_size = mmu_page_size();
119
    let mut remaining = bytes;
120
    let mut current_offset = offset;
121

            
122
514
    while !remaining.is_empty() {
123
        let page_base = current_offset & !(page_size - 1);
124
514
        let in_page = (current_offset - page_base) as usize;
125
514
        let chunk = remaining.len().min(page_size as usize - in_page);
126

            
127
        let guard = map_flash_page(page_base)?;
128
        let src = unsafe { guard.vaddr.add(in_page) };
129
        unsafe {
130
            ptr::copy_nonoverlapping(src, remaining.as_mut_ptr(), chunk);
131
        }
132
        unmap_flash_page(guard);
133

            
134
514
        current_offset += chunk as u32;
135
        remaining = &mut remaining[chunk..];
136
    }
137

            
138
    Ok(())
139
}
140

            
141
/// Drop the cache lines covering `vaddr..vaddr + size`.
142
///
143
/// Both users invalidate flash that may be mapped as code as well as data, so
144
/// every cache that can hold the line is included.
145
#[cfg(not(any(esp32, esp32h4, esp32p4, esp32s31)))]
146
fn invalidate_cache(vaddr: u32, size: u32) {
147
514
    unsafe { crate::soc::cache_invalidate_addr(vaddr, size) }
148
}
149

            
150
// Not `soc::cache_invalidate_addr`: it only covers the L1 D-cache (plus L2 on
151
// the P4) for DMA buffers, while flash is also cached by the L1 I-caches. On
152
// the P4, adding `soc::cache_invalidate_icache_addr` would invalidate L2 twice.
153
#[cfg(any(esp32h4, esp32p4, esp32s31))]
154
fn invalidate_cache(vaddr: u32, size: u32) {
155
    const CACHE_MAP_L1_ICACHE_0: u32 = 1 << 0;
156
    const CACHE_MAP_L1_ICACHE_1: u32 = 1 << 1;
157
    const CACHE_MAP_L1_DCACHE: u32 = 1 << 4;
158
    #[cfg(esp32p4)]
159
    const CACHE_MAP_L2_CACHE: u32 = 1 << 5;
160

            
161
    // IROM and DROM share the same window on the P4, which also needs L2.
162
    let map = CACHE_MAP_L1_ICACHE_0 | CACHE_MAP_L1_ICACHE_1 | CACHE_MAP_L1_DCACHE;
163
    #[cfg(esp32p4)]
164
    let map = map | CACHE_MAP_L2_CACHE;
165

            
166
    unsafe extern "C" {
167
        fn Cache_Invalidate_Addr(map: u32, addr: u32, size: u32);
168
    }
169
    unsafe {
170
        Cache_Invalidate_Addr(map, vaddr, size);
171
    }
172
}
173

            
174
#[cfg(esp32)]
175
fn invalidate_cache(_vaddr: u32, _size: u32) {
176
    // The ESP32 cache cannot invalidate a single address range, so the whole cache is flushed.
177
    esp32_cache::flush_caches();
178
}
179

            
180
/// Drop cache lines that map `start..start+len` after the caches are back on.
181
///
182
/// IDF invalidates in this order (`spi_flash_check_and_flush_cache` after the
183
/// operation's cache-restore path). ESP32 cannot invalidate by address and
184
/// flushes while still off instead.
185
#[cfg(not(esp32))]
186
pub(super) fn invalidate_mapped(start: u32, len: u32) {
187
10
    if len == 0 {
188
        return;
189
    }
190

            
191
    let page_size = mmu_page_size();
192
    let end = start.saturating_add(len);
193
10
    let mut addr = start & !(page_size - 1);
194

            
195
10
    while addr < end {
196
        invalidate_mapped_page(addr, page_size);
197
        addr = addr.saturating_add(page_size);
198
    }
199
}
200

            
201
/// Drop cache lines for every MMU entry that maps `page_paddr`.
202
///
203
/// One physical page can appear more than once. On C2, C3, and S3 the same
204
/// entry is also visible through a separate I-bus window, so both vaddrs are
205
/// invalidated.
206
#[cfg(not(esp32))]
207
fn invalidate_mapped_page(page_paddr: u32, page_size: u32) {
208
    let page = flash_page_number(page_paddr);
209

            
210
    for entry_id in all_entries() {
211
1280
        if entry_is_valid(entry_id)
212
            && entry_is_flash_mapping(entry_id)
213
20
            && entry_flash_page(entry_id) == page
214
        {
215
1
            invalidate_entry_caches(entry_id, page_size);
216
        }
217
    }
218
}
219

            
220
#[cfg(not(esp32))]
221
fn invalidate_entry_caches(entry_id: u32, page_size: u32) {
222
    cfg_select! {
223
        esp32s2 => {
224
            if let Some(vaddr) = entry_id_to_vaddr(entry_id) {
225
                invalidate_cache(vaddr as u32, page_size);
226
            }
227
        }
228
        _ => {
229
            let offset = entry_id * page_size;
230
            let drom = memory_range!("DROM").start as u32 + offset;
231
            let irom = memory_range!("IROM").start as u32 + offset;
232
            invalidate_cache(drom, page_size);
233
            if irom != drom {
234
                invalidate_cache(irom, page_size);
235
            }
236
        }
237
    }
238
}
239

            
240
/// Virtual address an MMU entry maps to, if it is reachable through a data bus.
241
fn entry_id_to_vaddr(entry_id: u32) -> Option<*const u8> {
242
    cfg_select! {
243
        esp32s2 => s2::entry_id_to_vaddr(entry_id),
244
        _ => {
245
            let base = memory_range!("DROM").start as u32;
246
514
            Some((base + entry_id * mmu_page_size()) as *const u8)
247
        }
248
    }
249
}
250

            
251
fn find_existing_entry(page_paddr: u32) -> Option<u32> {
252
    let page = flash_page_number(page_paddr);
253

            
254
    data_entries().find(|&entry_id| {
255
515
        entry_is_valid(entry_id)
256
            && entry_is_flash_mapping(entry_id)
257
            && entry_flash_page(entry_id) == page
258
    })
259
}
260

            
261
fn find_free_entry() -> Option<u32> {
262
    let entries = data_entries();
263
    // Skip the last entry reserved for bootloader internal flash access.
264
    (entries.start..entries.end.saturating_sub(1))
265
        .rev()
266
        .find(|&entry_id| !entry_is_valid(entry_id))
267
}
268

            
269
/// Entries that can back a temporary mapping and be read through as data.
270
fn data_entries() -> Range<u32> {
271
    cfg_select! {
272
        not(soc_has_mmu_table) => 0..property!("mmu.entry_num"),
273
        esp32s2 => s2::DATA_ENTRY_START..s2::DATA_ENTRY_END,
274
        // The table peripheral bounds the usable entry ids: `mmu.entry_num`
275
        // counts logical entries across every bus and indexes out of range on
276
        // the ESP32, whose table exposes its 64 DROM0 slots only.
277
        _ => 0..table::entry_count(),
278
    }
279
}
280

            
281
/// Every entry that can map flash, including windows that only serve code.
282
#[cfg(not(esp32))]
283
fn all_entries() -> Range<u32> {
284
    cfg_select! {
285
        // Include the S2 I-bus slots (0..0x80). Skip DBUS2/DPORT (0x140..).
286
        esp32s2 => 0..s2::DATA_ENTRY_END,
287
        _ => data_entries(),
288
    }
289
}
290

            
291
fn mmu_page_size() -> u32 {
292
    cfg_select! {
293
        not(soc_has_mmu_table) => indexed::mmu_page_size(),
294
        // The C2 page size is configurable (IDF `mmu_ll_get_page_size`).
295
        esp32c2 => c2::mmu_page_size(),
296
        _ => property!("mmu.page_size"),
297
    }
298
}
299

            
300
/// Inlined so that the ESP32 can call it from its cache-off window.
301
#[inline(always)]
302
fn flash_page_number(page_paddr: u32) -> u32 {
303
    cfg_select! {
304
        not(soc_has_mmu_table) => indexed::flash_page_number(page_paddr),
305
        esp32c2 => page_paddr >> c2::mmu_page_size().trailing_zeros(),
306
514
        _ => page_paddr >> 16,
307
    }
308
}
309

            
310
fn entry_is_valid(entry_id: u32) -> bool {
311
    cfg_select! {
312
        not(soc_has_mmu_table) => indexed::entry_is_valid(entry_id),
313
        _ => table::entry_is_valid(entry_id),
314
    }
315
}
316

            
317
fn entry_is_flash_mapping(entry_id: u32) -> bool {
318
    cfg_select! {
319
        not(soc_has_mmu_table) => indexed::entry_is_flash_mapping(entry_id),
320
        _ => table::entry_is_flash_mapping(entry_id),
321
    }
322
}
323

            
324
fn entry_flash_page(entry_id: u32) -> u32 {
325
    cfg_select! {
326
        not(soc_has_mmu_table) => indexed::entry_flash_page(entry_id),
327
        _ => table::entry_flash_page(entry_id),
328
    }
329
}
330

            
331
/// The ESP32 writes its entries inside a cache-off window instead.
332
#[cfg(not(any(esp32, esp32s2)))]
333
fn write_flash_entry(entry_id: u32, page_paddr: u32) {
334
    cfg_select! {
335
        not(soc_has_mmu_table) => indexed::write_flash_entry(entry_id, page_paddr),
336
        _ => table::write_flash_entry(entry_id, page_paddr),
337
    }
338
}
339

            
340
/// The ESP32 invalidates its entries inside a cache-off window instead.
341
#[cfg(not(esp32))]
342
fn set_entry_invalid(entry_id: u32) {
343
    cfg_select! {
344
        not(soc_has_mmu_table) => indexed::set_entry_invalid(entry_id),
345
        _ => table::set_entry_invalid(entry_id),
346
    }
347
}
348

            
349
#[cfg(not(soc_has_mmu_table))]
350
mod indexed {
351
    use crate::peripherals::SPI0;
352

            
353
    pub(super) fn mmu_page_size() -> u32 {
354
        let code = mmu_page_size_code();
355
        cfg_select! {
356
            // The S31 flash MMU's maximum page size is 256 KiB.
357
            esp32s31 => 0x40000 >> code,
358
            _ => 0x10000 >> code,
359
        }
360
    }
361

            
362
    fn mmu_page_size_code() -> u32 {
363
        let ctrl = SPI0::regs().mmu_power_ctrl().read();
364
        cfg_select! {
365
            any(esp32c5, esp32c61, esp32s31) => ctrl.mmu_page_size().bits() as u32,
366
            _ => ctrl.spi_mmu_page_size().bits() as u32,
367
        }
368
    }
369

            
370
    pub(super) fn flash_page_number(page_paddr: u32) -> u32 {
371
        page_paddr >> mmu_page_size().trailing_zeros()
372
    }
373

            
374
    fn with_entry<R>(entry_id: u32, f: impl FnOnce() -> R) -> R {
375
        SPI0::regs()
376
            .mmu_item_index()
377
            .write(|w| unsafe { w.mmu_item_index().bits(entry_id) });
378
        f()
379
    }
380

            
381
    pub(super) fn entry_is_valid(entry_id: u32) -> bool {
382
        with_entry(entry_id, || {
383
            SPI0::regs().mmu_item_content().read().valid().bit()
384
        })
385
    }
386

            
387
    pub(super) fn entry_is_flash_mapping(entry_id: u32) -> bool {
388
        cfg_select! {
389
            any(esp32c5, esp32c61, esp32h4) => with_entry(entry_id, || {
390
                !SPI0::regs().mmu_item_content().read().access_spiram().bit()
391
            }),
392
            _ => {
393
                let _ = entry_id;
394
                true
395
            }
396
        }
397
    }
398

            
399
    pub(super) fn entry_flash_page(entry_id: u32) -> u32 {
400
        with_entry(entry_id, || {
401
            SPI0::regs().mmu_item_content().read().paddr().bits() as u32
402
        })
403
    }
404

            
405
    pub(super) fn write_flash_entry(entry_id: u32, page_paddr: u32) {
406
        let page = super::flash_page_number(page_paddr) as u16;
407
        let encrypted = crate::efuse::flash_encryption();
408
        with_entry(entry_id, || {
409
            SPI0::regs().mmu_item_content().write(|w| {
410
                unsafe { w.paddr().bits(page) };
411
                #[cfg(any(esp32c5, esp32c61, esp32h4))]
412
                w.access_spiram().clear_bit();
413
                w.valid().set_bit();
414
                if encrypted {
415
                    w.sensitive().set_bit();
416
                }
417
                w
418
            });
419
        });
420
    }
421

            
422
    pub(super) fn set_entry_invalid(entry_id: u32) {
423
        with_entry(entry_id, || SPI0::regs().mmu_item_content().reset());
424
    }
425
}
426

            
427
#[cfg(soc_has_mmu_table)]
428
mod table {
429
    use crate::{pac::mmu_table::ENTRY, peripherals::MMU_TABLE};
430

            
431
    /// Look up one MMU entry.
432
    ///
433
    /// The ESP32 looks the entry up before it turns the cache off: the bounds
434
    /// check of the lookup can call into flash, which is unreachable then.
435
    #[inline(always)]
436
    pub(super) fn entry(entry_id: u32) -> &'static ENTRY {
437
        MMU_TABLE::regs().entry(entry_id as usize)
438
    }
439

            
440
    pub(super) fn entry_is_valid(entry_id: u32) -> bool {
441
        !entry(entry_id).read().invalid().bit()
442
    }
443

            
444
    pub(super) fn entry_is_flash_mapping(entry_id: u32) -> bool {
445
        let e = entry(entry_id).read();
446
        cfg_select! {
447
            esp32s2 => e.access_flash().bit(),
448
            esp32s3 => !e.access_spiram().bit(),
449
            _ => {
450
                let _ = e;
451
                true
452
            }
453
        }
454
    }
455

            
456
    pub(super) fn entry_flash_page(entry_id: u32) -> u32 {
457
515
        entry(entry_id).read().paddr().bits() as u32
458
    }
459

            
460
    // The ESP32 calls the two functions below with the cache off, where a call into flash would
461
    // hang. Inlining them into the caller in RAM keeps them reachable.
462
    #[cfg(not(esp32s2))]
463
    #[inline(always)]
464
    pub(super) fn write_entry(entry: &ENTRY, page_paddr: u32) {
465
        let page = super::flash_page_number(page_paddr) as u16;
466
        entry.write(|w| {
467
            cfg_select! {
468
                any(esp32, esp32c2, esp32c3) => unsafe { w.paddr().bits(page as u8) },
469
                _ => unsafe { w.paddr().bits(page) },
470
            };
471
            w.invalid().clear_bit();
472
            #[cfg(all(soc_has_psram, not(any(esp32, esp32s2))))]
473
            w.access_spiram().clear_bit();
474
            w
475
        });
476
    }
477

            
478
    #[inline(always)]
479
    pub(super) fn invalidate_entry(entry: &ENTRY) {
480
        entry.write(|w| w.invalid().set_bit());
481
    }
482

            
483
    #[cfg(not(any(esp32, esp32s2)))]
484
    pub(super) fn write_flash_entry(entry_id: u32, page_paddr: u32) {
485
        write_entry(entry(entry_id), page_paddr);
486
    }
487

            
488
    #[cfg(not(esp32))]
489
    pub(super) fn set_entry_invalid(entry_id: u32) {
490
        invalidate_entry(entry(entry_id));
491
    }
492

            
493
    /// The S2 derives its range from the bus layout instead.
494
    #[cfg(not(esp32s2))]
495
    pub(super) fn entry_count() -> u32 {
496
        MMU_TABLE::regs().entry_iter().count() as u32
497
    }
498
}
499

            
500
/// Flash MMU and cache maintenance for the ESP32.
501
///
502
/// The ESP32 cache must be off while the flash MMU is written or the cache is
503
/// flushed. Both operations disturb a cache fill that runs at the same time,
504
/// and the core that waits for the fill then hangs. ESP-IDF turns the cache
505
/// off around both operations, see `s_do_mapping()` in
506
/// `components/esp_mm/esp_mmu_map.c`.
507
///
508
/// Everything that runs inside the cache-off window must be reachable without
509
/// flash: the `#[ram]` entry points below only call `#[ram]` guard methods and
510
/// helpers that are inlined into them.
511
#[cfg(esp32)]
512
mod esp32_cache {
513
    use procmacros::ram;
514

            
515
    use super::table;
516
    use crate::{flash::cache::CacheGuard, pac::mmu_table::ENTRY};
517

            
518
    /// Map `page_paddr` into `entry` and make the new mapping visible.
519
    #[ram]
520
    pub(super) fn map_entry(entry: &ENTRY, page_paddr: u32) {
521
        let cache = CacheGuard::suspend();
522
        table::write_entry(entry, page_paddr);
523
        cache.flush_while_off();
524
    }
525

            
526
    /// Drop the lines of a temporary mapping and invalidate its entry, if we own it.
527
    #[ram]
528
    pub(super) fn unmap_entry(owned_entry: Option<&ENTRY>) {
529
        let cache = CacheGuard::suspend();
530
        if let Some(entry) = owned_entry {
531
            table::invalidate_entry(entry);
532
        }
533
        cache.flush_while_off();
534
    }
535

            
536
    /// Drop every cached line.
537
    #[ram]
538
    pub(super) fn flush_caches() {
539
        let cache = CacheGuard::suspend();
540
        cache.flush_while_off();
541
    }
542
}
543

            
544
#[cfg(esp32c2)]
545
mod c2 {
546
    use crate::peripherals::EXTMEM;
547

            
548
    /// 0 = 16 KiB, 1 = 32 KiB, 2 = 64 KiB (`EXTMEM_CACHE_MMU_PAGE_SIZE`).
549
    pub(super) fn mmu_page_size() -> u32 {
550
        match EXTMEM::regs()
551
            .cache_conf_misc()
552
            .read()
553
            .cache_mmu_page_size()
554
            .bits()
555
        {
556
            0 => 0x4000,
557
            1 => 0x8000,
558
            _ => 0x10000,
559
        }
560
    }
561
}
562

            
563
#[cfg(esp32s2)]
564
mod s2 {
565
    use procmacros::ram;
566

            
567
    use super::*;
568

            
569
    pub(super) const DATA_ENTRY_START: u32 = 0x200 / 4;
570
    pub(super) const DATA_ENTRY_END: u32 = 0x500 / 4;
571
    const IBUS_ENTRY_END: u32 = 0x300 / 4;
572

            
573
    /// Virtual address for an S2 MMU entry.
574
    ///
575
    /// Matches `mmu_ll_entry_id_to_vaddr_base()`: IBUS0/1 at 0x4000_0000,
576
    /// DROM/DBUS at 0x3F00_0000. DBUS2/DPORT (0x140..) is 32-bit access only
577
    /// and has no usable base here.
578
    pub(super) fn entry_id_to_vaddr(entry_id: u32) -> Option<*const u8> {
579
        let page_size = mmu_page_size();
580
        let (base, relative) = match entry_id {
581
            0x00..0x40 => (0x4000_0000u32, entry_id),
582
            0x40..0x80 => (0x4000_0000u32, entry_id - 0x40),
583
            0x80..0xC0 => (0x3F00_0000u32, entry_id - 0x80),
584
            0xC0..0x100 => (0x3F00_0000u32, entry_id - 0xC0),
585
            0x100..0x140 => (0x3F00_0000u32, entry_id - 0x100),
586
            _ => return None,
587
        };
588
        Some((base + relative * page_size) as *const u8)
589
    }
590

            
591
    /// Pick a free MMU slot, preferring IBUS2/DROM entries first.
592
    ///
593
    /// Only data-bus entries are considered: on ESP32-S2, DROM (entries
594
    /// 128..192) is backed by the I-cache bus (`drom0_in_icache = 1` in
595
    /// ESP-IDF) but is addressed through 0x3F00_0000, while IBUS0/1 is not
596
    /// reachable with 8-bit loads.
597
    pub(super) fn alloc_entry() -> Option<u32> {
598
        (DATA_ENTRY_START..IBUS_ENTRY_END - 1)
599
            .rev()
600
            .find(|&entry_id| !entry_is_valid(entry_id))
601
            .or_else(find_free_entry)
602
    }
603

            
604
    fn entry_uses_ibus(entry_id: u32) -> bool {
605
        (DATA_ENTRY_START..IBUS_ENTRY_END).contains(&entry_id)
606
    }
607

            
608
    #[ram]
609
    fn mmu_rom_set(entry_id: u32, access: u32, vaddr: u32, paddr: u32) -> i32 {
610
        unsafe extern "C" {
611
            fn Cache_Ibus_MMU_Set(
612
                ext_ram: u32,
613
                vaddr: u32,
614
                paddr: u32,
615
                psize: u32,
616
                num: u32,
617
                fixed: u32,
618
            ) -> i32;
619
            fn Cache_Dbus_MMU_Set(
620
                ext_ram: u32,
621
                vaddr: u32,
622
                paddr: u32,
623
                psize: u32,
624
                num: u32,
625
                fixed: u32,
626
            ) -> i32;
627
        }
628

            
629
        unsafe {
630
            if entry_uses_ibus(entry_id) {
631
                Cache_Ibus_MMU_Set(access, vaddr, paddr, 64, 1, 0)
632
            } else {
633
                Cache_Dbus_MMU_Set(access, vaddr, paddr, 64, 1, 0)
634
            }
635
        }
636
    }
637

            
638
    /// Map an MMU entry to a flash page via ROM (must run from IRAM on ESP32-S2).
639
    #[ram]
640
    pub(super) fn map_entry(entry_id: u32, page_paddr: u32) -> Result<*const u8, Error> {
641
        let vaddr = entry_id_to_vaddr(entry_id).ok_or(Error::NotSupported)?;
642
        let page_size = mmu_page_size();
643
        let vaddr_u32 = vaddr as u32;
644
        invalidate_cache(vaddr_u32, page_size);
645
        if mmu_rom_set(entry_id, 1 << 15, vaddr_u32, page_paddr) != 0 {
646
            return Err(Error::NotSupported);
647
        }
648
        invalidate_cache(vaddr_u32, page_size);
649
        Ok(vaddr)
650
    }
651
}