I/O blocking, priorities, PendSV scheduling
+pub mod shell;
\ No newline at end of file
+extern crate alloc;
+use core::{slice, num::ParseIntError};
+
+use crate::stdlib;
+
+enum CommandError {
+ BadCommand,
+ BadAddress,
+ NeedArgument,
+}
+
+impl From<ParseIntError> for CommandError {
+ fn from(_value: ParseIntError) -> Self {
+ CommandError::BadAddress
+ }
+}
+
+fn readline(buf: &mut [u8]) -> Option<&str> {
+ let mut inbuf = [0u8; 100];
+ let mut i = 0;
+ loop {
+ let n_read = stdlib::read(buf);
+ for b in &buf[..n_read] {
+ match *b {
+ b'\r' | b'\n' => {
+ stdlib::print("\r\n");
+ // TODO: don't dump the rest of the buffer
+ buf.copy_from_slice(&inbuf);
+ let s = core::str::from_utf8(&buf[..i]).unwrap();
+ return Some(s);
+ }
+ 0x08 => {
+ if i > 0 {
+ stdlib::print("\x08 \x08");
+ i -= 1;
+ }
+ }
+ 0x03 => {
+ stdlib::print("\r\n");
+ return None;
+ }
+ b => {
+ inbuf[i] = b;
+ stdlib::write(&inbuf[i .. i + 1]);
+ i += 1;
+ }
+ }
+ }
+ }
+}
+
+fn format_hex_u32(buf: &mut [u8], mut n: u32) {
+ let mut i: i32 = 7;
+ while i >= 0 {
+ let d = n & 0xF;
+ buf[i as usize] = match d {
+ 0..=9 => (0x30 + d) as u8,
+ _ => (0x41 - 10 + d) as u8,
+ };
+ i -= 1;
+ n >>= 4;
+ }
+}
+
+fn print_hex_u32(n: u32) {
+ let mut hexbuf = [0u8; 8];
+ format_hex_u32(&mut hexbuf, n);
+ stdlib::write(&hexbuf);
+}
+
+fn read_addr(a: u32) -> u32 {
+ unsafe {
+ let ptr = a as *const u32;
+ *ptr
+ }
+}
+
+fn parse_cmdline(cmdline: &str) -> Result<(), CommandError> {
+ let mut parts = cmdline.split_ascii_whitespace();
+ let cmd = parts.next().ok_or(CommandError::BadCommand)?;
+ match cmd {
+ "r" => {
+ let astr = parts.next().ok_or(CommandError::NeedArgument)?;
+ let mut aparts = astr.split('.');
+ let from = aparts.next().map(|s| u32::from_str_radix(s, 16)).ok_or(CommandError::BadAddress)??;
+ print_hex_u32(from);
+ stdlib::print(": ");
+ match aparts.next() {
+ Some(to) => {
+ let to = u32::from_str_radix(to, 16)?;
+ let mut line_c = 0;
+ for addr in (from..to).step_by(4) {
+ if line_c == 4 {
+ stdlib::print("\r\n");
+ print_hex_u32(addr);
+ stdlib::print(": ");
+ line_c = 0;
+ }
+ print_hex_u32(read_addr(addr));
+ stdlib::print(" ");
+ line_c += 1;
+ }
+ }
+ None => {
+ print_hex_u32(read_addr(from));
+ }
+ };
+
+ stdlib::print("\r\n");
+ Ok(())
+ }
+ _ => {
+ Err(CommandError::BadCommand)
+ }
+ }
+}
+
+pub fn shell(base: u32, _size: u32) -> ! {
+ let mut input_buffer = unsafe { slice::from_raw_parts_mut(base as *mut u8, 100) };
+ stdlib::sleep(1000);
+ stdlib::print("\r\nShell 0.1\r\n");
+ loop {
+ stdlib::print("> ");
+ if let Some(cmdline) = readline(&mut input_buffer) {
+ match parse_cmdline(cmdline) {
+ Ok(_) => (),
+ Err(CommandError::BadCommand) => stdlib::print("Bad command\r\n"),
+ Err(CommandError::BadAddress) => stdlib::print("Bad address\r\n"),
+ Err(CommandError::NeedArgument) => stdlib::print("Need argument\r\n"),
+ }
+ }
+ }
+}
\ No newline at end of file
use core::cell::RefCell;
use critical_section::Mutex;
+use usb_device::UsbError;
-use crate::peripherals::with_usb;
+use crate::{capabilities::CapType, peripherals::with_usb, task::with_task_manager};
#[link_section = ".kgram1"]
static CONSOLE: Mutex<RefCell<Console>> = Mutex::new(RefCell::new(Console::new()));
+#[derive(Debug)]
pub enum ConsoleError {
Read,
Write,
+ Blocked,
}
-struct Console {
+pub struct Console {
+ read_buffer: heapless::Deque<u8, 64>,
}
impl Console {
const fn new() -> Console {
- Console { }
+ Console {
+ read_buffer: heapless::Deque::new(),
+ }
+ }
+
+ pub fn fill_read_buffer(&mut self, data: &[u8]) {
+ for b in data {
+ match self.read_buffer.push_back(*b) {
+ Ok(()) => (),
+ Err(_) => break, // All remaining characters read will be dropped
+ }
+ with_task_manager(|t| t.unblock_task_with_capability(CapType::ConsoleRead));
+ }
+ }
+
+ pub fn write_ready(&mut self) {
+ with_task_manager(|tm| tm.unblock_task_with_capability(CapType::ConsoleWrite));
}
pub fn write(&mut self, bytes: impl AsRef<[u8]>) -> Result<usize, ConsoleError> {
with_usb(|u| {
- u.serial().write(bytes.as_ref()).map_err(|_| ConsoleError::Write)
+ match u.serial().write(bytes.as_ref()) {
+ Ok(n) => Ok(n),
+ Err(UsbError::WouldBlock) => Err(ConsoleError::Blocked),
+ Err(_) => Err(ConsoleError::Write),
+ }
})
}
+
+ pub fn read(&mut self, data: &mut [u8]) -> Result<usize, ConsoleError> {
+ if self.read_buffer.is_empty() {
+ return Err(ConsoleError::Blocked);
+ }
+ let mut l = 0;
+ for i in 0..data.len() {
+ data[i] = match self.read_buffer.pop_front() {
+ Some(b) => b,
+ None => break,
+ };
+ l += 1;
+ }
+ Ok(l)
+ }
}
pub fn init_console() {
// Does nothing since console can be statically initialized
}
-pub fn write(bytes: &[u8]) {
+pub fn with_console<F, R>(mut f: F) -> R
+where
+ F: FnMut(&mut Console) -> R,
+{
critical_section::with(|cs| {
let mut console = CONSOLE.borrow_ref_mut(cs);
- console.write(bytes).ok();
- });
+ f(&mut console)
+ })
+}
+
+pub fn write(bytes: &[u8]) -> Result<usize, ConsoleError> {
+ critical_section::with(|cs| {
+ let mut console = CONSOLE.borrow_ref_mut(cs);
+ console.write(bytes)
+ })
+}
+
+pub fn read(bytes: &mut [u8]) -> Result<usize, ConsoleError> {
+ critical_section::with(|cs| {
+ let mut console = CONSOLE.borrow_ref_mut(cs);
+ console.read(bytes)
+ })
}
pub(crate) fn _print(s: core::fmt::Arguments) {
let str = alloc::format!("{}", s);
- write(str.as_bytes());
+ write(str.as_bytes()).ok();
}
#[macro_export]
($($arg:tt)*) => {{
$($crate::console::write($arg.as_bytes());)*
}}
-}
\ No newline at end of file
+}
#![feature(error_in_core)]
mod allocator;
+mod apps;
mod asm;
mod capabilities;
mod console;
mod timer;
use bsp::entry;
-use cortex_m::asm::wfi;
use doa_hallonbrod as bsp;
use allocator::init_allocator;
*/
with_task_manager(|tm| {
- tm.add_task(task_one, &[CapType::ConsoleWrite]).expect("launch task one");
- tm.add_task(task_two, &[CapType::Led]).expect("launch task two");
+ tm.add_task(apps::shell::shell, &[CapType::ConsoleRead, CapType::ConsoleWrite], 0).expect("launch task one");
});
unsafe {
- task::launch(idle);
- }
-}
-
-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);
+ task::launch_idle_task()
}
}
}
ALREADY_PANICKING = true;
}
+ cortex_m::interrupt::disable();
write(b"PANIC!\n");
if let Some(p) = info.payload().downcast_ref::<&str>() {
use bsp::hal::Sio;
use bsp::hal::Timer;
use bsp::Pins;
+use cortex_m::peripheral::scb::SystemHandler;
use critical_section::Mutex;
use usbdev::init_usb;
}
pub fn init_peripherals() {
- let core = pac::CorePeripherals::take().unwrap();
+ let mut core = pac::CorePeripherals::take().unwrap();
+ // Set some core exception priorities
+ unsafe {
+ core.SCB.set_priority(SystemHandler::SysTick, 0xFF);
+ core.SCB.set_priority(SystemHandler::SVCall, 0xFF);
+ core.SCB.set_priority(SystemHandler::PendSV, 0xFF);
+ }
let mut platform = pac::Peripherals::take().unwrap();
let mut watchdog = Watchdog::new(platform.WATCHDOG);
let clocks = init_clocks_and_plls(
use core::cell::RefCell;
use crate::bsp;
+use crate::console::with_console;
use bsp::hal::clocks::UsbClock;
use bsp::hal::usb::UsbBus;
-use bsp::pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS, interrupt};
+use bsp::pac::{interrupt, RESETS, USBCTRL_DPRAM, USBCTRL_REGS};
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::*;
}
let mut buf = [0u8; 64];
match self.serial.read(&mut buf) {
- Ok(_c) => (), // c bytes written
+ Ok(n) => {
+ // n bytes read
+ with_console(|c| c.fill_read_buffer(&buf[0..n]));
+ }
Err(UsbError::WouldBlock) => (),
- Err(_err) => (),
+ Err(err) => panic!("Failed to read serial: {:?}", err),
+ }
+ match self.serial.flush() {
+ Ok(_) => {
+ with_console(|c| c.write_ready());
+ }
+ Err(UsbError::WouldBlock) => (),
+ Err(err) => panic!("Failed to flush serial: {:?}", err),
}
}
}
#[interrupt]
fn USBCTRL_IRQ() {
with_usb(|u| u.update());
-}
\ No newline at end of file
+}
use crate::timer::Ticks;
extern "C" {
- fn _svc_call(a: u32, b: u32, c: u32, d: u32);
+ fn _svc_call(a: u32, b: u32, c: u32, d: u32) -> u32;
}
global_asm!(r#"
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); }
+ write(bytes);
+}
+
+pub fn write(buf: &[u8]) {
+ let ptr = buf.as_ptr() as u32;
+ let len = buf.len();
+ unsafe { _svc_call(2, ptr, len as u32, 0); }
+}
+
+pub fn read(buf: &mut [u8]) -> usize {
+ let ptr = buf.as_ptr() as u32;
+ let len = buf.len();
+ unsafe { _svc_call(3, ptr, len as u32, 0) as usize }
}
pub fn led(on: bool) {
- unsafe { _svc_call(3, on as u32, 0, 0); }
+ unsafe { _svc_call(4, on as u32, 0, 0); }
}
\ No newline at end of file
use embedded_hal::digital::v2::OutputPin;
+use crate::bsp::pac::SCB;
use crate::capabilities::CapType;
-use crate::console;
+use crate::console::{self, ConsoleError};
use crate::peripherals::with_peripherals;
-use crate::task::{with_task_manager, TaskRegisters, current_process_has_capability};
+use crate::task::{with_task_manager, TaskRegisters, current_task_has_capability};
use crate::timer::ticks;
global_asm!(
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);
+ SCB::set_pendsv();
});
}
2 => {
- if !current_process_has_capability(CapType::ConsoleWrite) {
+ if !current_task_has_capability(CapType::ConsoleWrite) {
return;
}
let bytes = core::slice::from_raw_parts(regs.r1 as *const u8, regs.r2 as usize);
- console::write(bytes);
+ match console::write(bytes) {
+ Ok(n) => regs.r0 = n as u32,
+ Err(ConsoleError::Blocked) => {
+ with_task_manager(|tm| tm.block_current_task(CapType::ConsoleWrite));
+ SCB::set_pendsv();
+ }
+ Err(e) => panic!("write syscall error: {:?}", e),
+ }
}
3 => {
- if !current_process_has_capability(CapType::Led) {
+ if !current_task_has_capability(CapType::ConsoleRead) {
+ return;
+ }
+
+ let bytes = core::slice::from_raw_parts_mut(regs.r1 as *mut u8, regs.r2 as usize);
+ match console::read(bytes) {
+ Ok(n) => regs.r0 = n as u32,
+ Err(ConsoleError::Blocked) => {
+ with_task_manager(|t| {
+ t.block_current_task(CapType::ConsoleRead);
+ SCB::set_pendsv();
+ });
+ },
+ Err(e) => panic!("read syscall error: {:?}", e),
+ }
+ }
+ 4 => {
+ if !current_task_has_capability(CapType::Led) {
return;
}
_ => (), //panic!("Unknown SVCall"),
}
}
+
+global_asm!(
+ r#"
+ .global PendSV
+ .type PendSV,function
+PendSV:
+ ExceptionEntry
+ bl pendsv_handler // call the handler
+ ExceptionExit
+"#
+);
+
+#[no_mangle]
+unsafe fn pendsv_handler(regs: Option<&mut TaskRegisters>) {
+ let regs = regs.expect("PendSV from handler mode!?");
+ let t = ticks();
+ with_task_manager(|tm| {
+ tm.schedule(t);
+ tm.task_swap(regs);
+ });
+ SCB::clear_pendsv();
+}
\ No newline at end of file
use core::arch::global_asm;
use core::cell::RefCell;
+use core::cmp::Ordering;
+use cortex_m::asm::wfi;
use critical_section::Mutex;
+use embedded_hal::digital::v2::OutputPin;
pub use entry::TaskState;
pub(crate) use entry::{TaskEntry, TaskRegisters};
pub use error::TaskError;
+use crate::bsp::pac::SCB;
use crate::capabilities::{with_cap_registry, CapToken, CapType, MAX_CAPS};
+use crate::peripherals::with_peripherals;
use crate::timer::{ticks, Ticks};
+const MAX_TASKS: usize = 10;
+
#[link_section = ".appram"]
static mut APPRAM: [u8; 0x10000] = [0u8; 0x10000];
pub struct TaskManager {
region_map: [Option<u32>; 10],
next_tid: u32,
- tasks: heapless::Vec<TaskEntry, 10>,
+ tasks: heapless::Vec<TaskEntry, MAX_TASKS>,
current_task: usize,
+ pending_task: usize,
active: bool,
}
next_tid: 0,
tasks: heapless::Vec::new(),
current_task: 0,
+ pending_task: 0,
active: false,
}
}
pub fn add_task(
&mut self,
- entry: fn() -> !,
+ entry: fn(u32, u32) -> !,
requested_caps: &[CapType],
+ priority: u8,
) -> Result<TaskId, TaskError> {
let tid = self.next_tid;
self.next_tid += 1;
let t = TaskEntry {
id: 0,
- task_registers: regs,
+ regs,
data,
data_size: data_size as usize,
state: TaskState::Running,
+ priority: priority,
+ io_ready: false,
ticks_ran: 0,
caps,
};
Ok(tid)
}
- pub(crate) fn schedule(&mut self, now: Ticks, regs: &mut TaskRegisters) {
+ pub(crate) fn schedule(&mut self, now: Ticks) {
if !self.active {
return;
}
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 {
}
}
- 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;
+ let mut prio_list: heapless::Vec<usize, MAX_TASKS> = self
+ .tasks
+ .iter()
+ .enumerate()
+ .filter(|(_, t)| t.state == TaskState::Running)
+ .map(|(i, _)| i)
+ .collect();
+ if prio_list.is_empty() {
+ panic!("No scheduleable tasks!");
+ }
+ prio_list.sort_by(|a, b| {
+ let ta = &self.tasks[*a];
+ let tb = &self.tasks[*b];
+ // Tasks that are I/O ready jump to the head of the queue.
+ // Otherwise, order by priority.
+ if ta.io_ready && !tb.io_ready {
+ Ordering::Greater
+ } else if tb.io_ready && !tb.io_ready {
+ Ordering::Less
+ } else {
+ ta.priority.cmp(&tb.priority)
}
- c = (c + 1) % self.tasks.len();
- };
+ });
+ // TODO: fairness for tasks of the same priority
+ self.pending_task = prio_list[0];
+ }
- 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(crate) fn task_swap(&mut self, regs: &mut TaskRegisters) {
+ if self.pending_task == self.current_task {
+ return;
}
+
+ // Store the registers of the current task
+ let t = &mut self.tasks[self.current_task];
+ t.regs = regs.clone();
+
+ // Set new task
+ self.current_task = self.pending_task;
+
+ // Restore the registers for that task
+ let t = &mut self.tasks[self.current_task];
+ *regs = t.regs.clone();
+ t.ticks_ran += 1;
}
pub fn sleep(&mut self, t: Ticks) {
let task = &self.tasks[self.current_task];
task.caps.iter().any(|token| token.captype() == cap)
}
+
+ // Block the current task if it holds the capability. Panic if it doesn't.
+ pub(crate) fn block_current_task(&mut self, cap: CapType) {
+ let task = &mut self.tasks[self.current_task];
+ if !task.has_capability(cap) {
+ panic!("task {} does not have {:?}", task.id, cap);
+ }
+ with_peripherals(|p| p.led().set_low()).ok();
+ task.state = TaskState::Blocked(cap);
+ }
+
+ /// Unblock any task holding the given capability. Returns true if
+ /// a task was found, otherwise false.
+ pub fn unblock_task_with_capability(&mut self, cap: CapType) -> bool {
+ let task = self
+ .tasks
+ .iter_mut()
+ .find(|t| t.state == TaskState::Blocked(cap));
+ if let Some(t) = task {
+ // Rewind the PC by two bytes so the SVC call that blocked
+ // gets re-run.
+ t.regs.pc -= 2;
+ t.state = TaskState::Running;
+ // Set PendSV to schedule immediately after exiting this
+ // interrupt.
+ SCB::set_pendsv();
+ with_peripherals(|p| p.led().set_high()).ok();
+ true
+ } else {
+ false
+ }
+ }
}
pub fn init_tasks() {
})
}
-pub fn current_process_has_capability(cap: CapType) -> bool {
+pub fn current_task_has_capability(cap: CapType) -> bool {
with_task_manager(|tm| tm.current_process_has_capability(cap))
}
"#
);
-pub unsafe fn launch(entry: fn() -> !) -> ! {
+pub unsafe fn launch_idle_task() -> ! {
let regs = with_task_manager(|tm| {
- tm.add_task(entry, &[]).ok();
+ tm.add_task(idle, &[], 255).ok();
tm.start();
- tm.tasks[0].task_registers.clone()
+ tm.tasks[0].regs.clone()
});
_launch(®s)
}
+
+fn idle(_base: u32, _size: u32) -> ! {
+ loop {
+ wfi();
+ }
+}
-use crate::capabilities::{CapToken, MAX_CAPS};
+use crate::capabilities::{CapToken, MAX_CAPS, CapType};
use crate::task::TaskId;
use crate::timer::Ticks;
#[derive(Debug, PartialEq)]
pub enum TaskState {
+ /// Task running normally; eligible to be scheduled
Running,
- Blocked(u32),
+ /// Blocked waiting for the fulfillment of a device indicated by
+ /// the capability type contained within.
+ Blocked(CapType),
+ /// Waiting for the current number of ticks to equal the value
+ /// within.
Sleeping(Ticks),
}
#[derive(Debug)]
pub(crate) struct TaskEntry {
pub id: TaskId,
- pub task_registers: TaskRegisters,
+ pub regs: TaskRegisters,
pub data: u32,
pub data_size: usize,
pub state: TaskState,
+ pub priority: u8,
+ pub io_ready: bool,
pub ticks_ran: u32,
pub caps: heapless::Vec<CapToken, MAX_CAPS>,
}
+
+impl TaskEntry {
+ pub fn has_capability(&self, cap: CapType) -> bool {
+ self.caps.iter().any(|c| c.captype() == cap)
+ }
+}
\ No newline at end of file
-use core::arch::global_asm;
use core::cell::RefCell;
use crate::bsp;
-use crate::task::{with_task_manager, TaskRegisters};
+use cortex_m::peripheral::SCB;
+use cortex_m_rt::exception;
use critical_section::Mutex;
#[link_section = ".kgram1"]
})
}
-global_asm!(r#"
- .global SysTick
- .type SysTick,function
-SysTick:
- ExceptionEntry
- bl systick_handler
- ExceptionExit
-"#);
-
-#[no_mangle]
-fn systick_handler(regs: Option<&mut TaskRegisters>) {
+#[exception]
+fn SysTick() {
let (t, pending) = critical_section::with(|cs| {
let mut timer = TIMER.borrow_ref_mut(cs);
if let Some(ref mut timer) = *timer {
(f)();
}
- // do not schedule unless TaskRegisters is valid
- if let Some(regs) = regs {
- with_task_manager(|tm| {
- tm.schedule(t, regs);
- });
- }
+ // Do a scheduling pass
+ SCB::set_pendsv();
}