Skip to main content

ax_task/sync/bridge/
context.rs

1use crate::sync::context::{ContextBackend, enter_preempt_irqsave, exit_preempt_irqsave};
2
3pub(super) const CONTEXT_RAW: u8 = 0;
4pub(super) const CONTEXT_PREEMPT: u8 = 1;
5pub(super) const CONTEXT_IRQSAVE: u8 = 2;
6pub(super) const CONTEXT_PREEMPT_IRQSAVE: u8 = 3;
7
8/// Runtime callbacks used by the external synchronization provider.
9#[derive(Clone, Copy)]
10pub struct ContextOperations {
11    pub preempt_enter: fn() -> usize,
12    pub preempt_exit: unsafe fn(usize),
13    pub preempt_exit_irq_return: unsafe fn(usize),
14    pub irq_save_and_disable: fn() -> usize,
15    pub irq_restore: unsafe fn(usize),
16    pub hardirq_enter: fn(),
17    pub hardirq_exit: fn(),
18}
19
20struct ExternalContext<'ops>(&'ops ContextOperations);
21
22impl ContextBackend for ExternalContext<'_> {
23    type PreemptState = usize;
24    type IrqState = usize;
25
26    fn preempt_enter(&self) -> Self::PreemptState {
27        (self.0.preempt_enter)()
28    }
29
30    fn preempt_exit(&self, state: Self::PreemptState) {
31        // SAFETY: every caller passes a token returned by this backend.
32        unsafe { (self.0.preempt_exit)(state) };
33    }
34
35    fn preempt_exit_irq_return(&self, state: Self::PreemptState) {
36        // SAFETY: every caller passes the paired token while IRQs remain disabled.
37        unsafe { (self.0.preempt_exit_irq_return)(state) };
38    }
39
40    fn irq_save_and_disable(&self) -> Self::IrqState {
41        (self.0.irq_save_and_disable)()
42    }
43
44    fn irq_restore(&self, state: Self::IrqState) {
45        // SAFETY: every caller passes the state returned by this backend.
46        unsafe { (self.0.irq_restore)(state) };
47    }
48}
49
50/// Opaque execution-context state returned to an external wrapper.
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52#[repr(C)]
53pub struct ContextState {
54    preempt: usize,
55    irq: usize,
56}
57
58impl ContextState {
59    pub const fn new(preempt: usize, irq: usize) -> Self {
60        Self { preempt, irq }
61    }
62
63    pub const fn preempt(self) -> usize {
64        self.preempt
65    }
66
67    pub const fn irq(self) -> usize {
68        self.irq
69    }
70}
71
72pub fn context_enter(context: u8, operations: &ContextOperations) -> ContextState {
73    let backend = ExternalContext(operations);
74    match context {
75        CONTEXT_RAW => ContextState::new(0, 0),
76        CONTEXT_PREEMPT => ContextState::new(backend.preempt_enter(), 0),
77        CONTEXT_IRQSAVE => ContextState::new(0, backend.irq_save_and_disable()),
78        CONTEXT_PREEMPT_IRQSAVE => {
79            let (preempt, irq) = enter_preempt_irqsave(&backend);
80            ContextState::new(preempt, irq)
81        }
82        _ => panic!("unknown external lock context {context}"),
83    }
84}
85
86pub fn context_exit(context: u8, state: ContextState, operations: &ContextOperations) {
87    let backend = ExternalContext(operations);
88    match context {
89        CONTEXT_RAW => {}
90        CONTEXT_PREEMPT => backend.preempt_exit(state.preempt),
91        CONTEXT_IRQSAVE => backend.irq_restore(state.irq),
92        CONTEXT_PREEMPT_IRQSAVE => {
93            exit_preempt_irqsave((state.preempt, state.irq), &backend);
94        }
95        _ => panic!("unknown external lock context {context}"),
96    }
97}
98
99pub fn irq_return_preempt_enter(operations: &ContextOperations) -> usize {
100    ExternalContext(operations).preempt_enter()
101}
102
103/// # Safety
104///
105/// `state` must come from [`irq_return_preempt_enter`] on the same execution
106/// context and remain nested inside the active raw IRQ-save guard.
107pub unsafe fn irq_return_preempt_exit(state: usize, operations: &ContextOperations) {
108    ExternalContext(operations).preempt_exit_irq_return(state);
109}
110
111pub fn hardirq_enter(operations: &ContextOperations) {
112    (operations.hardirq_enter)();
113}
114
115pub fn hardirq_exit(operations: &ContextOperations) {
116    (operations.hardirq_exit)();
117}
118
119#[cfg(test)]
120mod tests {
121    use std::{sync::Mutex, vec::Vec};
122
123    use super::*;
124
125    const PREEMPT_TOKEN: usize = 0x1111;
126    const IRQ_TOKEN: usize = 0x2222;
127
128    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
129    enum Event {
130        PreemptEnter,
131        IrqDisable,
132        IrqRestore(usize),
133        PreemptExit(usize),
134    }
135
136    static TEST_LOCK: Mutex<()> = Mutex::new(());
137    static EVENTS: Mutex<Vec<Event>> = Mutex::new(Vec::new());
138
139    fn record(event: Event) {
140        EVENTS.lock().unwrap().push(event);
141    }
142
143    fn preempt_enter() -> usize {
144        record(Event::PreemptEnter);
145        PREEMPT_TOKEN
146    }
147
148    unsafe fn preempt_exit(state: usize) {
149        record(Event::PreemptExit(state));
150    }
151
152    unsafe fn preempt_exit_irq_return(_state: usize) {}
153
154    fn irq_save_and_disable() -> usize {
155        record(Event::IrqDisable);
156        IRQ_TOKEN
157    }
158
159    unsafe fn irq_restore(state: usize) {
160        record(Event::IrqRestore(state));
161    }
162
163    fn no_op() {}
164
165    fn operations() -> ContextOperations {
166        ContextOperations {
167            preempt_enter,
168            preempt_exit,
169            preempt_exit_irq_return,
170            irq_save_and_disable,
171            irq_restore,
172            hardirq_enter: no_op,
173            hardirq_exit: no_op,
174        }
175    }
176
177    fn take_events() -> Vec<Event> {
178        core::mem::take(&mut *EVENTS.lock().unwrap())
179    }
180
181    #[test]
182    fn combined_bridge_preserves_tokens_and_restores_irq_before_preempt() {
183        let _serial = TEST_LOCK.lock().unwrap();
184        take_events();
185
186        let state = context_enter(CONTEXT_PREEMPT_IRQSAVE, &operations());
187        assert_eq!(state, ContextState::new(PREEMPT_TOKEN, IRQ_TOKEN));
188        assert_eq!(take_events(), [Event::PreemptEnter, Event::IrqDisable]);
189
190        context_exit(CONTEXT_PREEMPT_IRQSAVE, state, &operations());
191        assert_eq!(
192            take_events(),
193            [
194                Event::IrqRestore(IRQ_TOKEN),
195                Event::PreemptExit(PREEMPT_TOKEN)
196            ]
197        );
198    }
199
200    #[test]
201    fn raw_bridge_does_not_invoke_callbacks() {
202        let _serial = TEST_LOCK.lock().unwrap();
203        take_events();
204
205        let state = context_enter(CONTEXT_RAW, &operations());
206        assert_eq!(state, ContextState::new(0, 0));
207        context_exit(CONTEXT_RAW, state, &operations());
208
209        assert!(take_events().is_empty());
210    }
211}