taskette_cortex_m/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5#![no_std]
6
7use cortex_m::peripheral::{SCB, SYST, scb::SystemHandler, syst::SystClkSource};
8use static_cell::ConstStaticCell;
9use taskette::{
10    arch::StackAllocation,
11    scheduler::{Scheduler, SchedulerConfig},
12};
13
14const IDLE_TASK_STACK_SIZE: usize = 2048;
15
16static IDLE_TASK_STACK: ConstStaticCell<Stack<IDLE_TASK_STACK_SIZE>> =
17    ConstStaticCell::new(Stack::new());
18
19#[repr(C, align(8))]
20#[derive(Clone, Debug)]
21struct HardwareSavedRegisters {
22    r0: u32,
23    r1: u32,
24    r2: u32,
25    r3: u32,
26    r12: u32,
27    lr: u32,
28    pc: u32,
29    xpsr: u32,
30}
31
32impl HardwareSavedRegisters {
33    fn from_pc_and_r0(pc: u32, r0: u32) -> Self {
34        Self {
35            r0,
36            r1: 0,
37            r2: 0,
38            r3: 0,
39            r12: 0,
40            lr: 0,
41            pc,
42            xpsr: 1 << 24, // Thumb state
43        }
44    }
45}
46
47#[repr(C, align(8))]
48#[derive(Clone, Debug)]
49struct SoftwareSavedRegisters {
50    // Software-saved registers
51    r4: u32,
52    r5: u32,
53    r6: u32,
54    r7: u32,
55    r8: u32,
56    r9: u32,
57    r10: u32,
58    r11: u32,
59    exc_return: u32, // LR on exception
60}
61
62impl SoftwareSavedRegisters {
63    fn new(fpu_regs_saved: bool) -> Self {
64        Self {
65            r4: 0,
66            r5: 0,
67            r6: 0,
68            r7: 0,
69            r8: 0,
70            r9: 0,
71            r10: 0,
72            r11: 0,
73            exc_return: if fpu_regs_saved {
74                0xFFFFFFED // thread-mode, PSP, FPU regs
75            } else {
76                0xFFFFFFFD // thread-mode, PSP, no FPU regs
77            },
78        }
79    }
80}
81
82/// Safely initializes the scheduler.
83pub fn init_scheduler(
84    _syst: SYST,
85    _scb: SCB,
86    clock_freq: u32,
87    config: SchedulerConfig,
88) -> Option<Scheduler> {
89    unsafe { Scheduler::init(clock_freq, config) }
90}
91
92/// Context switching procedure
93#[cfg(not(target_has_atomic = "ptr"))] // No atomic => thumbv6m
94#[unsafe(no_mangle)]
95#[unsafe(naked)]
96extern "C" fn PendSV() {
97    // Registers {R0-R3, R12, LR, PC, xPSR} are saved in the process stack by the hardware
98    core::arch::naked_asm!(
99        "mrs r0, psp",  // Read the process stack pointer (PSP, because the SP is MSP now)
100
101        "mov r1, sp",   // Temporarily save SP (MSP) in R1
102        "mov sp, r0",   // Set SP (MSP) to the loaded PSP value
103
104        "sub sp, #4*(5+1)",   // Move SP to just above R8-R11,LR (plus alignment)
105        "push {{r4-r7}}", // Save the lower half of the remaining registers in the process stack
106        "add sp, #4*9",   // Move the stack pointer to below R8-R11,LR
107        // Copy the higher half of the remaining registers into the lower half
108        "mov r2, r8",
109        "mov r3, r9",
110        "mov r4, r10",
111        "mov r5, r11",
112        "push {{r2-r5,lr}}", // Save the copied registers values and LR in the process stack
113        "sub sp, #4*4",   // Move the stack pointer to above R4-R7
114
115        "mov r0, sp",   // Update R0 using the new SP value
116
117        "mov sp, r1",   // Restore the value of original SP (MSP)
118
119        "bl {select_task}",  // Call `select_task` function. R0 (process stack pointer) is used as the first argument and the return value.
120
121        "mov r1, sp",   // Temporarily save SP (MSP) in R1
122        "mov sp, r0",   // Set SP (MSP) to the returned PSP value
123
124        "add sp, #4*4", // Move the stack pointer to just above R8-R11,LR
125        "pop {{r2-r6}}",  // Load the values of R8-R11 and LR from the process stack to R2-R6
126        // Restore R8-R11 and LR from the loaded values
127        "mov r8, r2",
128        "mov r9, r3",
129        "mov r10, r4",
130        "mov r11, r5",
131        "mov lr, r6",
132        "sub sp, #4*9", // Move the stack pointer to above R4-R7
133        "pop {{r4-r7}}",    // Restore R4-R7 from the process stack
134        "add sp, #4*(5+1)", // Move the stack pointer to below R8-R11 (plus alignment)
135
136        "mov r0, sp",   // Update R0 with the new SP value
137        "mov sp, r1",   // Restore the value of original SP (MSP)
138
139        "msr psp, r0",  // Set the PSP to the value of R0
140
141        "bx lr",    // Exit the exception handler by jumping to EXC_RETURN
142        select_task = sym taskette::scheduler::select_task,
143    );
144    // Hardware restores registers R0-R3 and R12 from the new stack
145}
146
147/// Context switching procedure
148#[cfg(all(target_has_atomic = "ptr", target_abi = "eabi"))] // Has atomic => thumbv7m or above, No FPU
149#[unsafe(no_mangle)]
150#[unsafe(naked)]
151extern "C" fn PendSV() {
152    // Registers {R0-R3, R12, LR, PC, xPSR} are saved in the process stack by the hardware
153    core::arch::naked_asm!(
154        "mrs r0, psp",  // Read the process stack pointer (PSP, because the SP is MSP now)
155
156        "sub r0, #4",   // For stack alignment
157        "stmdb r0!, {{r4-r11,lr}}", // Save the remaining registers and EXC_RETURN in the process stack
158
159        "bl {select_task}",  // Call `select_task` function. R0 (process stack pointer) is used as the first argument and the return value.
160
161        "ldmia r0!, {{r4-r11,lr}}",  // Restore the registers not saved by the hardware and EXC_RETURN from the process stack
162        "add r0, #4",   // For stack alignment
163
164        "msr psp, r0",   // Change PSP into the value returned by `select_task`
165
166        "bx lr",
167        select_task = sym taskette::scheduler::select_task,
168    );
169    // Hardware restores registers R0-R3 and R12 from the new stack
170}
171
172/// Context switching procedure
173/// For chips with an FPU.
174/// The approach based on the Armv8-M User Guide example: https://github.com/ARM-software/m-profile-user-guide-examples/tree/main/Exception_model/context-switch-fp
175#[cfg(target_abi = "eabihf")] // FPU
176#[unsafe(no_mangle)]
177#[unsafe(naked)]
178extern "C" fn PendSV() {
179    // Registers {R0-R3, R12, LR, PC, xPSR, S0-S15} are saved in the process stack by the hardware
180    core::arch::naked_asm!(
181        ".fpu fpv4-sp-d16", // Omitting this leads to a compile error in release mode (opt-level>0)
182        "mrs r0, psp",  // Read the process stack pointer (PSP, because the SP is MSP now)
183
184        "tst lr, #0x00000010",  // Check Bit 4 (FType) of EXC_RETURN (0 indicates the hardware-saved stack frame includes FP registers)
185        "it eq",   // The next instruction is conditional
186        "vstmdbeq r0!, {{s16-s31}}",    // Save the FP registers not saved by the hardware (if FType==0)
187
188        "sub r0, #4",   // For stack alignment
189        "stmdb r0!, {{r4-r11,lr}}", // Save the remaining registers and EXC_RETURN in the process stack
190
191        "bl {select_task}",  // Call `select_task` function. R0 (process stack pointer) is used as the first argument and the return value.
192
193        "ldmia r0!, {{r4-r11,lr}}",  // Restore the registers not saved by the hardware and EXC_RETURN from the process stack
194        "add r0, #4",   // For stack alignment
195
196        "tst lr, #0x00000010",  // Check Bit 4 (FType) of EXC_RETURN (0 indicates the hardware-saved stack frame includes FP registers)
197        "it eq",   // The next instruction is conditional
198        "vldmiaeq r0!, {{s16-s31}}",    // Load the FP registers not saved by the hardware (if FType==0)
199
200        "msr psp, r0",   // Change PSP into the value returned by `select_task`
201
202        "bx lr",
203        select_task = sym taskette::scheduler::select_task,
204    );
205    // Hardware restores registers R0-R3 and R12 from the new stack
206}
207
208#[cortex_m_rt::exception]
209fn SysTick() {
210    taskette::scheduler::handle_tick();
211}
212
213/// INTERNAL USE ONLY
214#[unsafe(no_mangle)]
215pub fn _taskette_setup(clock_freq: u32, tick_freq: u32) {
216    let peripherals = unsafe { cortex_m::Peripherals::steal() };
217    let mut scb = peripherals.SCB;
218    let mut syst = peripherals.SYST;
219
220    // On armv6m `set_priority` is not atomic
221    critical_section::with(|_| unsafe {
222        // Set priorities of core exceptions
223        scb.set_priority(
224            SystemHandler::PendSV,
225            255, /* Lowest possible priority */
226        );
227        scb.set_priority(
228            SystemHandler::SysTick,
229            255, /* Lowest possible priority */
230        );
231    });
232
233    // Configure the SysTick timer
234    assert!(clock_freq / tick_freq <= 0xFFFFFF); // SysTick has 24-bit limit
235    syst.set_clock_source(SystClkSource::Core);
236    syst.set_reload(clock_freq / tick_freq);
237    syst.enable_interrupt();
238}
239
240/// INTERNAL USE ONLY
241#[unsafe(no_mangle)]
242pub fn _taskette_start_timer() {
243    let peripherals = unsafe { cortex_m::Peripherals::steal() };
244    let mut syst = peripherals.SYST;
245
246    // Start the SysTick timer
247    syst.enable_counter();
248}
249
250/// INTERNAL USE ONLY
251#[unsafe(no_mangle)]
252pub fn _taskette_yield_now() {
253    SCB::set_pendsv();
254}
255
256/// INTERNAL USE ONLY
257#[unsafe(no_mangle)]
258pub fn _taskette_init_stack(sp: *mut u8, pc: usize, arg: *const u8, arg_size: usize) -> *mut u8 {
259    unsafe {
260        // Push the closure into the initial stack
261        let sp = push_to_stack(sp, arg, arg_size);
262        // Call `call_closure` with a pointer to the closure as the first argument
263        let sp = push_to_stack(
264            sp,
265            &HardwareSavedRegisters::from_pc_and_r0(pc as u32, sp as u32) as *const _ as *const u8,
266            core::mem::size_of::<HardwareSavedRegisters>(),
267        );
268        let sp = push_to_stack(
269            sp,
270            &SoftwareSavedRegisters::new(false) as *const _ as *const u8,
271            core::mem::size_of::<SoftwareSavedRegisters>(),
272        );
273        sp
274    }
275}
276
277/// INTERNAL USE ONLY
278#[unsafe(no_mangle)]
279pub unsafe fn _taskette_run_with_stack(pc: usize, sp: *mut u8, _stack_limit: *mut u8) -> ! {
280    unsafe {
281        core::arch::asm!(
282            // Write the new SP value to the PSP
283            "msr psp, {new_sp}",
284            // Change SP from MSP to PSP by setting the SPSEL bit of CONTROL register
285            "mrs {tmp}, control",
286            "orrs {tmp}, {spsel_mask}",
287            "msr control, {tmp}",
288            "isb",
289            // Jump to the new PC
290            "blx {new_pc}",
291            new_sp = in(reg) sp,
292            new_pc = in(reg) pc,
293            spsel_mask = in(reg) 0b10,
294            tmp = out(reg) _,
295        );
296    }
297
298    unreachable!()
299}
300
301#[unsafe(no_mangle)]
302pub fn _taskette_get_idle_task_stack() -> Option<&'static mut [u8]> {
303    if let Some(stack) = IDLE_TASK_STACK.try_take() {
304        Some(&mut stack.0)
305    } else {
306        None
307    }
308}
309
310/// INTERNAL USE ONLY
311#[unsafe(no_mangle)]
312pub fn _taskette_wait_for_interrupt() {
313    cortex_m::asm::wfi();
314}
315
316unsafe fn push_to_stack(sp: *mut u8, obj: *const u8, obj_size: usize) -> *mut u8 {
317    unsafe {
318        let size = obj_size;
319        // Ensure 8-byte alignment
320        let size = if size % 8 == 0 {
321            size
322        } else {
323            size + 8 - (size % 8)
324        };
325
326        let sp = sp.byte_sub(size);
327        core::ptr::copy(obj, sp, obj_size);
328
329        sp
330    }
331}
332
333/// Correctly aligned stack allocation helper.
334///
335/// It ensures allocation of a task-specific stack region correctly aligned at 8 bytes.
336/// Modeled after [rp2040-hal implementation](https://docs.rs/rp2040-hal/0.11.0/rp2040_hal/multicore/struct.Stack.html).
337#[repr(align(8))]
338pub struct Stack<const N: usize>([u8; N]);
339
340impl<const N: usize> Stack<N> {
341    pub const fn new() -> Self {
342        Self([0u8; N])
343    }
344}
345
346impl<const N: usize> StackAllocation for &mut Stack<N> {
347    fn as_mut_slice(&mut self) -> &mut [u8] {
348        &mut self.0
349    }
350}