Skip to main content

esp_sync/
raw.rs

1//! TODO
2
3use core::sync::atomic::{Ordering, compiler_fence};
4
5use crate::RestoreState;
6
7/// Trait for single-core locks.
8pub trait RawLock {
9    /// Acquires the raw lock
10    ///
11    /// # Safety
12    ///
13    /// The returned tokens must be released in reverse order, on the same thread that they were
14    /// created on.
15    unsafe fn enter(&self) -> RestoreState;
16
17    /// Releases the raw lock
18    ///
19    /// # Safety
20    ///
21    /// - The `token` must be created by `self.enter()`
22    /// - Tokens must be released in reverse order to their creation, on the same thread that they
23    ///   were created on.
24    unsafe fn exit(&self, token: RestoreState);
25}
26
27/// A lock that disables interrupts.
28pub struct SingleCoreInterruptLock;
29
30// Reserved bits in the PS register, these must be written as 0.
31#[cfg(all(xtensa, debug_assertions))]
32const RESERVED_MASK: u32 = 0b1111_1111_1111_1000_1111_0000_0000_0000;
33
34impl RawLock for SingleCoreInterruptLock {
35    #[inline]
36    unsafe fn enter(&self) -> RestoreState {
37        cfg_select! {
38            esp32p4 => {
39                // TODO: any with zcmp
40                // ESP32-P4 (v3.2/ECO7 etc.) Zcmp hardware bug workaround (IDF-14279 / DIG-661):
41                // Clearing mstatus.mie alone does not fully mask CLIC interrupts -- an
42                // interrupt can still fire mid-instruction on cm.push (and possibly on
43                // other multi-cycle sequences). Fix: raise mintthresh (CSR 0x347) to 0xFF
44                // while mie is cleared, then restore the previous mintthresh on exit.
45                // Ref: esp-idf commit c27c33a83 "fix(riscv): implement a workaround for
46                // Zcmp hardware bug".
47                let old_mintthresh: u32;
48                unsafe {
49                    core::arch::asm!(
50                        "li   t0, 0xff",
51                        "csrrw {0}, 0x347, t0",
52                        out(reg) old_mintthresh,
53                        out("t0") _,
54                    );
55                }
56                let mut mstatus = 0u32;
57                unsafe {
58                    core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus);
59                }
60                let mie_bit = mstatus & 0b1000;
61                let token = mie_bit | ((old_mintthresh & 0xff) << 8);
62            }
63            riscv => {
64                let mut mstatus = 0u32;
65                unsafe {
66                    core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus);
67                }
68                let token = mstatus & 0b1000;
69            }
70            xtensa => {
71                let token: u32;
72                unsafe {
73                    core::arch::asm!("rsil {0}, 5", out(reg) token);
74                }
75                #[cfg(debug_assertions)]
76                let token = token & !RESERVED_MASK;
77            }
78            _ => {
79                compile_error!("Unsupported architecture")
80            }
81        };
82
83        // Ensure no subsequent memory accesses are reordered to before interrupts are
84        // disabled.
85        compiler_fence(Ordering::SeqCst);
86
87        unsafe { RestoreState::new(token) }
88    }
89
90    #[inline]
91    unsafe fn exit(&self, token: RestoreState) {
92        // Ensure no preceeding memory accesses are reordered to after interrupts are
93        // enabled.
94        compiler_fence(Ordering::SeqCst);
95
96        let token = token.inner();
97
98        cfg_select! {
99            esp32p4 => {
100                if (token & 0b1000) != 0 {
101                    unsafe {
102                        riscv::interrupt::enable();
103                    }
104                }
105                // Restore mintthresh AFTER re-enabling mie (P4 Zcmp workaround, see enter()).
106                let old_mintthresh = (token >> 8) & 0xff;
107                unsafe {
108                    core::arch::asm!(
109                        "csrw 0x347, {0}",
110                        in(reg) old_mintthresh,
111                    );
112                }
113
114                // The delay between the moment we unmask the interrupt threshold register
115                // and the moment the potential requested interrupt is triggered is not
116                // null: up to three machine cycles/instructions can be executed.
117                riscv::asm::nop();
118                riscv::asm::nop();
119                riscv::asm::nop();
120            }
121            riscv => {
122                if token != 0 {
123                    unsafe {
124                        riscv::interrupt::enable();
125                    }
126                }
127            }
128            xtensa => {
129                #[cfg(debug_assertions)]
130                if token & RESERVED_MASK != 0 {
131                    // We could do this transformation in fmt.rs automatically, but experiments
132                    // show this is only worth it in terms of binary size for code inlined into many
133                    // places.
134                    #[cold]
135                    #[inline(never)]
136                    fn __assert_failed() {
137                        panic!("Reserved bits in PS register must be written as 0");
138                    }
139
140                    __assert_failed();
141                }
142
143                unsafe {
144                    core::arch::asm!(
145                        "wsr.ps {0}",
146                        "rsync", in(reg) token)
147                }
148            }
149            _ => {
150                compile_error!("Unsupported architecture")
151            }
152        }
153    }
154}