edition = "2021"
[dependencies]
-cortex-m = "0.7"
+cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7"
critical-section = "1.1"
embedded-alloc = "0.5"
} INSERT AFTER .bss;
SECTIONS {
+ .appram : {
+ *(.appram);
+ . = ALIGN(4);
+ } > RAM
+ _appram = ADDR(.appram);
+ _appram_end = ADDR(.appram) + SIZEOF(.appram);
+
.kgram1 : {
*(.kgram1);
. = ALIGN(4);
+use core::arch::global_asm;
+
+// ExceptionEntry pushes LR and saves all of the task register state to
+// a TaskRegisters struct on the stack. It is intended to be used as a
+// primitive for pre-emptive multitasking.
+global_asm!(r#"
+ .macro ExceptionEntry
+ cpsid i
+ mov r0, lr
+ movs r1, #0x0F // Check which mode we just came from
+ ands r0, r1
+ cmp r0, #0xD // if we came from something other than thread mode/process stack,
+ bne 2f // skip stacking the rest of the registers and move on
+
+ // stack the registers
+ push {{r4, r5, r6, r7}} // push r4-r7
+ mov r0, r8
+ mov r1, r9
+ mov r2, r10
+ mov r3, r11
+ push {{r0, r1, r2, r3}} // push r8-r11
+ mrs r0, PSP // grab the process stack pointer
+ push {{r0}} // push PSP
+ sub sp, #32 // reserve space for the next eight registers
+ mov r1, sp // copy the stack pointer
+ ldm r0!, {{r4, r5, r6, r7}} // load r0-r3
+ stm r1!, {{r4, r5, r6, r7}} // store r0-r3
+ ldm r0!, {{r4, r5, r6, r7}} // load r12, lr, pc, xpsr
+ stm r1!, {{r4, r5, r6, r7}} // save r12, lr, pc, xpsr
+ mov r0, sp // get address of TaskRegisters struct
+ b 3f
+
+ // Don't stack, provide None
+2: movs r0, #0
+3: push {{lr}} // save LR
+ .endm
+"#);
+
+// ExceptionExit is the reverse of ExceptionEntry. It restores all of
+// the task registers and then exits the exception handler via the LR
+// pushed to the stack earlier.
+global_asm!(r#"
+ .macro ExceptionExit
+ pop {{r0}} // restore LR
+ mov lr, r0 // copy to LR
+ movs r1, #0xF
+ ands r0, r1
+ cmp r0, #0xD // check if we were using the process stack
+ bne 2f // and if not, skip unstacking
+
+ ldr r0, [sp, #0x20] // load PSP from the TaskRegisters struct
+ msr PSP, r0 // Store it back in its register
+ pop {{r4, r5, r6, r7}} // load r0-r3
+ stm r0!, {{r4, r5, r6, r7}} // store r0-r3
+ pop {{r4, r5, r6, r7}} // load r12, lr, pc, xpsr
+ stm r0!, {{r4, r5, r6, r7}} // store r12, lr, pc, xpsr
+ add sp, #4 // skip the stack value we already got
+ pop {{r4, r5, r6, r7}}
+ mov r8, r4
+ mov r9, r5
+ mov r10, r6
+ mov r11, r7
+ pop {{r4, r5, r6, r7}}
+
+2: cpsie i
+ bx lr
+ .endm
+"#);
\ No newline at end of file
#![no_std]
#![no_main]
+#![feature(error_in_core)]
mod allocator;
+mod asm;
mod console;
mod panic;
mod peripherals;
+mod stdlib;
+mod svc;
+mod task;
mod timer;
-use allocator::init_allocator;
use bsp::entry;
-use console::init_console;
+use cortex_m::asm::wfi;
use doa_hallonbrod as bsp;
-use embedded_hal::digital::v2::OutputPin;
-use peripherals::{init_peripherals, with_peripherals, with_usb};
+
+use allocator::init_allocator;
+use console::init_console;
+use task::{init_tasks, with_task_manager};
+
+use peripherals::init_peripherals;
use r0;
-use usbd_human_interface_device::page::Keyboard;
-use usbd_human_interface_device::UsbHidError;
extern "C" {
static mut __skgram1: u32;
init_allocator();
init_peripherals();
init_console();
+ init_tasks();
- let ticks = with_peripherals(|p| p.timer().get_counter().ticks());
+ let ticks = timer::ticks();
kprintf!("Kernel started: {} ticks\r\n", ticks);
+ /*
timer::schedule(1000, true, || {
with_peripherals(|p| p.led().set_high().unwrap());
match with_usb(|u| u.keyboard().device().write_report([Keyboard::Keyboard8])) {
}).expect("could not schedule key up");
})
.expect("could not schedule");
+ */
+
+ with_task_manager(|tm| {
+ tm.add_task(task_one);
+ tm.add_task(task_two);
+ });
+
+ unsafe {
+ task::launch(idle);
+ }
+}
- loop {}
+fn idle() -> ! {
+ loop {
+ wfi();
+ }
}
+
+fn task_one() -> ! {
+ loop {
+ stdlib::print("\r\n");
+ for _ in 0..10 {
+ stdlib::print("1");
+ stdlib::sleep(100);
+ }
+ }
+}
+
+fn task_two() -> ! {
+ let mut on = true;
+ loop {
+ stdlib::print("2");
+ stdlib::led(on);
+ on = !on;
+ stdlib::sleep(223);
+ }
+}
\ No newline at end of file
use bsp::pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS, interrupt};
use critical_section::Mutex;
use frunk_core::hlist::{HCons, HNil};
+use frunk_core::labelled::chars::C;
use usb_device::{class_prelude::*, prelude::*};
use usbd_human_interface_device::device::keyboard::NKROBootKeyboard;
use usbd_human_interface_device::prelude::*;
}
Ok(_leds) => {}
}
+ let mut buf = [0u8; 64];
+ match self.serial.read(&mut buf) {
+ Ok(_c) => (), // c bytes written
+ Err(UsbError::WouldBlock) => (),
+ Err(_err) => (),
+ }
}
}
+use core::arch::global_asm;
+
+use crate::timer::Ticks;
+
+extern "C" {
+ fn _svc_call(a: u32, b: u32, c: u32, d: u32);
+}
+
+global_asm!(r#"
+ .global _svc_call
+ .type _svc_call,function
+_svc_call:
+ svc #64
+ bx lr
+"#);
+
+pub fn sleep(t: Ticks) {
+ unsafe { _svc_call(1, t, 0, 0); }
+}
+
+pub fn print(s: &str) {
+ let bytes = s.as_bytes();
+ let s_ptr = bytes.as_ptr() as u32;
+ let s_len = bytes.len();
+ unsafe { _svc_call(2, s_ptr, s_len as u32, 0); }
+}
+
+pub fn led(on: bool) {
+ unsafe { _svc_call(3, on as u32, 0, 0); }
+}
\ No newline at end of file
+use core::arch::global_asm;
+
+use embedded_hal::digital::v2::OutputPin;
+
+use crate::console;
+use crate::peripherals::with_peripherals;
+use crate::task::{with_task_manager, TaskRegisters};
+use crate::timer::ticks;
+
+global_asm!(
+ r#"
+ .global SVCall
+ .type SVCall,function
+SVCall:
+ ExceptionEntry
+ bl svcall_handler // call the handler
+ ExceptionExit
+"#
+);
+
+#[no_mangle]
+unsafe fn svcall_handler(regs: Option<&mut TaskRegisters>) {
+ let regs = regs.expect("SVCall in kernel mode!?");
+ match regs.r0 {
+ 1 => {
+ let ticks = ticks();
+ with_task_manager(|tm| {
+ tm.sleep(regs.r1);
+ tm.schedule(ticks, regs);
+ });
+ }
+ 2 => {
+ let bytes = core::slice::from_raw_parts(regs.r1 as *const u8, regs.r2 as usize);
+ console::write(bytes);
+ }
+ 3 => {
+ let on = regs.r1 != 0;
+ with_peripherals(|p| {
+ if on {
+ p.led().set_high().ok()
+ } else {
+ p.led().set_low().ok()
+ }
+ });
+ }
+ _ => (), //panic!("Unknown SVCall"),
+ }
+}
+mod entry;
+mod error;
+
+use core::arch::global_asm;
+use core::cell::RefCell;
+
+use critical_section::Mutex;
+pub use entry::TaskState;
+pub(crate) use entry::{TaskEntry, TaskRegisters};
+pub use error::TaskError;
+
+use crate::timer::{ticks, Ticks};
+
+#[link_section = ".appram"]
+static mut APPRAM: [u8; 0x10000] = [0u8; 0x10000];
+
+pub type TaskID = u32;
+
+static TASK_MANAGER: Mutex<RefCell<Option<TaskManager>>> = Mutex::new(RefCell::new(None));
+
+pub struct TaskManager {
+ region_map: [Option<u32>; 10],
+ next_tid: u32,
+ tasks: heapless::Vec<TaskEntry, 10>,
+ current_task: usize,
+ active: bool,
+}
+
+impl TaskManager {
+ fn new() -> TaskManager {
+ TaskManager {
+ region_map: [None; 10],
+ next_tid: 0,
+ tasks: heapless::Vec::new(),
+ current_task: 0,
+ active: false,
+ }
+ }
+
+ pub(crate) fn start(&mut self) {
+ self.active = true;
+ }
+
+ pub(crate) fn stop(&mut self) {
+ self.active = false;
+ }
+
+ pub fn add_task(&mut self, entry: fn() -> !) {
+ let tid = self.next_tid;
+ self.next_tid += 1;
+
+ let region = self.region_map.iter().position(|v| v.is_none());
+ let Some(region) = region else {
+ panic!("no mappable regions!");
+ };
+ self.region_map[region] = Some(tid);
+ let entry = entry as *const () as u32;
+ let data: u32 = (unsafe { &mut APPRAM } as *mut u8 as u32) + (region as u32 * 0x1000);
+ let data_size = 0x1000;
+
+ let mut regs = TaskRegisters::new();
+ regs.pc = entry;
+ // subtract 0x20 for exception register stack because
+ // returning from scheduling will unstack this
+ regs.sp = data + data_size - 0x20;
+ // Set up initial info about memory
+ regs.r0 = data;
+ regs.r1 = data_size;
+
+ let t = TaskEntry {
+ id: 0,
+ task_registers: regs,
+ data,
+ data_size: data_size as usize,
+ state: TaskState::Running,
+ ticks_ran: 0,
+ };
+ self.tasks.push(t).expect("too many tasks");
+ }
+
+ pub(crate) fn schedule(&mut self, now: Ticks, regs: &mut TaskRegisters) {
+ if !self.active {
+ return;
+ }
+
+ if self.tasks.len() == 0 {
+ panic!("Scheduling with no tasks!");
+ }
+
+ // Store the registers of the current task
+ let t = &mut self.tasks[self.current_task];
+ t.task_registers = regs.clone();
+
+ // Check if any tasks are done sleeping
+ for t in &mut self.tasks {
+ if let TaskState::Sleeping(t1) = t.state {
+ if now >= t1 {
+ t.state = TaskState::Running;
+ }
+ }
+ }
+
+ let start_index = self.current_task;
+ let mut c = (start_index + 1) % self.tasks.len();
+ let next_task_index = loop {
+ if self.tasks[c].state == TaskState::Running {
+ break Some(c);
+ }
+ if c == start_index {
+ break None;
+ }
+ c = (c + 1) % self.tasks.len();
+ };
+
+ if let Some(next_task_index) = next_task_index {
+ // Switch to the next task
+ self.current_task = next_task_index;
+ // Restore the registers for that task
+ let t = &mut self.tasks[self.current_task];
+ *regs = t.task_registers.clone();
+ t.ticks_ran += 1;
+ } else {
+ panic!("No scheduleable tasks!");
+ }
+ }
+
+ pub fn sleep(&mut self, t: Ticks) {
+ if self.tasks.len() == 0 {
+ panic!("Cannot sleep with no tasks");
+ }
+
+ let t1 = ticks() + t;
+ self.tasks[self.current_task].state = TaskState::Sleeping(t1);
+ }
+}
+
+pub fn init_tasks() {
+ critical_section::with(|cs| {
+ let mut task_manager = TASK_MANAGER.borrow_ref_mut(cs);
+ *task_manager = Some(TaskManager::new());
+ });
+}
+
+pub fn with_task_manager<F, R>(mut f: F) -> R
+where
+ F: FnMut(&mut TaskManager) -> R,
+{
+ critical_section::with(|cs| {
+ let mut task_manager = TASK_MANAGER.borrow_ref_mut(cs);
+ if let Some(ref mut task_manager) = *task_manager {
+ return f(task_manager);
+ } else {
+ panic!("USB Manager not initialized");
+ }
+ })
+}
+
+extern "C" {
+ fn _launch(regs: &TaskRegisters) -> !;
+}
+
+global_asm!(
+ r#"
+ .global _launch
+ .type _launch,function
+_launch:
+ ldr r3, [r0, #32] // load SP
+ ldr r2, [r0, #24] // load PC
+ ldr r1, [r0, #4] // load r1
+ ldr r0, [r0, #0] // load r0
+ add sp, #88 // erase the TaskRegisters struct from the stack
+
+ // set up PSP and drop privileges
+ movs r4, #3
+ msr CONTROL, r4
+ isb
+ dsb
+
+ mov sp, r3 // set up stack
+ add sp, #0x20 // Remove the space reserved for register
+ // unstacking since we aren't doing that here.
+ // And away we go!
+ bx r2
+"#
+);
+
+pub unsafe fn launch(entry: fn() -> !) -> ! {
+ let regs = with_task_manager(|tm| {
+ tm.add_task(entry);
+ tm.start();
+ tm.tasks[0].task_registers.clone()
+ });
+
+ _launch(®s)
+}
+use crate::task::TaskID;
+use crate::timer::Ticks;
+
+#[derive(Debug, PartialEq)]
+pub enum TaskState {
+ Running,
+ Blocked(u32),
+ Sleeping(Ticks),
+}
+
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub(crate) struct TaskRegisters {
+ pub r0: u32,
+ pub r1: u32,
+ pub r2: u32,
+ pub r3: u32,
+ pub r12: u32,
+ pub lr: u32,
+ pub pc: u32,
+ pub xpsr: u32,
+ pub sp: u32,
+ pub r8: u32,
+ pub r9: u32,
+ pub r10: u32,
+ pub r11: u32,
+ pub r4: u32,
+ pub r5: u32,
+ pub r6: u32,
+ pub r7: u32,
+}
+
+impl TaskRegisters {
+ pub fn new() -> TaskRegisters {
+ TaskRegisters {
+ r0: 0,
+ r1: 0,
+ r2: 0,
+ r3: 0,
+ r4: 0,
+ r5: 0,
+ r6: 0,
+ r7: 0,
+ r8: 0,
+ r9: 0,
+ r10: 0,
+ r11: 0,
+ r12: 0,
+ sp: 0,
+ lr: 0,
+ pc: 0,
+ xpsr: 0x01000000, // Default value sets thread mode, which must always be set
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct TaskEntry {
+ pub id: TaskID,
+ pub task_registers: TaskRegisters,
+ pub data: u32,
+ pub data_size: usize,
+ pub state: TaskState,
+ pub ticks_ran: u32,
+}
+use core::fmt;
+
+#[derive(Debug)]
+pub enum TaskError {
+ Allocation,
+}
+
+impl fmt::Display for TaskError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ TaskError::Allocation => write!(f, "allocation"),
+ }
+ }
+}
+
+impl core::error::Error for TaskError {}
\ No newline at end of file
+use core::arch::global_asm;
use core::cell::RefCell;
use crate::bsp;
-use cortex_m_rt::exception;
+use crate::task::{with_task_manager, TaskRegisters};
use critical_section::Mutex;
#[link_section = ".kgram1"]
static TIMER: Mutex<RefCell<Option<Timer>>> = Mutex::new(RefCell::new(None));
-type Ticks = u32;
+pub type Ticks = u32;
#[derive(Debug)]
pub enum TimerError {
})
}
-#[exception]
-fn SysTick() {
- let pending = critical_section::with(|cs| {
+pub fn ticks() -> Ticks {
+ critical_section::with(|cs| {
+ let mut timer = TIMER.borrow_ref_mut(cs);
+ if let Some(ref mut timer) = *timer {
+ timer.t
+ } else {
+ panic!("Timer not initialized");
+ }
+ })
+}
+
+global_asm!(r#"
+ .global SysTick
+ .type SysTick,function
+SysTick:
+ ExceptionEntry
+ bl systick_handler
+ ExceptionExit
+"#);
+
+#[no_mangle]
+fn systick_handler(regs: Option<&mut TaskRegisters>) {
+ let (t, pending) = critical_section::with(|cs| {
let mut timer = TIMER.borrow_ref_mut(cs);
if let Some(ref mut timer) = *timer {
timer.tick();
- timer.gather_pending()
+ (timer.t, timer.gather_pending())
} else {
panic!("Timer not initialized");
}
for f in pending {
(f)();
}
+
+ // do not schedule unless TaskRegisters is valid
+ if let Some(regs) = regs {
+ with_task_manager(|tm| {
+ tm.schedule(t, regs);
+ });
+ }
}