commit:3b240d6a16e08b9ea76a1ad25f9e152cb7d321cd
author:Chip
committer:Chip
date:Fri Sep 1 22:33:39 2023 -0500
parents:ff54f9880addb3e4bf6025bc9510df3fc0401bf8
Multitasking!
diff --git a/Cargo.toml b/Cargo.toml
line changes: +1/-1
index 8c2ac3f..c5a2070
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
 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"

diff --git a/memory.x b/memory.x
line changes: +7/-0
index 68f96ec..5eed022
--- a/memory.x
+++ b/memory.x
@@ -24,6 +24,13 @@ SECTIONS {
 } INSERT AFTER .bss;
 
 SECTIONS {
+    .appram : {
+        *(.appram);
+        . = ALIGN(4);
+    } > RAM
+    _appram = ADDR(.appram);
+    _appram_end = ADDR(.appram) + SIZEOF(.appram);
+
     .kgram1 : {
         *(.kgram1);
         . = ALIGN(4);

diff --git a/src/asm.rs b/src/asm.rs
line changes: +68/-0
index 0000000..63fbd83
--- /dev/null
+++ b/src/asm.rs
@@ -0,0 +1,68 @@
+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

diff --git a/src/main.rs b/src/main.rs
line changes: +50/-8
index 051a0b1..876db9d
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,21 +1,27 @@
 #![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;
@@ -32,10 +38,12 @@ fn main() -> ! {
     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])) {
@@ -57,6 +65,40 @@ fn main() -> ! {
         }).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

diff --git a/src/peripherals/usbdev.rs b/src/peripherals/usbdev.rs
line changes: +7/-0
index d8d7bcd..4dad14d
--- a/src/peripherals/usbdev.rs
+++ b/src/peripherals/usbdev.rs
@@ -6,6 +6,7 @@ use bsp::hal::usb::UsbBus;
 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::*;
@@ -40,6 +41,12 @@ impl<'a> UsbManager<'a> {
                 }
                 Ok(_leds) => {}
             }
+            let mut buf = [0u8; 64];
+            match self.serial.read(&mut buf) {
+                Ok(_c) => (), // c bytes written
+                Err(UsbError::WouldBlock) => (),
+                Err(_err) => (),
+            }
         }
     }
 

diff --git a/src/stdlib.rs b/src/stdlib.rs
line changes: +30/-0
index 0000000..d9134a3
--- /dev/null
+++ b/src/stdlib.rs
@@ -0,0 +1,30 @@
+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

diff --git a/src/svc.rs b/src/svc.rs
line changes: +48/-0
index 0000000..291104d
--- /dev/null
+++ b/src/svc.rs
@@ -0,0 +1,48 @@
+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"),
+    }
+}

diff --git a/src/task.rs b/src/task.rs
line changes: +195/-0
index 0000000..7bf833b
--- /dev/null
+++ b/src/task.rs
@@ -0,0 +1,195 @@
+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(&regs)
+}

diff --git a/src/task/entry.rs b/src/task/entry.rs
line changes: +65/-0
index 0000000..04cb4d4
--- /dev/null
+++ b/src/task/entry.rs
@@ -0,0 +1,65 @@
+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,
+}

diff --git a/src/task/error.rs b/src/task/error.rs
line changes: +16/-0
index 0000000..a351b20
--- /dev/null
+++ b/src/task/error.rs
@@ -0,0 +1,16 @@
+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

diff --git a/src/timer.rs b/src/timer.rs
line changes: +34/-6
index 2f5d362..b69f8f6
--- a/src/timer.rs
+++ b/src/timer.rs
@@ -1,13 +1,14 @@
+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 {
@@ -98,13 +99,33 @@ pub fn schedule(d: Ticks, recurring: bool, f: fn()) -> Result<usize, 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");
         }
@@ -113,4 +134,11 @@ fn SysTick() {
     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);
+        });
+    }
 }