Skip to main content

ax_task/sync/
context.rs

1//! Execution-context guards used by the synchronization primitives.
2
3use core::marker::PhantomData;
4
5/// Saves the local interrupt state and disables local interrupts.
6///
7/// This low-level entry point exists for capability adapters whose trait API
8/// transports the saved interrupt state separately from an RAII guard.
9#[doc(hidden)]
10#[inline(always)]
11pub fn irq_save_and_disable() -> usize {
12    imp::irq_save_and_disable()
13}
14
15/// Restores a local interrupt state returned by [`irq_save_and_disable`].
16///
17/// # Safety
18///
19/// `state` must come from the matching save operation on the current CPU and
20/// must be restored exactly once, in properly nested order.
21#[doc(hidden)]
22#[inline(always)]
23pub unsafe fn irq_restore(state: usize) {
24    imp::irq_restore(state);
25}
26
27/// Internal critical-section contract used by spin-lock guards.
28#[doc(hidden)]
29pub trait GuardState {
30    /// Saved state needed when the guard is released.
31    type State: Clone + Copy;
32
33    /// Enters the critical section.
34    fn acquire() -> Self::State;
35
36    /// Leaves the critical section.
37    fn release(state: Self::State);
38
39    /// Returns whether locks using this state participate in task lockdep.
40    fn lockdep_enabled() -> bool {
41        false
42    }
43}
44
45/// Owns an entered critical section until a lock guard takes it over.
46///
47/// Lockdep validation can panic in host tests. Keeping the state in this
48/// temporary guard ensures that every acquisition path restores its execution
49/// context while unwinding.
50pub(crate) struct PendingGuardState<G: GuardState> {
51    state: Option<G::State>,
52}
53
54impl<G: GuardState> PendingGuardState<G> {
55    #[inline(always)]
56    pub(crate) fn acquire() -> Self {
57        Self {
58            state: Some(G::acquire()),
59        }
60    }
61
62    #[inline(always)]
63    pub(crate) fn into_state(mut self) -> G::State {
64        self.state
65            .take()
66            .expect("pending guard state must be present")
67    }
68}
69
70impl<G: GuardState> Drop for PendingGuardState<G> {
71    #[inline(always)]
72    fn drop(&mut self) {
73        if let Some(state) = self.state.take() {
74            G::release(state);
75        }
76    }
77}
78
79/// Raw lock state which does not alter the execution context.
80#[doc(hidden)]
81pub struct RawState;
82
83/// Lock state which disables kernel preemption.
84#[doc(hidden)]
85pub struct PreemptState;
86
87impl PreemptState {
88    pub(crate) fn release_from_irq_return(state: usize) {
89        imp::enable_preempt_from_irq_return(state);
90    }
91}
92
93/// Lock state which saves and disables local interrupts.
94#[doc(hidden)]
95pub struct IrqSaveState;
96
97/// Lock state which disables preemption, then saves and disables interrupts.
98#[doc(hidden)]
99pub struct PreemptIrqSaveState;
100
101impl GuardState for RawState {
102    type State = ();
103
104    #[inline(always)]
105    fn acquire() -> Self::State {}
106
107    #[inline(always)]
108    fn release(_state: Self::State) {}
109}
110
111impl GuardState for PreemptState {
112    type State = usize;
113
114    #[inline(always)]
115    fn acquire() -> Self::State {
116        imp::disable_preempt()
117    }
118
119    #[inline(always)]
120    fn release(state: Self::State) {
121        imp::enable_preempt(state);
122    }
123
124    fn lockdep_enabled() -> bool {
125        true
126    }
127}
128
129impl GuardState for IrqSaveState {
130    type State = usize;
131
132    #[inline(always)]
133    fn acquire() -> Self::State {
134        imp::irq_save_and_disable()
135    }
136
137    #[inline(always)]
138    fn release(state: Self::State) {
139        imp::irq_restore(state);
140    }
141}
142
143impl GuardState for PreemptIrqSaveState {
144    type State = usize;
145
146    #[inline(always)]
147    fn acquire() -> Self::State {
148        let preemption = imp::disable_preempt();
149        assert_eq!(preemption & 1, 0, "preemption token must be aligned");
150        preemption | (imp::irq_save_and_disable() & 1)
151    }
152
153    #[inline(always)]
154    fn release(state: Self::State) {
155        imp::irq_restore(state & 1);
156        imp::enable_preempt(state & !1);
157    }
158
159    fn lockdep_enabled() -> bool {
160        true
161    }
162}
163
164/// An RAII guard which disables kernel preemption while it is alive.
165///
166/// The guard is bound to the task that acquires it and cannot be transferred
167/// to another execution context:
168///
169/// ```compile_fail
170/// fn require_send<T: Send>() {}
171/// require_send::<ax_task::sync::PreemptGuard>();
172/// ```
173pub struct PreemptGuard {
174    state: <PreemptState as GuardState>::State,
175    _not_send: PhantomData<*mut ()>,
176}
177
178impl PreemptGuard {
179    /// Disables preemption and creates a guard which restores it on drop.
180    pub fn new() -> Self {
181        Self {
182            state: PreemptState::acquire(),
183            _not_send: PhantomData,
184        }
185    }
186}
187
188impl Default for PreemptGuard {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194impl Drop for PreemptGuard {
195    fn drop(&mut self) {
196        PreemptState::release(self.state);
197    }
198}
199
200/// An RAII guard which saves and disables local interrupts while it is alive.
201///
202/// The saved IRQ state belongs to the acquiring CPU, so the guard cannot be
203/// transferred to another execution context:
204///
205/// ```compile_fail
206/// fn require_send<T: Send>() {}
207/// require_send::<ax_task::sync::IrqSaveGuard>();
208/// ```
209pub struct IrqSaveGuard {
210    state: <IrqSaveState as GuardState>::State,
211    _not_send: PhantomData<*mut ()>,
212}
213
214impl IrqSaveGuard {
215    /// Saves and disables local interrupts.
216    pub fn new() -> Self {
217        Self {
218            state: IrqSaveState::acquire(),
219            _not_send: PhantomData,
220        }
221    }
222}
223
224impl Default for IrqSaveGuard {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230impl Drop for IrqSaveGuard {
231    fn drop(&mut self) {
232        IrqSaveState::release(self.state);
233    }
234}
235
236/// An RAII guard which disables preemption and local interrupts.
237///
238/// Entry disables preemption before interrupts. Drop restores interrupts
239/// before re-enabling preemption, matching Linux spin-lock IRQ-save ordering.
240///
241/// Both saved states belong to the acquiring task and CPU, so the guard cannot
242/// be transferred to another execution context:
243///
244/// ```compile_fail
245/// fn require_send<T: Send>() {}
246/// require_send::<ax_task::sync::PreemptIrqSaveGuard>();
247/// ```
248pub struct PreemptIrqSaveGuard {
249    state: <PreemptIrqSaveState as GuardState>::State,
250    _not_send: PhantomData<*mut ()>,
251}
252
253impl PreemptIrqSaveGuard {
254    /// Enters a preemption-disabled, IRQ-disabled critical section.
255    pub fn new() -> Self {
256        Self {
257            state: PreemptIrqSaveState::acquire(),
258            _not_send: PhantomData,
259        }
260    }
261}
262
263impl Default for PreemptIrqSaveGuard {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269impl Drop for PreemptIrqSaveGuard {
270    fn drop(&mut self) {
271        PreemptIrqSaveState::release(self.state);
272    }
273}
274
275#[cfg(all(feature = "host-test", not(target_os = "none")))]
276mod imp {
277    use std::cell::{Cell, RefCell};
278
279    std::thread_local! {
280        static PREEMPT_DEPTH: Cell<usize> = const { Cell::new(0) };
281        static IRQ_ENABLED: Cell<bool> = const { Cell::new(true) };
282        static EVENTS: RefCell<std::vec::Vec<&'static str>> = const { RefCell::new(std::vec::Vec::new()) };
283    }
284
285    pub(super) fn disable_preempt() -> usize {
286        EVENTS.with_borrow_mut(|events| events.push("preempt-disable"));
287        PREEMPT_DEPTH.set(PREEMPT_DEPTH.get() + 1);
288        0
289    }
290
291    pub(super) fn enable_preempt(_state: usize) {
292        EVENTS.with_borrow_mut(|events| events.push("preempt-enable"));
293        PREEMPT_DEPTH.set(
294            PREEMPT_DEPTH
295                .get()
296                .checked_sub(1)
297                .expect("unbalanced preemption guard"),
298        );
299    }
300
301    pub(super) fn enable_preempt_from_irq_return(state: usize) {
302        enable_preempt(state);
303    }
304
305    pub(super) fn irq_save_and_disable() -> usize {
306        EVENTS.with_borrow_mut(|events| events.push("irq-disable"));
307        let was_enabled = IRQ_ENABLED.replace(false);
308        usize::from(was_enabled)
309    }
310
311    pub(super) fn irq_restore(state: usize) {
312        EVENTS.with_borrow_mut(|events| events.push("irq-restore"));
313        IRQ_ENABLED.set(state != 0);
314    }
315
316    pub(super) fn snapshot() -> (usize, bool) {
317        (PREEMPT_DEPTH.get(), IRQ_ENABLED.get())
318    }
319
320    #[cfg(all(test, feature = "host-test", not(target_os = "none")))]
321    pub(super) fn take_events() -> std::vec::Vec<&'static str> {
322        EVENTS.take()
323    }
324
325    pub(super) fn preempt_depth() -> usize {
326        PREEMPT_DEPTH.get()
327    }
328
329    #[cfg(feature = "preempt")]
330    pub(super) fn finish_initial_context_switch() {
331        assert_eq!(
332            PREEMPT_DEPTH.get(),
333            1,
334            "initial host context switch must inherit one exclusion depth"
335        );
336        PREEMPT_DEPTH.set(0);
337    }
338}
339
340#[cfg(not(all(feature = "host-test", not(target_os = "none"))))]
341mod imp {
342    #[inline(always)]
343    pub(super) fn disable_preempt() -> usize {
344        #[cfg(feature = "multitask")]
345        return crate::disable_preempt();
346        #[cfg(not(feature = "multitask"))]
347        0
348    }
349
350    #[inline(always)]
351    pub(super) fn enable_preempt(state: usize) {
352        #[cfg(feature = "multitask")]
353        crate::enable_preempt(state);
354        #[cfg(not(feature = "multitask"))]
355        let _ = state;
356    }
357
358    #[inline(always)]
359    pub(super) fn enable_preempt_from_irq_return(state: usize) {
360        #[cfg(feature = "multitask")]
361        crate::enable_preempt_from_irq_return(state);
362        #[cfg(not(feature = "multitask"))]
363        let _ = state;
364    }
365
366    #[inline(always)]
367    pub(super) fn irq_save_and_disable() -> usize {
368        let was_enabled = ax_hal::asm::irqs_enabled();
369        ax_hal::asm::disable_irqs();
370        usize::from(was_enabled)
371    }
372
373    #[inline(always)]
374    pub(super) fn irq_restore(state: usize) {
375        if state != 0 {
376            ax_hal::asm::enable_irqs();
377        } else {
378            ax_hal::asm::disable_irqs();
379        }
380    }
381}
382
383/// Returns the preemption depth tracked by the host critical-section provider.
384#[cfg(all(feature = "host-test", not(target_os = "none")))]
385#[doc(hidden)]
386pub fn host_preempt_depth() -> usize {
387    imp::preempt_depth()
388}
389
390#[cfg(all(feature = "preempt", feature = "host-test", not(target_os = "none")))]
391pub(crate) fn finish_initial_host_context_switch() {
392    imp::finish_initial_context_switch();
393}
394
395#[cfg(all(feature = "host-test", not(target_os = "none")))]
396pub(crate) fn host_context_snapshot() -> (usize, bool) {
397    imp::snapshot()
398}
399
400#[cfg(all(test, feature = "host-test", not(target_os = "none")))]
401mod tests {
402    use super::{IrqSaveGuard, PreemptGuard, PreemptIrqSaveGuard, imp};
403
404    #[test]
405    fn preempt_guard_nests_and_restores_depth() {
406        assert_eq!(imp::snapshot(), (0, true));
407        let outer = PreemptGuard::new();
408        assert_eq!(imp::snapshot(), (1, true));
409        {
410            let _inner = PreemptGuard::new();
411            assert_eq!(imp::snapshot(), (2, true));
412        }
413        assert_eq!(imp::snapshot(), (1, true));
414        drop(outer);
415        assert_eq!(imp::snapshot(), (0, true));
416    }
417
418    #[test]
419    fn irq_save_guard_preserves_nested_disabled_state() {
420        assert_eq!(imp::snapshot(), (0, true));
421        let outer = IrqSaveGuard::new();
422        assert_eq!(imp::snapshot(), (0, false));
423        {
424            let _inner = IrqSaveGuard::new();
425            assert_eq!(imp::snapshot(), (0, false));
426        }
427        assert_eq!(imp::snapshot(), (0, false));
428        drop(outer);
429        assert_eq!(imp::snapshot(), (0, true));
430    }
431
432    #[test]
433    fn combined_guard_restores_irq_before_preempt_context() {
434        assert_eq!(imp::snapshot(), (0, true));
435        let _ = imp::take_events();
436        let guard = PreemptIrqSaveGuard::new();
437        assert_eq!(imp::snapshot(), (1, false));
438        drop(guard);
439        assert_eq!(imp::snapshot(), (0, true));
440        assert_eq!(
441            imp::take_events(),
442            [
443                "preempt-disable",
444                "irq-disable",
445                "irq-restore",
446                "preempt-enable"
447            ]
448        );
449    }
450}