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}