1
#![cfg_attr(not(feature = "rt"), expect(unused))]
2

            
3
use core::{ops::Range, sync::atomic::Ordering};
4

            
5
use portable_atomic::AtomicU32;
6
use procmacros::ram;
7

            
8
use crate::efuse::ChipRevision;
9

            
10
#[cfg_attr(esp32, path = "esp32/mod.rs")]
11
#[cfg_attr(esp32c2, path = "esp32c2/mod.rs")]
12
#[cfg_attr(esp32c3, path = "esp32c3/mod.rs")]
13
#[cfg_attr(esp32c5, path = "esp32c5/mod.rs")]
14
#[cfg_attr(esp32c6, path = "esp32c6/mod.rs")]
15
#[cfg_attr(esp32c61, path = "esp32c61/mod.rs")]
16
#[cfg_attr(esp32h2, path = "esp32h2/mod.rs")]
17
#[cfg_attr(esp32h4, path = "esp32h4/mod.rs")]
18
#[cfg_attr(esp32p4, path = "esp32p4/mod.rs")]
19
#[cfg_attr(esp32s2, path = "esp32s2/mod.rs")]
20
#[cfg_attr(esp32s3, path = "esp32s3/mod.rs")]
21
#[cfg_attr(esp32s31, path = "esp32s31/mod.rs")]
22
mod implementation;
23

            
24
#[cfg(soc_has_xtal32k_pads)]
25
pub(crate) mod xtal32k;
26

            
27
cfg_select! {
28
    all(feature = "unstable", ulp_riscv_driver_supported) => {
29
        pub use self::implementation::*;
30
    }
31
    _ => {
32
        pub(crate) use self::implementation::*;
33
    }
34
}
35

            
36
#[allow(unused)]
37
373
pub(crate) fn is_valid_ram_address(address: usize) -> bool {
38
    addr_in_range(address, memory_range!("DRAM"))
39
373
}
40

            
41
#[allow(unused)]
42
pub(crate) fn is_slice_in_dram<T>(slice: &[T]) -> bool {
43
    slice_in_range(slice, memory_range!("DRAM"))
44
}
45

            
46
#[allow(unused)]
47
#[cfg(soc_has_psram)]
48
pub(crate) fn is_valid_psram_address(address: usize) -> bool {
49
    addr_in_range(address, crate::psram::psram_range())
50
}
51

            
52
#[allow(unused)]
53
#[cfg(soc_has_psram)]
54
pub(crate) fn is_slice_in_psram<T>(slice: &[T]) -> bool {
55
    slice_in_range(slice, crate::psram::psram_range())
56
}
57

            
58
#[allow(unused)]
59
pub(crate) fn is_valid_memory_address(address: usize) -> bool {
60
    cfg_select! {
61
        soc_has_psram => is_valid_ram_address(address) || is_valid_psram_address(address),
62
        _ => is_valid_ram_address(address),
63
    }
64
}
65

            
66
fn slice_in_range<T>(slice: &[T], range: Range<usize>) -> bool {
67
    let slice = slice.as_ptr_range();
68
    let start = slice.start as usize;
69
    let end = slice.end as usize;
70
    // `end` is >= `start`, so we don't need to check that `end > range.start`
71
    // `end` is also one past the last element, so it can be equal to the range's
72
    // end which is also one past the memory region's last valid address.
73
590
    addr_in_range(start, range.clone()) && end <= range.end
74
}
75

            
76
pub(crate) fn addr_in_range(addr: usize, range: Range<usize>) -> bool {
77
    range.contains(&addr)
78
}
79

            
80
#[cfg(feature = "rt")]
81
#[cfg(riscv)]
82
#[unsafe(export_name = "hal_main")]
83
328
fn hal_main(a0: usize, a1: usize, a2: usize) -> ! {
84
    unsafe extern "Rust" {
85
        // This symbol will be provided by the user via `#[entry]`
86
        fn main(a0: usize, a1: usize, a2: usize) -> !;
87
    }
88

            
89
    setup_stack_guard();
90

            
91
    unsafe {
92
328
        main(a0, a1, a2);
93
    }
94
}
95

            
96
#[cfg(all(xtensa, feature = "rt"))]
97
mod xtensa {
98
    use core::arch::{global_asm, naked_asm};
99

            
100
    /// The ESP32 has a first stage bootloader that handles loading program data into the right
101
    /// place, so loading is skipped here. Called by xtensa-lx-rt in Reset.
102
    #[unsafe(export_name = "__init_data")]
103
    extern "C" fn __init_data() -> bool {
104
        false
105
    }
106

            
107
    extern "C" fn __init_persistent() -> bool {
108
        matches!(
109
            crate::system::reset_reason(),
110
            None | Some(crate::rtc_cntl::SocResetReason::ChipPowerOn)
111
        )
112
    }
113

            
114
    unsafe extern "C" {
115
        static _dram2_uninit_bss_start: u32;
116
        static _dram2_uninit_bss_end: u32;
117
        static _rtc_fast_bss_start: u32;
118
        static _rtc_fast_bss_end: u32;
119
        static _rtc_fast_persistent_end: u32;
120
        static _rtc_fast_persistent_start: u32;
121

            
122
        static _rtc_slow_bss_start: u32;
123
        static _rtc_slow_bss_end: u32;
124
        static _rtc_slow_persistent_end: u32;
125
        static _rtc_slow_persistent_start: u32;
126

            
127
        fn _xtensa_lx_rt_zero_fill(s: *mut u32, e: *mut u32);
128

            
129
        static mut __stack_chk_guard: u32;
130
    }
131

            
132
    global_asm!(
133
        "
134
        .literal sym_init_persistent, {__init_persistent}
135
        .literal sym_xtensa_lx_rt_zero_fill, {_xtensa_lx_rt_zero_fill}
136

            
137
        .literal sym_dram2_uninit_bss_start, {_dram2_uninit_bss_start}
138
        .literal sym_dram2_uninit_bss_end, {_dram2_uninit_bss_end}
139

            
140
        .literal sym_rtc_fast_bss_start, {_rtc_fast_bss_start}
141
        .literal sym_rtc_fast_bss_end, {_rtc_fast_bss_end}
142
        .literal sym_rtc_fast_persistent_end, {_rtc_fast_persistent_end}
143
        .literal sym_rtc_fast_persistent_start, {_rtc_fast_persistent_start}
144

            
145
        .literal sym_rtc_slow_bss_start, {_rtc_slow_bss_start}
146
        .literal sym_rtc_slow_bss_end, {_rtc_slow_bss_end}
147
        .literal sym_rtc_slow_persistent_end, {_rtc_slow_persistent_end}
148
        .literal sym_rtc_slow_persistent_start, {_rtc_slow_persistent_start}
149
        ",
150
        __init_persistent = sym __init_persistent,
151
        _xtensa_lx_rt_zero_fill = sym _xtensa_lx_rt_zero_fill,
152

            
153
        _dram2_uninit_bss_end = sym _dram2_uninit_bss_end,
154
        _dram2_uninit_bss_start = sym _dram2_uninit_bss_start,
155

            
156
        _rtc_fast_bss_end = sym _rtc_fast_bss_end,
157
        _rtc_fast_bss_start = sym _rtc_fast_bss_start,
158
        _rtc_fast_persistent_end = sym _rtc_fast_persistent_end,
159
        _rtc_fast_persistent_start = sym _rtc_fast_persistent_start,
160

            
161
        _rtc_slow_bss_end = sym _rtc_slow_bss_end,
162
        _rtc_slow_bss_start = sym _rtc_slow_bss_start,
163
        _rtc_slow_persistent_end = sym _rtc_slow_persistent_end,
164
        _rtc_slow_persistent_start = sym _rtc_slow_persistent_start,
165
    );
166

            
167
    #[unsafe(export_name = "__post_init")]
168
    #[unsafe(naked)]
169
    #[allow(named_asm_labels)]
170
    extern "C" fn post_init() {
171
        naked_asm!(
172
            "
173
            entry  a1, 0x10                            // 4 words for callx4 spill area
174

            
175
            l32r   a2, sym_xtensa_lx_rt_zero_fill      // Pre-load address of zero-fill function
176

            
177
            l32r   a6, sym_dram2_uninit_bss_start      // Set input range to .dram2_uninit.bss
178
            l32r   a7, sym_dram2_uninit_bss_end        //
179
            callx4 a2                                  // Zero-fill
180

            
181
            l32r   a6, sym_rtc_fast_bss_start          // Set input range to .rtc_fast.bss
182
            l32r   a7, sym_rtc_fast_bss_end            //
183
            callx4 a2                                  // Zero-fill
184

            
185
            l32r   a6, sym_rtc_slow_bss_start          // Set input range to .rtc_slow.bss
186
            l32r   a7, sym_rtc_slow_bss_end            //
187
            callx4 a2                                  // Zero-fill
188

            
189
            l32r   a3, sym_init_persistent             // Do we need to initialize persistent data?
190
            callx4 a3
191
            beqz   a6, .Lpost_init_return              // If not, skip initialization
192

            
193
            l32r   a6, sym_rtc_fast_persistent_start   // Set input range to .rtc_fast.persistent
194
            l32r   a7, sym_rtc_fast_persistent_end     //
195
            callx4 a2                                  // Zero-fill
196

            
197
            l32r   a6, sym_rtc_slow_persistent_start   // Set input range to .rtc_slow.persistent
198
            l32r   a7, sym_rtc_slow_persistent_end     //
199
            callx4 a2                                  // Zero-fill
200

            
201
        .Lpost_init_return:
202
            retw.n
203
        ",
204
        )
205
    }
206

            
207
    // The section switch must stay in the same block as the literals: rustc does not emit
208
    // separate `global_asm!` items in source order.
209
    global_asm!(
210
        cfg_select! {
211
            esp32s3 => ".pushsection .rwtext,\"ax\",@progbits",
212
            _ => ".pushsection .text,\"ax\",@progbits",
213
        },
214
        "
215
        .literal sym_stack_chk_guard, {__stack_chk_guard}
216
        .literal stack_guard_value, {stack_guard_value}
217
        .literal sym_esp32_init, {__esp32_init}
218
        .popsection
219
        ",
220
        __stack_chk_guard = sym __stack_chk_guard,
221
        stack_guard_value = const esp_config::esp_config_int!(
222
            u32,
223
            "ESP_HAL_CONFIG_STACK_GUARD_VALUE"
224
        ),
225
        __esp32_init = sym esp32_init,
226
    );
227

            
228
    #[cfg_attr(esp32s3, unsafe(link_section = ".rwtext"))]
229
    #[unsafe(export_name = "__pre_init")]
230
    #[unsafe(naked)]
231
    unsafe extern "C" fn esp32_reset() {
232
        // Set up stack protector value before jumping to a rust function
233
        naked_asm! {
234
            "
235
            entry a1, 0x10 // 4 words for callx4 spill area
236

            
237
            // Set up the stack protector value
238
            l32r   a2, sym_stack_chk_guard
239
            l32r   a3, stack_guard_value
240
            s32i.n a3, a2, 0
241

            
242
            l32r   a2, sym_esp32_init
243
            callx4 a2
244

            
245
            retw.n
246
            "
247
        }
248
    }
249

            
250
    #[cfg_attr(esp32s3, unsafe(link_section = ".rwtext"))]
251
    fn esp32_init() {
252
        unsafe {
253
            super::configure_cpu_caches();
254
        }
255

            
256
        crate::interrupt::setup_interrupts();
257
    }
258
}
259

            
260
#[cfg(feature = "rt")]
261
#[unsafe(export_name = "__stack_chk_fail")]
262
unsafe extern "C" fn stack_chk_fail() {
263
    panic!("Stack corruption detected");
264
}
265

            
266
#[cfg(all(feature = "rt", riscv))]
267
fn setup_stack_guard() {
268
    unsafe extern "C" {
269
        static mut __stack_chk_guard: u32;
270
    }
271

            
272
    unsafe {
273
        let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
274
        // we _should_ use a random value but we don't have a good source for random
275
        // numbers here
276
        stack_chk_guard.write_volatile(esp_config::esp_config_int!(
277
            u32,
278
            "ESP_HAL_CONFIG_STACK_GUARD_VALUE"
279
        ));
280
    }
281
}
282

            
283
#[cfg(all(init_stack_ptr_range_check, feature = "rt"))]
284
pub(crate) fn ensure_stack_pointer_in_range() {
285
    unsafe extern "C" {
286
        static _stack_end_cpu0: u32;
287
        static _stack_start_cpu0: u32;
288
    }
289
    let current_sp: usize;
290
    cfg_select! {
291
        xtensa => unsafe {
292
            core::arch::asm!("mov {0}, sp", out(reg) current_sp);
293
        },
294
        _ => unsafe {
295
325
            core::arch::asm!("mv {0}, sp", out(reg) current_sp);
296
        },
297
    }
298
325
    let stack_bottom = (&raw const _stack_end_cpu0) as usize;
299
325
    let stack_top = (&raw const _stack_start_cpu0) as usize;
300
    assert!(
301
325
        current_sp > stack_bottom && current_sp <= stack_top,
302
        "stack pointer out of range: sp=0x{:x}, bottom=0x{:x}, top=0x{:x}",
303
        current_sp,
304
        stack_bottom,
305
        stack_top
306
    );
307
}
308

            
309
#[cfg(all(feature = "rt", stack_guard_monitoring))]
310
pub(crate) fn enable_main_stack_guard_monitoring() {
311
    unsafe {
312
        unsafe extern "C" {
313
            static mut __stack_chk_guard: u32;
314
        }
315

            
316
        let guard_addr = core::ptr::addr_of_mut!(__stack_chk_guard) as *mut _ as u32;
317
        crate::debugger::set_stack_watchpoint(guard_addr as usize);
318
    }
319
}
320

            
321
#[cfg(all(riscv, write_vec_table_monitoring))]
322
pub(crate) fn trap_section_protected() -> bool {
323
    cfg!(stack_guard_monitoring_with_debugger_connected) || !crate::debugger::debugger_connected()
324
}
325

            
326
#[cfg(all(riscv, write_vec_table_monitoring))]
327
pub(crate) fn setup_trap_section_protection() {
328
    if !trap_section_protected() {
329
        return;
330
    }
331

            
332
    unsafe extern "C" {
333
        static _rwtext_len: u32;
334
        static _trap_section_origin: u32;
335
    }
336

            
337
    let rwtext_len = core::ptr::addr_of!(_rwtext_len) as usize;
338

            
339
    // protect as much as possible via NAPOT
340
    let len = 1 << (usize::BITS - rwtext_len.leading_zeros() - 1) as usize;
341
    if len == 0 {
342
        warn!("No trap vector protection available");
343
        return;
344
    }
345

            
346
    // protect MTVEC and trap handlers
347
    // (probably plus some more bytes because of NAPOT)
348
    // via watchpoint 1.
349
    //
350
    // Why not use PMP? On C2/C3 the bootloader locks all available PMP entries.
351
    // And additionally we write to MTVEC for direct-vectoring and we write
352
    // to __EXTERNAL_INTERRUPTS when setting an interrupt handler.
353
    let addr = core::ptr::addr_of!(_trap_section_origin) as usize;
354

            
355
    unsafe {
356
        crate::debugger::set_watchpoint(1, addr, len);
357
    }
358
}
359

            
360
#[cfg(all(feature = "rt", enable_pmp, riscv))]
361
pub(crate) fn enable_pmp() {
362
    use core::arch::asm;
363

            
364
    #[derive(Debug, PartialEq, Eq)]
365
    enum PmpError {
366
        NoFreeEntries,
367
        InvalidRange,
368
    }
369

            
370
975
    unsafe fn pmp_add_tor_region(
371
        start_addr: u32,
372
        end_addr: u32,
373
        permission: u8,
374
    ) -> Result<(), PmpError> {
375
        // The CPU hardwires the address bits below the PMP granularity to zero, rounding both ends
376
        // of the region down. Round the start up instead, so that the region cannot grow downwards
377
        // over memory in front of it, such as the trap section and its interrupt handler table.
378
        let granularity = property!("soc.cpu_pmp_granularity");
379
        let start_addr = start_addr.next_multiple_of(granularity);
380
975
        let end_addr = end_addr & !(granularity - 1);
381

            
382
975
        if start_addr >= end_addr {
383
            return Err(PmpError::InvalidRange);
384
        }
385

            
386
        const MAX_PMP_ENTRIES: usize = 16;
387
        let mut free_idx = None;
388

            
389
        // 1. Find two consecutive unpopulated PMP entries
390
        for i in 1..MAX_PMP_ENTRIES {
391
            unsafe {
392
975
                if is_pmp_entry_free(i - 1) && is_pmp_entry_free(i) {
393
                    free_idx = Some(i);
394
                    break;
395
                }
396
            }
397
        }
398

            
399
        let idx = free_idx.ok_or(PmpError::NoFreeEntries)?;
400
        let start_idx = idx - 1;
401
        let end_idx = idx;
402

            
403
        // 2. Write the addresses to pmpaddr registers
404
        // For TOR: pmpaddr[idx-1] = start_bits, pmpaddr[idx] = end_bits
405
        // PMP addresses shift physical addresses down by 2 bits (34-bit physical address support)
406
975
        let pmpaddr_start = start_addr >> 2;
407
975
        let pmpaddr_end = end_addr >> 2;
408

            
409
        unsafe {
410
975
            write_pmpaddr(start_idx, pmpaddr_start);
411
975
            write_pmpaddr(end_idx, pmpaddr_end);
412
        }
413

            
414
        // 3. Configure the PMP settings
415
        unsafe {
416
            modify_pmp_config(start_idx, /* 0b00 | */ 1 << 7); // Off, no permissions, locked
417
975
            modify_pmp_config(end_idx, 0b01 << 3 | 1 << 7 | permission); // TOR, locked
418
        }
419

            
420
        // 4. Flush instruction cache / pipeline to ensure PMP takes effect immediately
421
        unsafe {
422
975
            asm!("fence", "fence.i", options(nostack));
423
        }
424

            
425
        Ok(())
426
975
    }
427

            
428
    /// Returns whether a PMP entry is unlocked and disabled (address matching OFF).
429
    unsafe fn is_pmp_entry_free(idx: usize) -> bool {
430
975
        let cfg_reg = idx / 4;
431
        let byte_offset = idx % 4;
432

            
433
        let cfg_val = unsafe { read_pmpcfg(cfg_reg) };
434
975
        let entry_cfg = (cfg_val >> (byte_offset * 8)) & 0xFF;
435

            
436
        // Unlocked and no permissions or address-matching mode configured.
437
975
        (entry_cfg & 0x9F) == 0
438
    }
439

            
440
    /// Helper to read pmpcfgX registers dynamically
441
    unsafe fn read_pmpcfg(reg: usize) -> u32 {
442
        let mut val: u32;
443
        unsafe {
444
975
            match reg {
445
975
                0 => asm!("csrr {}, pmpcfg0", out(reg) val),
446
                1 => asm!("csrr {}, pmpcfg1", out(reg) val),
447
                2 => asm!("csrr {}, pmpcfg2", out(reg) val),
448
                3 => asm!("csrr {}, pmpcfg3", out(reg) val),
449
                _ => panic!("Invalid PMP config register"),
450
            }
451
        }
452
        val
453
    }
454

            
455
    /// Helper to write to a dynamically chosen pmpcfgX register byte slot
456
    unsafe fn modify_pmp_config(idx: usize, byte_cfg: u8) {
457
        let reg = idx / 4;
458
        let byte_offset = idx % 4;
459
        let bit_shift = byte_offset * 8;
460

            
461
        let mut current_val = unsafe { read_pmpcfg(reg) };
462
        // Clear old configuration byte
463
975
        current_val &= !(0xFF << bit_shift);
464
        // Set new configuration byte
465
975
        current_val |= (byte_cfg as u32) << bit_shift;
466

            
467
        unsafe {
468
            match reg {
469
975
                0 => asm!("csrw pmpcfg0, {}", in(reg) current_val),
470
                1 => asm!("csrw pmpcfg1, {}", in(reg) current_val),
471
                2 => asm!("csrw pmpcfg2, {}", in(reg) current_val),
472
                3 => asm!("csrw pmpcfg3, {}", in(reg) current_val),
473
                _ => panic!("Invalid PMP config register"),
474
            }
475
        }
476
    }
477

            
478
    /// Helper to write to a pmpaddrX register
479
    unsafe fn write_pmpaddr(idx: usize, val: u32) {
480
        unsafe {
481
1950
            match idx {
482
975
                0 => asm!("csrw pmpaddr0, {}", in(reg) val),
483
975
                1 => asm!("csrw pmpaddr1, {}", in(reg) val),
484
                2 => asm!("csrw pmpaddr2, {}", in(reg) val),
485
                3 => asm!("csrw pmpaddr3, {}", in(reg) val),
486
                4 => asm!("csrw pmpaddr4, {}", in(reg) val),
487
                5 => asm!("csrw pmpaddr5, {}", in(reg) val),
488
                6 => asm!("csrw pmpaddr6, {}", in(reg) val),
489
                7 => asm!("csrw pmpaddr7, {}", in(reg) val),
490
                8 => asm!("csrw pmpaddr8, {}", in(reg) val),
491
                9 => asm!("csrw pmpaddr9, {}", in(reg) val),
492
                10 => asm!("csrw pmpaddr10, {}", in(reg) val),
493
                11 => asm!("csrw pmpaddr11, {}", in(reg) val),
494
                12 => asm!("csrw pmpaddr12, {}", in(reg) val),
495
                13 => asm!("csrw pmpaddr13, {}", in(reg) val),
496
                14 => asm!("csrw pmpaddr14, {}", in(reg) val),
497
                15 => asm!("csrw pmpaddr15, {}", in(reg) val),
498
                _ => panic!("Invalid PMP address register index"),
499
            }
500
        }
501
975
    }
502

            
503
    // protect .rwtext
504
    unsafe {
505
        unsafe extern "C" {
506
            static _rwtext_start: u32;
507
            static _rwtext_end: u32;
508
        }
509

            
510
        #[allow(clippy::if_same_then_else, reason = "False positive")]
511
325
        if pmp_add_tor_region(
512
            &_rwtext_start as *const _ as u32,
513
            &_rwtext_end as *const _ as u32,
514
            0b101, // X-R
515
        )
516
        .is_ok()
517
        {
518
            debug!("PMP write protection enabled for .rwtext");
519
        } else {
520
            debug!("Unable to enable PMP for .rwtext");
521
        }
522
    }
523

            
524
    // protect .stack
525
    unsafe {
526
        unsafe extern "C" {
527
            static _stack_end_cpu0: u32;
528
            static _stack_start_cpu0: u32;
529
        }
530

            
531
        #[allow(clippy::if_same_then_else, reason = "False positive")]
532
325
        if pmp_add_tor_region(
533
            &_stack_end_cpu0 as *const _ as u32,
534
            &_stack_start_cpu0 as *const _ as u32,
535
            0b011, // -WR
536
        )
537
        .is_ok()
538
        {
539
            debug!("PMP execute protection enabled for .stack");
540
        } else {
541
            debug!("Unable to enable PMP for .stack");
542
        }
543
    }
544

            
545
    // protect data
546
    unsafe {
547
        unsafe extern "C" {
548
            static _data_start: u32;
549
            static _noinit_end: u32;
550
        }
551

            
552
        #[allow(clippy::if_same_then_else, reason = "False positive")]
553
325
        if pmp_add_tor_region(
554
            &_data_start as *const _ as u32,
555
            &_noinit_end as *const _ as u32,
556
            0b011, // -WR
557
        )
558
        .is_ok()
559
        {
560
            debug!("PMP execute protection enabled for data");
561
        } else {
562
            debug!("Unable to enable PMP for data");
563
        }
564
    }
565
}
566

            
567
static CHIP_REVISION: AtomicU32 = AtomicU32::new(0);
568
const LOADED: u32 = 1 << 31;
569
const MAX_REVISION: ChipRevision = ChipRevision::from_packed(0xFFFF);
570

            
571
#[cold]
572
fn load_chip_revision_from_efuse() -> u16 {
573
    let chip_revision = crate::efuse::chip_revision();
574
    let chip_revision = chip_revision.packed();
575
    CHIP_REVISION.store(chip_revision as u32 | LOADED, Ordering::Release);
576
    chip_revision
577
}
578

            
579
#[ram]
580
fn load_chip_revision() -> ChipRevision {
581
    let stored = CHIP_REVISION.load(Ordering::Acquire);
582
    if stored & LOADED == 0 {
583
        return ChipRevision::from_packed(load_chip_revision_from_efuse());
584
    }
585
    ChipRevision::from_packed((stored & u16::MAX as u32) as u16)
586
}
587

            
588
fn chip_revision_in_range(range: Range<ChipRevision>) -> bool {
589
    const BUILD_TIME_MIN_REV: ChipRevision = ChipRevision::from_combined(
590
        esp_config::esp_config_int!(u16, "ESP_HAL_CONFIG_MIN_CHIP_REVISION"),
591
    );
592

            
593
    // Check to determine chip is obviously in or out of range, without reading efuse
594
    #[allow(
595
        clippy::absurd_extreme_comparisons,
596
        reason = "Not absurd depending on configuration"
597
    )]
598
    if range.end < BUILD_TIME_MIN_REV {
599
        // Chip will not boot in this range
600
        return false;
601
    }
602

            
603
    #[allow(
604
        clippy::absurd_extreme_comparisons,
605
        reason = "Not absurd depending on configuration"
606
    )]
607
    if range.start <= BUILD_TIME_MIN_REV && range.end == MAX_REVISION {
608
        return true;
609
    }
610

            
611
    let chip_revision = load_chip_revision();
612

            
613
    range.start <= chip_revision && chip_revision < range.end
614
}
615

            
616
/// Returns whether the chip revision is at least the given revision.
617
#[allow(dead_code)]
618
pub(crate) fn chip_revision_above(revision: ChipRevision) -> bool {
619
    chip_revision_in_range(revision..MAX_REVISION)
620
}
621

            
622
/// Returns whether the chip is at least the given revision, in the same major version.
623
#[allow(dead_code)]
624
pub(crate) fn chip_minor_revision_above(revision: ChipRevision) -> bool {
625
    let next_major = ChipRevision {
626
        major: revision.major + 1,
627
        minor: 0,
628
    };
629
    chip_revision_in_range(revision..next_major)
630
}