commit:ae440dae6a1d9f560eda1d7f8d13aba77b21edfd
author:Chip
committer:Chip
date:Sun Sep 24 01:07:06 2023 -0500
parents:7cfe784d88145dfa2768498ff805848088d28b91
I/O blocking, priorities, PendSV scheduling
diff --git a/src/apps.rs b/src/apps.rs
line changes: +1/-0
index 0000000..6e52c7b
--- /dev/null
+++ b/src/apps.rs
@@ -0,0 +1 @@
+pub mod shell; 
\ No newline at end of file

diff --git a/src/apps/shell.rs b/src/apps/shell.rs
line changes: +133/-0
index 0000000..5856dc2
--- /dev/null
+++ b/src/apps/shell.rs
@@ -0,0 +1,133 @@
+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

diff --git a/src/console.rs b/src/console.rs
line changes: +65/-9
index ae17d8e..f089ba4
--- a/src/console.rs
+++ b/src/console.rs
@@ -2,46 +2,102 @@ extern crate alloc;
 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]
@@ -56,4 +112,4 @@ macro_rules! kprint {
     ($($arg:tt)*) => {{
         $($crate::console::write($arg.as_bytes());)*
     }}
-} 
\ No newline at end of file
+}

diff --git a/src/main.rs b/src/main.rs
line changes: +3/-30
index 5d107a4..d8de592
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@
 #![feature(error_in_core)]
 
 mod allocator;
+mod apps;
 mod asm;
 mod capabilities;
 mod console;
@@ -14,7 +15,6 @@ mod task;
 mod timer;
 
 use bsp::entry;
-use cortex_m::asm::wfi;
 use doa_hallonbrod as bsp;
 
 use allocator::init_allocator;
@@ -71,37 +71,10 @@ fn main() -> ! {
     */
 
     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()
     }
 }

diff --git a/src/panic.rs b/src/panic.rs
line changes: +1/-0
index c1cbbcf..3497f66
--- a/src/panic.rs
+++ b/src/panic.rs
@@ -14,6 +14,7 @@ fn panic(info: &PanicInfo) -> ! {
         }
         ALREADY_PANICKING = true;
     }
+    cortex_m::interrupt::disable();
     write(b"PANIC!\n");
 
     if let Some(p) = info.payload().downcast_ref::<&str>() {

diff --git a/src/peripherals.rs b/src/peripherals.rs
line changes: +8/-1
index 7a2a64d..38b32e7
--- a/src/peripherals.rs
+++ b/src/peripherals.rs
@@ -12,6 +12,7 @@ use bsp::hal::watchdog::Watchdog;
 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;
@@ -40,7 +41,13 @@ impl Peripherals {
 }
 
 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(

diff --git a/src/peripherals/usbdev.rs b/src/peripherals/usbdev.rs
line changes: +15/-5
index 4dad14d..dd84397
--- a/src/peripherals/usbdev.rs
+++ b/src/peripherals/usbdev.rs
@@ -1,12 +1,12 @@
 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::*;
@@ -43,9 +43,19 @@ impl<'a> UsbManager<'a> {
             }
             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),
             }
         }
     }
@@ -119,4 +129,4 @@ where
 #[interrupt]
 fn USBCTRL_IRQ() {
     with_usb(|u| u.update());
-} 
\ No newline at end of file
+}

diff --git a/src/stdlib.rs b/src/stdlib.rs
line changes: +15/-5
index d9134a3..2de1ce8
--- a/src/stdlib.rs
+++ b/src/stdlib.rs
@@ -3,7 +3,7 @@ use core::arch::global_asm;
 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#"
@@ -20,11 +20,21 @@ pub fn sleep(t: Ticks) {
 
 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

diff --git a/src/svc.rs b/src/svc.rs
line changes: +53/-7
index 39cc3ab..50c7025
--- a/src/svc.rs
+++ b/src/svc.rs
@@ -2,10 +2,11 @@ use core::arch::global_asm;
 
 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!(
@@ -24,22 +25,45 @@ 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);
+                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;
             }
 
@@ -55,3 +79,25 @@ unsafe fn svcall_handler(regs: Option<&mut TaskRegisters>) {
         _ => (), //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

diff --git a/src/task.rs b/src/task.rs
line changes: +98/-31
index 70d7c8b..1b33b8f
--- a/src/task.rs
+++ b/src/task.rs
@@ -3,15 +3,22 @@ mod error;
 
 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];
 
@@ -22,8 +29,9 @@ static TASK_MANAGER: Mutex<RefCell<TaskManager>> = Mutex::new(RefCell::new(TaskM
 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,
 }
 
@@ -34,6 +42,7 @@ impl TaskManager {
             next_tid: 0,
             tasks: heapless::Vec::new(),
             current_task: 0,
+            pending_task: 0,
             active: false,
         }
     }
@@ -48,8 +57,9 @@ impl TaskManager {
 
     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;
@@ -78,10 +88,12 @@ impl TaskManager {
 
         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,
         };
@@ -90,7 +102,7 @@ impl TaskManager {
         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;
         }
@@ -99,10 +111,6 @@ impl TaskManager {
             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 {
@@ -112,28 +120,49 @@ impl TaskManager {
             }
         }
 
-        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) {
@@ -149,6 +178,38 @@ impl TaskManager {
         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() {
@@ -165,7 +226,7 @@ where
     })
 }
 
-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))
 }
 
@@ -198,12 +259,18 @@ _launch:
 "#
 );
 
-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(&regs)
 }
+
+fn idle(_base: u32, _size: u32) -> ! {
+    loop {
+        wfi();
+    }
+}

diff --git a/src/task/entry.rs b/src/task/entry.rs
line changes: +16/-3
index 687af1f..f316d2e
--- a/src/task/entry.rs
+++ b/src/task/entry.rs
@@ -1,11 +1,16 @@
-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),
 }
 
@@ -58,10 +63,18 @@ impl TaskRegisters {
 #[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

diff --git a/src/timer.rs b/src/timer.rs
line changes: +6/-19
index b69f8f6..658ffc2
--- a/src/timer.rs
+++ b/src/timer.rs
@@ -1,8 +1,8 @@
-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"]
@@ -110,17 +110,8 @@ pub fn ticks() -> Ticks {
     })
 }
 
-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 {
@@ -135,10 +126,6 @@ fn systick_handler(regs: Option<&mut TaskRegisters>) {
         (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();
 }