1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
//! A port of the RuCOS kernel to Cortex-M
#![no_std]
#![feature(naked_functions)]
use core::arch::asm;
use core::mem::MaybeUninit;
use core::ptr::write_volatile;
use cortex_m::interrupt::free;
use cortex_m::peripheral::{scb, syst::SystClkSource, SCB, SYST};
use rucos::Kernel;
const _TICK_RATE_HZ: u32 = 1000;
/// Kernel tick rate in hertz
pub const TICK_RATE_HZ: u64 = _TICK_RATE_HZ as u64;
/// Maximum number of kernel tasks
pub const MAX_NUM_TASKS: usize = 256;
static mut KERNEL: MaybeUninit<Kernel<u32, u64, MAX_NUM_TASKS>> = MaybeUninit::uninit();
/// Initialize the kernel and create the idle task
///
/// # Arguments
///
/// * `idle_stack`: Idle task stack
/// * `user_idle_task`: Optional idle task function
///
/// # Note
///
/// The idle task is the lowest priority task and is always ready to run, it
/// must not block or call any kernel APIs (e.g. `sleep`)
pub fn init(idle_stack: &mut [u8], user_idle_task: Option<fn(u32) -> !>) {
unsafe {
KERNEL = MaybeUninit::new(Kernel::new());
}
match user_idle_task {
Some(entry) => create(usize::MAX, usize::MAX, idle_stack, entry, None),
None => create(usize::MAX, usize::MAX, idle_stack, idle_task, None),
}
}
/// Create a task
///
/// # Arguments
///
/// * `id`: Task ID
/// * `priority`: Task priority, with a lower number meaning higher priority
/// * `stack`: Task stack memory
/// * `entry`: Task function
/// * `arg`: An optional argument to pass to `entry`
///
/// # Note
///
/// A context switch may occur after calling this API, if the kernel is running
pub fn create(id: usize, priority: usize, stack: &mut [u8], entry: fn(u32) -> !, arg: Option<u32>) {
let mut stack_ptr = stack.as_mut_ptr() as u32 + stack.len() as u32;
let arg = arg.unwrap_or(0);
// Align the stack
stack_ptr &= 0xFFFF_FFF8;
let register_values = [
0x0100_0000, // xPSR
entry as *const () as u32, // PC
task_exit as *const () as u32, // R14 (LR)
0x1212_1212, // R12
0x0303_0303, // R3
0x0202_0202, // R2
0x0101_0101, // R1
arg, // R0
0xFFFF_FFFD, // R14 (EXC_RETURN)
0x1111_1111, // R11
0x1010_1010, // R10
0x0909_0909, // R9
0x0808_0808, // R8
0x0707_0707, // R7
0x0606_0606, // R6
0x0505_0505, // R5
0x0404_0404, // R4
];
for register_value in register_values {
stack_ptr -= 4;
unsafe { write_volatile(stack_ptr as *mut u32, register_value) };
}
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.create(id, priority, stack_ptr) {
SCB::set_pendsv();
}
});
}
/// Delete a task
///
/// # Arguments
///
/// * `id`: Task to delete or `None` to delete the current task
///
/// # Note
///
/// A context switch may occur after calling this API
pub fn delete(id: Option<usize>) {
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.delete(id) {
SCB::set_pendsv();
}
});
}
/// Start the kernel
///
/// # Arguments
///
/// * `scb`: System control block (from the `cortex-m` crate)
/// * `systick`: System tick (from the `cortex-m` crate)
/// * `clock_freq_hz`: Core clock frequency in hertz
///
/// # Note
///
/// Does not return: Program execution continues from tasks or interrupt
/// handlers after calling this API
pub fn start(scb: &mut SCB, systick: &mut SYST, clock_freq_hz: u32) -> ! {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
let first_task_stack_ptr = kernel.start();
systick.set_reload((clock_freq_hz / _TICK_RATE_HZ) - 1);
systick.clear_current();
systick.set_clock_source(SystClkSource::Core);
systick.enable_interrupt();
systick.enable_counter();
unsafe {
// Context switch should only happen once all interrupts have been serviced
scb.set_priority(scb::SystemHandler::PendSV, 0xFF);
asm!(
"cpsid i", // Disable interrupts
"mov r0, {tmp}", // Get first task stack pointer
"msr psp, r0", // Write PSP
"mrs r1, control", // Read CONTROL
"orr r1, r1, #2", // Set SP = PSP
"bic r1, r1, #4", // Clear FPCA (reset FPU)
"msr control, r1", // Write CONTROL
"isb", // Sync instructions
"ldmia sp!, {{r4-r11, r14}}", // Restore R4 - R11, LR
"ldmia sp!, {{r0-r3}}", // Restore R0 - R3
"ldmia sp!, {{r12, r14}}", // Load R12 and LR
"ldmia sp!, {{r1, r2}}", // Load PC and discard xPSR
"cpsie i", // Enable interrupts
"bx r1", // Branch to first task
tmp = in(reg) first_task_stack_ptr,
options(noreturn),
)
};
}
/// Get the ID of the current task
///
/// # Returns
///
/// ID of the current task
pub fn get_current_task() -> usize {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
// Does not modify the kernel
kernel.get_current_task()
}
/// Get the current value of the kernel tick
///
/// # Returns
///
/// Current value of the kernel tick
///
/// # Note
///
/// Ticks correspond to system time based on `TICK_RATE_HZ`
pub fn get_current_tick() -> u64 {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
// Does not modify the kernel
kernel.get_current_tick()
}
/// Sleep the current task
///
/// # Arguments
///
/// * `delay`: Number of ticks to sleep
///
/// # Note
///
/// Ticks correspond to system time based on `TICK_RATE_HZ`
pub fn sleep(delay: u64) {
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.sleep(delay) {
SCB::set_pendsv();
}
});
}
/// Suspend a task
///
/// # Arguments
///
/// * `id`: Task to suspend or `None` to suspend the current task
///
/// # Note
///
/// A context switch may occur after calling this API
pub fn suspend(id: Option<usize>) {
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.suspend(id) {
SCB::set_pendsv();
}
});
}
/// Resume a task
///
/// # Arguments
///
/// * `id`: Task to resume
///
/// # Note
///
/// A context switch may occur after calling this API
pub fn resume(id: usize) {
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.resume(id) {
SCB::set_pendsv();
}
});
}
/// SysTick interrupt handler
///
/// At a frequency of `TICK_RATE_HZ`, updates the kernel tick and runs the
/// scheduler
#[no_mangle]
pub extern "C" fn SysTick() {
free(|_| {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
if kernel.tick_update(1) {
SCB::set_pendsv();
}
});
}
/// PendSV interrupt handler
///
/// Context switch implementation
#[naked]
#[no_mangle]
pub extern "C" fn PendSV() {
unsafe {
// TODO: Replace disabling interrupts with BASEPRI adjustment
asm!(
"cpsid i", // Disable interrupts
"mrs r0, psp", // Read PSP
"mov r1, lr", // Save LR
"tst r14, #0x10", // Check if FPU is being used
"it eq", // ...
"vstmdbeq r0!, {{s16-s31}}", // Push the FPU registers
"stmdb r0!, {{r4-r11, r14}}", // Push the CPU registers
"push {{r1}}", // Push LR
"bl context_switch", // context_switch(R0) -> R0
"pop {{r1}}", // Pop LR
"ldmia r0!, {{r4-r11, r14}}", // Pop the CPU registers
"tst r14, #0x10", // Check if FPU is being used
"it eq", // ...
"vldmiaeq r0!, {{s16-s31}}", // Pop the FPU registers
"msr psp, r0", // Write PSP
"cpsie i", // Enable interrupts
"bx r1", // Branch to next task
options(noreturn),
);
}
}
/// Perform a context switch
///
/// # Arguments
///
/// * `curr_task_stack_ptr`: Stack pointer of the current task
///
/// # Returns
///
/// Stack pointer of the next task
#[no_mangle]
fn context_switch(curr_task_stack_ptr: u32) -> u32 {
let kernel = unsafe { &mut *KERNEL.as_mut_ptr() };
kernel.handle_context_switch(Some(curr_task_stack_ptr))
}
/// Tasks should not exit
fn task_exit() {
loop {}
}
/// Default idle task function
fn idle_task(_: u32) -> ! {
loop {}
}