1
//! Private implementation details of the RTOS that owns context switching.
2
//!
3
//! A context switch runs at [`Priority::min`][super::Priority::min]. It is the last action of a
4
//! CPU before the CPU returns to the interrupted context. An RTOS registers its context-switch
5
//! handler here, then asks a CPU to run the handler.
6
//!
7
//! These functions are not part of the application API.
8
#![cfg_attr(
9
    context_switch_source = "software0",
10
    doc = "\nA CPU switches its own tasks through the `Software0` CPU-internal interrupt. The HAL
11
    defines the handler of that interrupt."
12
)]
13
#![cfg_attr(
14
    context_switch_source = "clint",
15
    doc = "\nA CPU switches its own tasks through the machine software interrupt of the CLINT."
16
)]
17
#![cfg_attr(
18
    context_switch_source = "from_cpu",
19
    doc = "\nA CPU switches its own tasks through the `FROM_CPU_INTR0` interrupt. The HAL reserves
20
    that interrupt."
21
)]
22
#![cfg_attr(
23
    multi_core,
24
    doc = "\nA CPU switches the tasks of the other CPU through [`Ipc`][super::ipc::Ipc]."
25
)]
26

            
27
use crate::system::Cpu;
28
#[cfg(xtensa)]
29
use crate::trapframe::TrapFrame;
30

            
31
/// The signature of a context-switch handler.
32
///
33
/// On RISC-V, the handler runs as a direct-bound interrupt handler, and returns from the
34
/// interrupt itself. On Xtensa, the vectored dispatcher calls the handler with the trap frame,
35
/// and the interrupt returns to the context in that trap frame.
36
pub type ContextSwitchHandler = cfg_select! {
37
    riscv => {
38
        unsafe extern "C" fn()
39
    }
40
    xtensa => {
41
        extern "C" fn(&mut TrapFrame)
42
    }
43
};
44

            
45
/// Registers the context-switch handler of `core`.
46
///
47
/// A CPU accepts one context-switch handler. [`request_context_switch`] therefore takes no
48
/// handler of its own, and never waits.
49
///
50
/// # Panics
51
///
52
/// Panics if `core` already has a context-switch handler.
53
#[cfg_attr(
54
    not(context_switch_source = "ipc"),
55
    doc = "\nIn debug builds, panics if `core` is not the current CPU. A CPU-local interrupt
56
    carries the context switch, and only that CPU can enable the interrupt.\n"
57
)]
58
/// # Safety
59
///
60
/// On RISC-V, the interrupt jumps to `handler` with the interrupted `t0` in `mscratch`.
61
/// `handler` must restore `t0` from that CSR, save and restore every other register it
62
/// modifies, and return with `mret`.
63
///
64
/// On Xtensa, the interrupt returns to the context in the trap frame. `handler` must leave a
65
/// context that the CPU can run.
66
64
pub unsafe fn set_context_switch_handler(core: Cpu, handler: ContextSwitchHandler) {
67
    cfg_select! {
68
        context_switch_source = "ipc" => {}
69
        _ => {
70
            debug_assert_eq!(core, Cpu::current(), "A CPU installs its own handler");
71
            unsafe { local::install(handler) };
72
        }
73
    }
74

            
75
    // A CPU-local interrupt cannot reach the other CPU. Dual-core chips that keep
76
    // that interrupt for a local switch still register the same handler with IPC.
77
    #[cfg(multi_core)]
78
    unsafe {
79
        super::ipc::set_context_switch_handler(core, handler);
80
    }
81
}
82

            
83
/// Asks `core` to run its context-switch handler.
84
///
85
/// The handler runs at [`Priority::min`][super::Priority::min]. The call does not wait.
86
///
87
/// # Panics
88
///
89
/// In debug builds, panics if `core` has no context-switch handler.
90
#[cfg_attr(
91
    multi_core,
92
    doc = "\nIn debug builds, panics if `core` is the APP CPU and that CPU is not running."
93
)]
94
#[inline]
95
pub fn request_context_switch(core: Cpu) {
96
    cfg_select! {
97
        context_switch_source = "ipc" => {
98
            super::ipc::request_context_switch(core);
99
        }
100
        multi_core => {
101
            if core == Cpu::current() {
102
                local::trigger();
103
            } else {
104
                super::ipc::request_context_switch(core);
105
            }
106
        }
107
        _ => {
108
            debug_assert_eq!(core, Cpu::current(), "A CPU switches its own tasks");
109
            local::trigger();
110
        }
111
    }
112
}
113

            
114
/// The `Software0` context-switch path.
115
#[cfg(context_switch_source = "software0")]
116
mod local {
117
    use portable_atomic::{AtomicPtr, Ordering};
118

            
119
    use super::*;
120
    use crate::ram;
121

            
122
    /// `Software0` is CPU interrupt 7, which runs at the minimum priority.
123
    const SW_INTERRUPT: u32 = 1 << 7;
124

            
125
    static HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
126

            
127
    pub(super) unsafe fn install(handler: ContextSwitchHandler) {
128
        HANDLER.store(handler as *mut (), Ordering::Release);
129
        unsafe { xtensa_lx::interrupt::enable_mask(SW_INTERRUPT) };
130
    }
131

            
132
    #[inline]
133
    pub(super) fn trigger() {
134
        unsafe { xtensa_lx::interrupt::set(SW_INTERRUPT) };
135
    }
136

            
137
    /// The vectored dispatcher clears the request before it calls this handler.
138
    #[ram]
139
    #[unsafe(export_name = "Software0")]
140
    extern "C" fn context_switch(context: &mut TrapFrame) {
141
        let handler = HANDLER.load(Ordering::Acquire);
142
        debug_assert!(!handler.is_null(), "The CPU has no context switch handler");
143

            
144
        let handler = unsafe { core::mem::transmute::<*mut (), ContextSwitchHandler>(handler) };
145
        handler(context);
146
    }
147
}
148

            
149
/// The RISC-V context-switch path.
150
///
151
/// A CPU takes the context switch in a handler that this module binds directly to the
152
/// interrupt. The handler borrows `t0` only, and keeps the interrupted value in `mscratch`.
153
/// That CSR is free, because the hardware clears `mstatus.MIE` on trap entry, and nothing here
154
/// enables interrupts again.
155
#[cfg(any(context_switch_source = "from_cpu", context_switch_source = "clint"))]
156
mod local {
157
    use portable_atomic::{AtomicPtr, Ordering};
158

            
159
    use super::*;
160
    use crate::interrupt;
161

            
162
    cfg_select! {
163
        context_switch_source = "clint" => {
164
            use crate::{interrupt::CpuInterrupt, peripherals::CLINT};
165
        }
166
        _ => {
167
            use crate::{
168
                interrupt::software::SoftwareInterrupt,
169
                peripherals::{FROM_CPU_INTR0, Interrupt},
170
            };
171
        }
172
    }
173

            
174
    /// The address of the register that clears the request.
175
    static REQUEST: AtomicPtr<u32> = AtomicPtr::new(core::ptr::null_mut());
176
    static HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
177

            
178
    pub(super) unsafe fn install(handler: ContextSwitchHandler) {
179
        HANDLER.store(handler as *mut (), Ordering::Release);
180

            
181
        cfg_select! {
182
            context_switch_source = "clint" => {
183
                REQUEST.store(CLINT::regs().msip().as_ptr(), Ordering::Release);
184

            
185
                // Both controllers serve the machine software interrupt as interrupt 3. The
186
                // CLIC applies the priority of that line to it, and needs the line enabled.
187
                // The PLIC does not serve the interrupt at all: `mie` enables it, and the run
188
                // level masks it.
189
                interrupt::bind_cpu_interrupt(CpuInterrupt::Interrupt3, stub);
190
                #[cfg(interrupt_controller = "clic")]
191
                interrupt::enable_cpu_interrupt(
192
                    CpuInterrupt::Interrupt3,
193
                    interrupt::Priority::min(),
194
                );
195
            }
196
            _ => {
197
                let regs = cfg_select! {
198
                    soc_has_intpri => crate::peripherals::INTPRI::regs(),
199
                    _ => crate::peripherals::SYSTEM::regs(),
200
                };
201
                REQUEST.store(regs.cpu_intr_from_cpu(0).as_ptr(), Ordering::Release);
202

            
203
64
                interrupt::enable_direct_inner(
204
                    Interrupt::FROM_CPU_INTR0,
205
                    interrupt::Priority::min(),
206
                    interrupt::IPC_INTERRUPT,
207
                    stub,
208
                );
209
            }
210
        }
211
    }
212

            
213
    #[inline]
214
    pub(super) fn trigger() {
215
        cfg_select! {
216
            context_switch_source = "clint" => {
217
                CLINT::regs().msip().write(|w| w.msip().set_bit());
218
            }
219
            _ => {
220
565
                SoftwareInterrupt::new(unsafe { FROM_CPU_INTR0::steal() }).raise();
221
            }
222
        }
223
    }
224

            
225
    /// Clears the request, then jumps to the context-switch handler.
226
    #[cfg_attr(
227
        not(interrupt_controller = "clic"),
228
        unsafe(link_section = ".trap.rust")
229
    )]
230
    #[cfg_attr(interrupt_controller = "clic", unsafe(link_section = ".rwtext"))]
231
    #[unsafe(naked)]
232
    unsafe extern "C" fn stub() {
233
        core::arch::naked_asm!(
234
            "
235
            csrw mscratch, t0
236

            
237
            # Clear the request before the switch, so that a request raised for the next
238
            # switch survives.
239
            la   t0, {request}
240
            lw   t0, 0(t0)
241
            sw   zero, 0(t0)
242

            
243
            # The handler restores t0 from mscratch and returns with mret.
244
            la   t0, {handler}
245
            lw   t0, 0(t0)
246
            jr   t0
247
            ",
248
            request = sym REQUEST,
249
            handler = sym HANDLER,
250
        )
251
    }
252
}