firmware for my Touchscreen E-Paper Input Module for Framework Laptop 16

memory protection, exec syscall, make launcher unprivileged

+530 -238
+1
.idea/epd_firmware.iml
··· 14 14 <sourceFolder url="file://$MODULE_DIR$/eepy-serial/src" isTestSource="false" /> 15 15 <sourceFolder url="file://$MODULE_DIR$/eepy-sys/src" isTestSource="false" /> 16 16 <sourceFolder url="file://$MODULE_DIR$/eepy/src" isTestSource="false" /> 17 + <sourceFolder url="file://$MODULE_DIR$/eepy-launcher/src" isTestSource="false" /> 17 18 <excludeFolder url="file://$MODULE_DIR$/fw16_epd_bsp/target" /> 18 19 <excludeFolder url="file://$MODULE_DIR$/target" /> 19 20 <excludeFolder url="file://$MODULE_DIR$/fw16-epd-bsp/target" />
+2 -2
Cargo.toml
··· 10 10 "eepy-example-app", 11 11 "elf2epb", 12 12 "pervasive-spi", 13 - "tp370pgh01", 13 + "tp370pgh01", "eepy-launcher", 14 14 ] 15 15 16 16 [workspace.dependencies] ··· 32 32 heapless = "0.8" 33 33 once_cell = { version = "1.20", default-features = false, features = ["critical-section", "portable-atomic"] } 34 34 postcard = "1.1" 35 - serde = { version = "1.0", default-features = false, features = ["derive"] } 35 + serde = { version = "1.0", default-features = false, features = ["derive"] }
+1 -1
eepy-example-app/build.sh
··· 1 1 #!/bin/sh 2 2 3 - rm -r out 3 + rm -rf out 4 4 mkdir out 5 5 mkdir out/bins 6 6
+8 -1
eepy-example-app/src/main.rs
··· 21 21 #[link_section = ".header"] 22 22 #[used] 23 23 static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( 24 - "Example TESTING FOO BAR BAZ", 24 + "ExampleApp", 25 25 env!("CARGO_PKG_VERSION"), 26 26 entry, 27 27 ); ··· 33 33 let mut draw_target = EpdDrawTarget::new(); 34 34 35 35 let mut button = Button::with_default_style_auto_sized(Point::new(10, 40), "Click me", true); 36 + let mut exit_button = Button::with_default_style_auto_sized(Point::new(10, 386), "Exit", false); 36 37 button.draw_init(&mut draw_target); 38 + exit_button.draw_init(&mut draw_target); 37 39 draw_target.refresh(false, RefreshBlockMode::BlockAcknowledge); 38 40 39 41 let mut counter = 0; 40 42 41 43 loop { 42 44 while let Some(ev) = next_event() { 45 + if exit_button.tick(&mut draw_target, ev).clicked { 46 + return; 47 + } 48 + 43 49 let mut needs_refresh = false; 44 50 45 51 let response = button.tick(&mut draw_target, ev); 46 52 if response.clicked { 47 53 draw_target.clear(BinaryColor::Off).unwrap(); 48 54 button.draw_init(&mut draw_target); 55 + exit_button.draw_init(&mut draw_target); 49 56 50 57 counter += 1; 51 58 let mut s = String::<16>::new();
+8
eepy-launcher/.cargo/config.toml
··· 1 + [build] 2 + target = "thumbv6m-none-eabi" 3 + 4 + [target.thumbv6m-none-eabi] 5 + rustflags = [ 6 + "-C", "link-arg=--nmagic", 7 + "-C", "link-arg=-Tlauncher.x", 8 + ]
+1
eepy-launcher/.gitignore
··· 1 + out/
+10
eepy-launcher/Cargo.toml
··· 1 + [package] 2 + name = "eepy-launcher" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + eepy-sys = { path = "../eepy-sys" } 8 + eepy-gui = { path = "../eepy-gui" } 9 + embedded-graphics.workspace = true 10 + panic-halt = "1.0"
+7
eepy-launcher/build.sh
··· 1 + #!/bin/sh 2 + 3 + rm -rf out 4 + mkdir out 5 + cargo build --release 6 + elf2epb -i ../target/thumbv6m-none-eabi/release/eepy-launcher -o out/eepy-launcher.s00.epb 7 + truncate -s 128K out/eepy-launcher.s00.epb
+18
eepy-sys/src/exec.rs
··· 1 + use core::hint::unreachable_unchecked; 2 + use crate::syscall; 3 + use crate::syscall::SyscallNumber; 4 + 5 + pub fn exec(slot: u8) -> ! { 6 + unsafe { 7 + syscall!( 8 + SyscallNumber::Exec, 9 + in slot, 10 + ); 11 + 12 + unreachable_unchecked() 13 + } 14 + } 15 + 16 + pub fn exit() -> ! { 17 + exec(0); 18 + }
+138 -4
eepy-sys/src/header.rs
··· 1 1 use core::str::Utf8Error; 2 2 3 + #[cfg(feature = "defmt")] 4 + use defmt::{warn, debug}; 5 + 3 6 pub const XIP_BASE: *const u8 = 0x10000000 as *const u8; 4 7 pub const PROGRAM_RAM_AREA_BASE: *mut u8 = 0x20020000 as *mut u8; 5 8 6 9 pub const SLOT_SIZE: usize = 0x80000; 7 10 11 + pub const unsafe fn slot(id: u8) -> *const ProgramSlotHeader { 12 + if id > 31 { 13 + panic!("slot ID must be between 0 and 31"); 14 + } 15 + 16 + if id == 0 { 17 + // "Slot 0" is used for the launcher, which is stored 128K into the flash 18 + XIP_BASE.add(128 * 1024).cast() 19 + } else { 20 + XIP_BASE.add(SLOT_SIZE * id as usize).cast() 21 + } 22 + } 23 + 24 + 25 + pub struct Programs { 26 + id: u8, 27 + } 28 + 29 + impl Programs { 30 + pub fn new() -> Self { 31 + Self { id: 0 } 32 + } 33 + } 34 + 35 + impl Iterator for Programs { 36 + type Item = *const ProgramSlotHeader; 37 + 38 + fn next(&mut self) -> Option<Self::Item> { 39 + loop { 40 + self.id += 1; 41 + 42 + if self.id > 31 { 43 + return None; 44 + } 45 + 46 + let s = unsafe { slot(self.id) }; 47 + 48 + unsafe { 49 + if (*s).is_valid() { 50 + #[cfg(feature = "defmt")] 51 + debug!("Found program {} version {} in slot {}", (*s).name().unwrap(), (*s).version().unwrap(), self.id); 52 + return Some(s); 53 + } else { 54 + #[cfg(feature = "defmt")] 55 + debug!("No program found in slot {}", self.id); 56 + } 57 + } 58 + } 59 + } 60 + 61 + fn size_hint(&self) -> (usize, Option<usize>) { 62 + (0, Some(32 - self.id as usize)) 63 + } 64 + } 65 + 8 66 #[repr(C)] 9 67 pub struct ProgramSlotHeader { 10 68 pub block_erase_cycles: usize, ··· 13 71 14 72 pub data_len: usize, 15 73 pub data_lma: *const u8, 16 - pub data_vma: *const u8, 74 + pub data_vma: *mut u8, 17 75 18 76 pub bss_len: usize, 19 - pub bss_vma: *const u8, 77 + pub bss_vma: *mut u8, 20 78 21 79 pub name_len: usize, 22 80 pub name_ptr: *const u8, ··· 37 95 len: 0, 38 96 data_len: 0, 39 97 data_lma: core::ptr::null(), 40 - data_vma: core::ptr::null(), 98 + data_vma: core::ptr::null_mut(), 41 99 bss_len: 0, 42 - bss_vma: core::ptr::null(), 100 + bss_vma: core::ptr::null_mut(), 43 101 name_len: name.len(), 44 102 name_ptr: name.as_ptr(), 45 103 version_len: version.len(), ··· 48 106 } 49 107 } 50 108 109 + pub fn is_valid(&self) -> bool { 110 + // Erased flash contains all 1s 111 + if self.len == 0 || self.len == usize::MAX { 112 + return false; 113 + } 114 + 115 + if self.len > SLOT_SIZE { 116 + #[cfg(feature = "defmt")] 117 + warn!("Program header has invalid size"); 118 + return false; 119 + } 120 + 121 + if !self.check_crc() { 122 + #[cfg(feature = "defmt")] 123 + warn!("Program has invalid CRC"); 124 + return false; 125 + } 126 + 127 + if self.name().is_err() { 128 + #[cfg(feature = "defmt")] 129 + warn!("Program name is not valid UTF-8"); 130 + return false; 131 + } 132 + 133 + if self.version().is_err() { 134 + #[cfg(feature = "defmt")] 135 + warn!("Program version string is not valid UTF-8"); 136 + return false; 137 + } 138 + 139 + let slot_min = (&raw const *self) as usize; 140 + let slot_max = slot_min + SLOT_SIZE; 141 + let slot_range = slot_min..slot_max; 142 + let ram_min = PROGRAM_RAM_AREA_BASE as usize; 143 + let ram_max = ram_min + 136 * 1024; 144 + let ram_range = ram_min..ram_max; 145 + 146 + if self.data_len > 0 { 147 + if !slot_range.contains(&(self.data_lma as usize)) || !slot_range.contains(&(self.data_lma as usize + self.data_len - 1)) { 148 + #[cfg(feature = "defmt")] 149 + warn!("Program has invalid data section addresses"); 150 + return false; 151 + } 152 + 153 + if !ram_range.contains(&(self.data_vma as usize)) || !ram_range.contains(&(self.data_vma as usize + self.data_len - 1)) { 154 + #[cfg(feature = "defmt")] 155 + warn!("Program has invalid data section load addresses"); 156 + return false; 157 + } 158 + } 159 + 160 + if self.bss_len > 0 { 161 + if !ram_range.contains(&(self.bss_vma as usize)) || !ram_range.contains(&(self.data_vma as usize + self.data_len - 1)) { 162 + #[cfg(feature = "defmt")] 163 + warn!("Program has invalid bss section addresses"); 164 + return false; 165 + } 166 + } 167 + 168 + true 169 + } 170 + 171 + pub fn slot(&self) -> u8 { 172 + (((&raw const *self as usize) - XIP_BASE as usize) / SLOT_SIZE) as u8 173 + } 174 + 51 175 pub fn check_crc(&self) -> bool { 52 176 if self.len >= SLOT_SIZE || self.len < size_of::<ProgramSlotHeader>() { 53 177 return false; ··· 68 192 pub fn version(&self) -> Result<&str, Utf8Error> { 69 193 unsafe { 70 194 core::str::from_utf8(core::slice::from_raw_parts(self.version_ptr, self.version_len)) 195 + } 196 + } 197 + 198 + pub unsafe fn load(&self) { 199 + if self.data_len > 0 { 200 + core::ptr::copy_nonoverlapping(self.data_lma, self.data_vma, self.data_len); 201 + } 202 + 203 + if self.bss_len > 0 { 204 + self.bss_vma.write_bytes(0, self.bss_len); 71 205 } 72 206 } 73 207 }
+6 -41
eepy-sys/src/input.rs
··· 1 1 use core::fmt::{Display, Formatter}; 2 - use crate::syscall; 2 + use core::mem::MaybeUninit; 3 + use crate::{syscall, SafeOption}; 3 4 use crate::syscall::SyscallNumber; 4 5 5 6 #[repr(usize)] ··· 76 77 } 77 78 } 78 79 79 - #[repr(usize)] 80 - #[derive(Copy, Clone, Debug, Eq, PartialEq)] 81 - #[cfg_attr(feature = "defmt", derive(defmt::Format))] 82 - pub enum ScEventType { 83 - NoEvent = 0, 84 - TouchUp = 1, 85 - TouchDown = 2, 86 - TouchMove = 3, 87 - RefreshFinished = 4, 88 - } 89 - 90 - impl TryFrom<usize> for ScEventType { 91 - type Error = (); 92 - 93 - fn try_from(value: usize) -> Result<Self, Self::Error> { 94 - match value { 95 - x if x == ScEventType::NoEvent as usize => Ok(ScEventType::NoEvent), 96 - x if x == ScEventType::TouchUp as usize => Ok(ScEventType::TouchUp), 97 - x if x == ScEventType::TouchDown as usize => Ok(ScEventType::TouchDown), 98 - x if x == ScEventType::TouchMove as usize => Ok(ScEventType::TouchMove), 99 - x if x == ScEventType::RefreshFinished as usize => Ok(ScEventType::RefreshFinished), 100 - _ => Err(()), 101 - } 102 - } 103 - } 104 - 105 80 pub fn next_event() -> Option<Event> { 106 - let mut ev_type: usize; 107 - let mut x: u16; 108 - let mut y: u16; 81 + let mut event: MaybeUninit<SafeOption<Event>> = MaybeUninit::uninit(); 109 82 110 83 unsafe { 111 84 syscall!( 112 85 SyscallNumber::Input, 113 - out ev_type in InputSyscall::NextEvent, 114 - out x, 115 - out y, 86 + in InputSyscall::NextEvent, 87 + in event.as_mut_ptr(), 116 88 ); 117 - } 118 89 119 - match ScEventType::try_from(ev_type) { 120 - Ok(ScEventType::NoEvent) => None, 121 - Ok(ScEventType::TouchUp) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Up, x, y })), 122 - Ok(ScEventType::TouchDown) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Down, x, y })), 123 - Ok(ScEventType::TouchMove) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Move, x, y })), 124 - Ok(ScEventType::RefreshFinished) => Some(Event::RefreshFinished), 125 - Err(_) => panic!("invalid touch event"), 90 + event.assume_init().into() 126 91 } 127 92 } 128 93
+31 -1
eepy-sys/src/lib.rs
··· 6 6 pub mod image; 7 7 pub mod input; 8 8 pub mod usb; 9 + pub mod exec; 9 10 10 - pub use tp370pgh01::IMAGE_BYTES; 11 + pub use tp370pgh01::IMAGE_BYTES; 12 + 13 + /// FFI-safe version of the standard `Option` type. 14 + /// 15 + /// Convert to/from a standard `Option` using `.into()`. 16 + #[repr(C)] 17 + #[derive(Copy, Clone, Debug, Eq, PartialEq)] 18 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 19 + pub enum SafeOption<T> { 20 + None, 21 + Some(T), 22 + } 23 + 24 + impl<T> From<Option<T>> for SafeOption<T> { 25 + fn from(value: Option<T>) -> Self { 26 + match value { 27 + None => SafeOption::None, 28 + Some(v) => SafeOption::Some(v), 29 + } 30 + } 31 + } 32 + 33 + impl<T> From<SafeOption<T>> for Option<T> { 34 + fn from(value: SafeOption<T>) -> Self { 35 + match value { 36 + SafeOption::None => None, 37 + SafeOption::Some(v) => Some(v), 38 + } 39 + } 40 + }
+2
eepy-sys/src/syscall.rs
··· 6 6 Image = 1, 7 7 Input = 2, 8 8 Usb = 3, 9 + Exec = 4, 9 10 } 10 11 11 12 impl TryFrom<u8> for SyscallNumber { ··· 17 18 x if x == SyscallNumber::Image as u8 => Ok(SyscallNumber::Image), 18 19 x if x == SyscallNumber::Input as u8 => Ok(SyscallNumber::Input), 19 20 x if x == SyscallNumber::Usb as u8 => Ok(SyscallNumber::Usb), 21 + x if x == SyscallNumber::Exec as u8 => Ok(SyscallNumber::Exec), 20 22 _ => Err(()), 21 23 } 22 24 }
+52 -49
eepy/src/gui.rs eepy-launcher/src/main.rs
··· 1 - use defmt::debug; 1 + #![no_std] 2 + #![no_main] 3 + 4 + extern crate panic_halt; 5 + 6 + use core::arch::asm; 2 7 use embedded_graphics::geometry::AnchorPoint; 3 8 use embedded_graphics::pixelcolor::BinaryColor; 4 9 use embedded_graphics::prelude::*; ··· 7 12 use eepy_gui::element::button::Button; 8 13 use eepy_gui::element::Gui; 9 14 use eepy_gui::element::slider::Slider; 15 + use eepy_sys::exec::exec; 10 16 use eepy_sys::image::RefreshBlockMode; 11 17 use eepy_sys::input::{has_event, next_event, set_touch_enabled, Event, TouchEventType}; 18 + use eepy_sys::header::{ProgramSlotHeader, Programs}; 19 + 20 + #[link_section = ".header"] 21 + #[used] 22 + static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( 23 + "Launcher", 24 + env!("CARGO_PKG_VERSION"), 25 + entry, 26 + ); 12 27 13 28 enum Page { 14 29 MainPage, ··· 17 32 18 33 struct MainPage { 19 34 scratchpad_button: Button<'static>, 20 - test_buttons: [Button<'static>; 16], 21 - s1: Slider, 22 - s2: Slider, 35 + app_buttons: [Option<(Button<'static>, u8)>; 32], 23 36 } 24 37 25 38 impl MainPage { 26 39 fn new() -> Self { 27 - let test_buttons = [ 28 - Button::with_default_style(Rectangle::new(Point::new(10, 50), Size::new(40, 40)), "0", false), 29 - Button::with_default_style(Rectangle::new(Point::new(60, 50), Size::new(40, 40)), "1", false), 30 - Button::with_default_style(Rectangle::new(Point::new(110, 50), Size::new(40, 40)), "2", false), 31 - Button::with_default_style(Rectangle::new(Point::new(160, 50), Size::new(40, 40)), "3", false), 32 - Button::with_default_style(Rectangle::new(Point::new(10, 100), Size::new(40, 40)), "4", false), 33 - Button::with_default_style(Rectangle::new(Point::new(60, 100), Size::new(40, 40)), "5", false), 34 - Button::with_default_style(Rectangle::new(Point::new(110, 100), Size::new(40, 40)), "6", false), 35 - Button::with_default_style(Rectangle::new(Point::new(160, 100), Size::new(40, 40)), "7", false), 36 - Button::with_default_style(Rectangle::new(Point::new(10, 150), Size::new(40, 40)), "8", false), 37 - Button::with_default_style(Rectangle::new(Point::new(60, 150), Size::new(40, 40)), "9", false), 38 - Button::with_default_style(Rectangle::new(Point::new(110, 150), Size::new(40, 40)), "A", false), 39 - Button::with_default_style(Rectangle::new(Point::new(160, 150), Size::new(40, 40)), "B", false), 40 - Button::with_default_style(Rectangle::new(Point::new(10, 200), Size::new(40, 40)), "C", false), 41 - Button::with_default_style(Rectangle::new(Point::new(60, 200), Size::new(40, 40)), "D", false), 42 - Button::with_default_style(Rectangle::new(Point::new(110, 200), Size::new(40, 40)), "E", false), 43 - Button::with_default_style(Rectangle::new(Point::new(160, 200), Size::new(40, 40)), "F", false), 44 - ]; 40 + let mut buttons = [const { None }; 32]; 41 + let mut programs = Programs::new(); 45 42 43 + for y in 0..16 { 44 + for x in 0..2 { 45 + if let Some(prog) = programs.next() { 46 + let bi = y * 2 + x; 47 + let x_coord = if x == 0 { 10 } else { 125 }; 48 + let y_coord = 35 + 23 * y as i32; 49 + let button = Button::with_default_style( 50 + Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), 51 + unsafe { (*prog).name().unwrap() }, 52 + false, 53 + ); 54 + let slot_num = unsafe { (&*prog).slot() }; 55 + buttons[bi] = Some((button, slot_num)) 56 + } 57 + } 58 + } 46 59 47 60 Self { 48 61 scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), 49 - test_buttons, 50 - s1: Slider::with_default_style(Point::new(10, 300), 220, 1, 20, 10), 51 - s2: Slider::with_default_style(Point::new(10, 350), 220, 1, 20, 10), 62 + app_buttons: buttons, 52 63 } 53 64 } 54 65 } ··· 58 69 59 70 fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 60 71 self.scratchpad_button.draw_init(draw_target); 61 - self.s1.draw_init(draw_target); 62 - self.s2.draw_init(draw_target); 63 - for button in &self.test_buttons { 64 - button.draw_init(draw_target); 72 + for b in &self.app_buttons { 73 + if let Some((button, _)) = b { 74 + button.draw_init(draw_target); 75 + } 65 76 } 66 77 } 67 78 ··· 75 86 draw_target.refresh(true, RefreshBlockMode::BlockAcknowledge); 76 87 } 77 88 78 - for button in &mut self.test_buttons { 79 - needs_refresh |= button.tick(draw_target, ev).needs_refresh; 80 - } 89 + for b in &mut self.app_buttons { 90 + if let Some((button, s)) = b { 91 + let response = button.tick(draw_target, ev); 81 92 82 - if self.s1.tick(draw_target, ev) { 83 - needs_refresh = true; 84 - draw_target.fill_solid(&self.s2.bounding_box(), BinaryColor::Off).unwrap(); 85 - self.s2.marker_radius = self.s1.value; 86 - self.s2.draw_init(draw_target); 87 - } 93 + if response.clicked { 94 + exec(*s); 95 + } 88 96 89 - if self.s2.tick(draw_target, ev) { 90 - needs_refresh = true; 91 - draw_target.fill_solid(&self.s1.bounding_box(), BinaryColor::Off).unwrap(); 92 - self.s1.marker_radius = self.s2.value; 93 - self.s1.draw_init(draw_target); 97 + needs_refresh |= response.needs_refresh; 98 + } 94 99 } 95 100 96 101 if needs_refresh { ··· 184 189 185 190 if self.slider.tick(draw_target, ev) { 186 191 refresh = Some(RefreshBlockMode::NonBlocking); 187 - debug!("stroke width = {}", self.slider.value); 188 192 handle_drawing = false; 189 193 } 190 194 ··· 266 270 } 267 271 268 272 fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 269 - debug!("gui tick"); 270 273 if let Some(page) = self.get_current_page_mut().tick(draw_target, ev) { 271 274 self.current_page = page; 272 275 draw_target.clear(BinaryColor::Off).unwrap(); ··· 276 279 } 277 280 } 278 281 279 - pub(crate) fn gui_main(mut draw_target: EpdDrawTarget) -> ! { 280 - debug!("gui_main"); 281 282 283 + #[no_mangle] 284 + pub extern "C" fn entry() { 285 + let mut draw_target = EpdDrawTarget::new(); 282 286 set_touch_enabled(true); 283 287 let mut gui = MainGui::new(); 284 288 gui.draw_init(&mut draw_target); ··· 292 296 if !has_event() { 293 297 // has_event() is a syscall. The SVCall exception is a WFE wakeup event, so we need two 294 298 // WFEs so we don't immediately wake up. 295 - cortex_m::asm::wfe(); 296 - cortex_m::asm::wfe(); 299 + unsafe { asm!("wfe", "wfe") }; 297 300 } 298 301 } 299 302 }
+3
eepy/src/launcher.rs
··· 1 + #[link_section = ".launcher"] 2 + #[used] 3 + static LAUNCHER: [u8; 0x20000] = *include_bytes!("../../eepy-launcher/out/eepy-launcher.s00.epb");
+65 -14
eepy/src/main.rs
··· 1 1 #![no_main] 2 2 #![no_std] 3 3 4 - mod programs; 5 - mod gui; 6 4 mod serial; 7 5 mod ringbuffer; 8 6 mod syscall; 7 + mod launcher; 9 8 10 9 extern crate panic_probe; 11 10 extern crate defmt_rtt; 12 11 12 + use core::arch::asm; 13 13 use core::cell::RefCell; 14 14 use critical_section::Mutex; 15 15 use defmt::{debug, info, trace, warn}; 16 + use embedded_hal::delay::DelayNs; 16 17 use embedded_hal::digital::OutputPin; 17 18 use embedded_hal::i2c::I2c; 18 19 use mcp9808::MCP9808; ··· 25 26 use usb_device::bus::UsbBusAllocator; 26 27 use usb_device::prelude::*; 27 28 use usbd_serial::SerialPort; 29 + use eepy_sys::exec::exec; 28 30 use fw16_epd_bsp::{entry, hal, pac, EpdBusy, EpdCs, EpdDc, EpdPowerSwitch, EpdReset, EpdSck, EpdSdaWrite, EpdTouchInt, EpdTouchReset, I2CScl, I2CSda, LaptopSleep, Pins}; 29 31 use fw16_epd_bsp::hal::{Sio, Timer, I2C}; 30 32 use fw16_epd_bsp::hal::clocks::ClockSource; ··· 34 36 use fw16_epd_bsp::hal::timer::{Alarm, Alarm0}; 35 37 use fw16_epd_bsp::pac::I2C0; 36 38 use fw16_epd_bsp::pac::interrupt; 37 - use eepy_gui::draw_target::EpdDrawTarget; 38 39 use eepy_sys::input::{Event, TouchEvent, TouchEventType}; 39 40 use eepy_sys::misc::get_serial; 40 41 use tp370pgh01::rp2040::{Rp2040PervasiveSpiDelays, IoPin}; 41 42 use tp370pgh01::{Tp370pgh01, IMAGE_BYTES}; 42 - use crate::programs::{slot, Programs}; 43 43 use crate::ringbuffer::RingBuffer; 44 - //use crate::programs::Programs; 44 + 45 + const SRAM_END: *mut u8 = 0x20042000 as *mut u8; 45 46 46 47 static CORE1_STACK: Stack<8192> = Stack::new(); 47 48 ··· 107 108 108 109 core.SCB.set_sleepdeep(); 109 110 111 + // Set MPU regions for non-privileged code 112 + unsafe { 113 + // Region 0: Unprivileged RAM region (first 128K) - start 0x20020000, length 128K 114 + core.MPU.rnr.write(0); 115 + core.MPU.rbar.write(0x20020000); 116 + core.MPU.rasr.write( 117 + (0b1 << 28) // XN 118 + | (0b011 << 24) // AP: full access 119 + | (0b000 << 19) // TEX 120 + | (0b011 << 16) // S, C, B: non-shareable, writeback 121 + | (0b00000000 << 8) // all subregions enabled 122 + | (16 << 1) // size: 2^(16 + 1) = 128K 123 + | (0b1 << 0) // enable 124 + ); 125 + 126 + // Region 1: Unprivileged RAM region (final 8K) - start 0x20040000, length 8K 127 + core.MPU.rnr.write(1); 128 + core.MPU.rbar.write(0x20040000); 129 + core.MPU.rasr.write( 130 + (0b1 << 28) // XN 131 + | (0b011 << 24) // AP: full access 132 + | (0b000 << 19) // TEX 133 + | (0b011 << 16) // S, C, B: non-shareable, writeback 134 + | (0b00000000 << 8) // all subregions enabled 135 + | (12 << 1) // size: 2^(12 + 1) = 8K 136 + | (0b1 << 0) // enable 137 + ); 138 + 139 + // Region 3: Flash - start: 0x10000000, length 16M 140 + core.MPU.rnr.write(3); 141 + core.MPU.rbar.write(0x10000000); 142 + core.MPU.rasr.write( 143 + (0b0 << 28) // disable XN 144 + | (0b110 << 24) // AP: privileged or unprivileged read-only 145 + | (0b000 << 19) // TEX 146 + | (0b011 << 16) // S, C, B: non-shareable, writeback 147 + | (0b00000000 << 8) // all subregions enabled 148 + | (23 << 1) // size: 2^(23 + 1) = 16M 149 + | (0b1 << 0) // enable 150 + ); 151 + 152 + // Enable MPU 153 + core.MPU.ctrl.write( 154 + (0b1 << 2) // PRIVDEFENA: enable default memory map for privileged access 155 + | (0b0 << 1) // HFNMIENA: disable MPU for HardFault and NMI 156 + | (0b1 << 0) // ENABLE: enable MPU 157 + ); 158 + } 159 + 110 160 // Read flash unique ID 111 161 cortex_m::interrupt::disable(); 112 162 let mut id = [0u8; 8]; ··· 185 235 186 236 let mut timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); 187 237 // make sure temperature sensor has read temperature 188 - //timer.delay_ms(35); 238 + timer.delay_ms(35); 189 239 let mut alarm = timer.alarm_0().unwrap(); 190 240 alarm.enable_interrupt(); 191 241 critical_section::with(|cs| GLOBAL_ALARM0.borrow_ref_mut(cs).replace(alarm)); ··· 281 331 pac::NVIC::unmask(interrupt::SW0_IRQ); 282 332 }; 283 333 284 - // testing program loading 285 334 unsafe { 286 - let prog = slot(1); 287 - debug!("{}", (*prog).name().unwrap()); 288 - debug!("{}", (*prog).version().unwrap()); 289 - ((*prog).entry)(); 335 + asm!( 336 + "msr psp, {sram_end}", 337 + "msr control, {control_0b10}", 338 + "isb", 339 + sram_end = in(reg) SRAM_END, 340 + control_0b10 = in(reg) 0b10, 341 + ); 342 + 343 + exec(0); 290 344 } 291 - 292 - let draw_target = EpdDrawTarget::new(); 293 - gui::gui_main(draw_target); 294 345 } 295 346 296 347 #[interrupt]
-50
eepy/src/programs.rs
··· 1 - use eepy_sys::header::*; 2 - 3 - pub(crate) const unsafe fn slot(id: u8) -> *const ProgramSlotHeader { 4 - if id < 1 || id > 31 { 5 - panic!("slot ID must be between 1 and 31"); 6 - } 7 - 8 - XIP_BASE.add(SLOT_SIZE * id as usize).cast::<ProgramSlotHeader>() 9 - } 10 - 11 - 12 - pub(crate) struct Programs { 13 - id: u8, 14 - } 15 - 16 - impl Programs { 17 - pub(crate) fn new() -> Self { 18 - Self { id: 0 } 19 - } 20 - } 21 - 22 - impl Iterator for Programs { 23 - type Item = *const ProgramSlotHeader; 24 - 25 - fn next(&mut self) -> Option<Self::Item> { 26 - return None; 27 - /* 28 - loop { 29 - self.id += 1; 30 - 31 - if self.id == 32 { 32 - return None; 33 - } 34 - 35 - let s = unsafe { slot(self.id) }; 36 - 37 - if s.is_valid_program() { 38 - debug!("Found program {} version {} in slot {}", s.name().unwrap(), s.version_string().unwrap(), self.id); 39 - return Some(s); 40 - } else { 41 - debug!("No program found in slot {}", self.id); 42 - } 43 - 44 - }*/ 45 - } 46 - 47 - fn size_hint(&self) -> (usize, Option<usize>) { 48 - (0, Some(32 - self.id as usize)) 49 - } 50 - }
+111 -62
eepy/src/syscall.rs
··· 1 - use core::arch::global_asm; 2 - use defmt::trace; 1 + use core::arch::{asm, global_asm}; 2 + use defmt::{debug, trace, Formatter}; 3 + use eepy_sys::exec::exec; 4 + use eepy_sys::header::slot; 3 5 use eepy_sys::syscall::SyscallNumber; 6 + use crate::SRAM_END; 4 7 5 8 global_asm!(include_str!("syscall.s")); 6 9 10 + #[derive(Copy, Clone, Debug, Eq, PartialEq)] 11 + struct StackFrame { 12 + r0: usize, 13 + r1: usize, 14 + r2: usize, 15 + r3: usize, 16 + r12: usize, 17 + lr: *const u8, 18 + pc: *const u8, 19 + xpsr: usize, 20 + } 21 + 22 + impl defmt::Format for StackFrame { 23 + fn format(&self, fmt: Formatter) { 24 + defmt::write!( 25 + fmt, 26 + "r0=0x{:x} r1=0x{:x} r2=0x{:x} r3=0x{:x} r12=0x{:x} lr={:x} pc={:x} xpsr=0x{:x}", 27 + self.r0, 28 + self.r1, 29 + self.r2, 30 + self.r3, 31 + self.r12, 32 + self.lr, 33 + self.pc, 34 + self.xpsr, 35 + ) 36 + } 37 + } 38 + 7 39 /// Main syscall (SVC) handler. 8 40 /// 9 41 /// This function is called by the assembly trampoline code in `syscall.s`. ··· 15 47 /// The pc value from the stack can be used to extract the literal operand from the SVC instruction 16 48 /// which triggered the syscall. 17 49 #[no_mangle] 18 - extern "C" fn handle_syscall(sp: *mut usize) { 50 + extern "C" fn handle_syscall(sp: *mut StackFrame, using_psp: bool) { 19 51 // Stack contains R0, R1, R2, R3, R12, LR, ReturnAddress, xPSR 20 - let stack_values = unsafe { core::slice::from_raw_parts_mut(sp, 8) }; 21 - let syscall_num = unsafe { (stack_values[6] as *const u8).sub(2).read() }; 52 + let stack_values = unsafe { &mut *sp }; 53 + let syscall_num = unsafe { *stack_values.pc.sub(2) }; 22 54 23 - trace!( 24 - "syscall: sp={} imm={:x} r0={:x} r1={:x} r2={:x} r3={:x} r12={:x} lr={:x} pc={:x} xpsr={:x}", 25 - sp, 26 - syscall_num, 27 - stack_values[0], 28 - stack_values[1], 29 - stack_values[2], 30 - stack_values[3], 31 - stack_values[4], 32 - stack_values[5], 33 - stack_values[6], 34 - stack_values[7], 35 - ); 55 + trace!("syscall: sp={} imm={:x} {}", sp, syscall_num, stack_values); 36 56 37 57 match SyscallNumber::try_from(syscall_num) { 38 58 Ok(SyscallNumber::Misc) => misc::handle_misc(stack_values), 39 59 Ok(SyscallNumber::Image) => image::handle_image(stack_values), 40 60 Ok(SyscallNumber::Input) => input::handle_input(stack_values), 41 61 Ok(SyscallNumber::Usb) => todo!("usb syscalls"), 42 - _ => panic!("illegal syscall"), 62 + Ok(SyscallNumber::Exec) => handle_exec(stack_values, using_psp), 63 + Err(_) => panic!("illegal syscall"), 64 + } 65 + } 66 + 67 + fn handle_exec(stack_values: &mut StackFrame, using_psp: bool) { 68 + // disable privileged mode 69 + unsafe { 70 + asm!( 71 + "msr control, {control_0b11}", 72 + "isb", 73 + control_0b11 = in(reg) 0b11, 74 + ); 75 + } 76 + 77 + let slot_n = stack_values.r0 as u8; 78 + unsafe { 79 + let program = slot(slot_n); 80 + if !(*program).is_valid() { 81 + panic!("tried to exec invalid program"); 82 + } 83 + 84 + (*program).load(); 85 + stack_values.pc = core::mem::transmute((*program).entry); 86 + stack_values.lr = program_return_handler as *const u8; 87 + 88 + // Move the saved registers to the top of program memory 89 + // This makes sure all programs start with an empty program stack 90 + if using_psp { 91 + let ptr: *mut StackFrame = SRAM_END.sub(size_of::<StackFrame>()).cast(); 92 + ptr.write(*stack_values); 93 + asm!( 94 + "msr psp, {ptr}", 95 + ptr = in(reg) ptr, 96 + ); 97 + } else { 98 + // If the saved registers are on the main stack, just set the PSP to 99 + // the top of program memory 100 + asm!( 101 + "msr psp, {ptr}", 102 + ptr = in(reg) SRAM_END, 103 + ) 104 + } 43 105 } 44 106 } 45 107 108 + // NOTE: this function runs in unprivileged thread mode 109 + extern "C" fn program_return_handler() { 110 + exec(0); 111 + } 112 + 46 113 mod misc { 47 114 use eepy_sys::misc::MiscSyscall; 48 115 use crate::SERIAL_NUMBER; 116 + use super::StackFrame; 49 117 50 - pub(super) fn handle_misc(stack_values: &mut [usize]) { 51 - match MiscSyscall::try_from(stack_values[0]) { 118 + pub(super) fn handle_misc(stack_values: &mut StackFrame) { 119 + match MiscSyscall::try_from(stack_values.r0) { 52 120 Ok(MiscSyscall::GetSerial) => handle_get_serial(stack_values), 53 121 _ => panic!("illegal syscall"), 54 122 } 55 123 } 56 124 57 - fn handle_get_serial(stack_values: &mut [usize]) { 58 - stack_values[0] = (&raw const *SERIAL_NUMBER.get().unwrap()) as usize; 125 + fn handle_get_serial(stack_values: &mut StackFrame) { 126 + stack_values.r0 = (&raw const *SERIAL_NUMBER.get().unwrap()) as usize; 59 127 } 60 128 } 61 129 ··· 64 132 use eepy_sys::image::{ImageSyscall, RefreshBlockMode}; 65 133 use tp370pgh01::IMAGE_BYTES; 66 134 use crate::{DO_REFRESH, FAST_REFRESH, IMAGE_BUFFER, REFRESHING}; 135 + use super::StackFrame; 67 136 68 - pub(super) fn handle_image(stack_values: &mut [usize]) { 69 - match ImageSyscall::try_from(stack_values[0]) { 137 + pub(super) fn handle_image(stack_values: &mut StackFrame) { 138 + match ImageSyscall::try_from(stack_values.r0) { 70 139 Ok(ImageSyscall::WriteImage) => handle_write_image(stack_values), 71 140 Ok(ImageSyscall::Refresh) => handle_refresh(stack_values), 72 141 _ => panic!("illegal syscall"), 73 142 } 74 143 } 75 144 76 - fn handle_write_image(stack_values: &mut [usize]) { 77 - let image: &[u8; IMAGE_BYTES] = unsafe { &*(stack_values[1] as *const [u8; IMAGE_BYTES]) }; 145 + fn handle_write_image(stack_values: &mut StackFrame) { 146 + let image: &[u8; IMAGE_BYTES] = unsafe { &*(stack_values.r1 as *const [u8; IMAGE_BYTES]) }; 78 147 critical_section::with(|cs| IMAGE_BUFFER.borrow_ref_mut(cs).copy_from_slice(image)); 79 148 } 80 149 81 - fn handle_refresh(stack_values: &mut [usize]) { 82 - let fast_refresh = stack_values[1] != 0; 83 - let blocking_mode = RefreshBlockMode::try_from(stack_values[2]).expect("illegal refresh blocking mode"); 150 + fn handle_refresh(stack_values: &mut StackFrame) { 151 + let fast_refresh = stack_values.r1 != 0; 152 + let blocking_mode = RefreshBlockMode::try_from(stack_values.r2).expect("illegal refresh blocking mode"); 84 153 85 154 DO_REFRESH.store(true, Ordering::Relaxed); 86 155 FAST_REFRESH.store(fast_refresh, Ordering::Relaxed); ··· 98 167 99 168 mod input { 100 169 use core::sync::atomic::Ordering; 101 - use eepy_sys::input::{Event, InputSyscall, ScEventType, TouchEvent, TouchEventType}; 170 + use eepy_sys::input::{Event, InputSyscall}; 171 + use eepy_sys::SafeOption; 102 172 use crate::{EVENT_QUEUE, TOUCH_ENABLED}; 173 + use super::StackFrame; 103 174 104 - pub(super) fn handle_input(stack_values: &mut [usize]) { 105 - match InputSyscall::try_from(stack_values[0]) { 175 + pub(super) fn handle_input(stack_values: &mut StackFrame) { 176 + match InputSyscall::try_from(stack_values.r0) { 106 177 Ok(InputSyscall::NextEvent) => handle_next_event(stack_values), 107 178 Ok(InputSyscall::SetTouchEnabled) => handle_set_touch_enabled(stack_values), 108 179 Ok(InputSyscall::HasEvent) => handle_has_event(stack_values), ··· 110 181 } 111 182 } 112 183 113 - fn handle_next_event(stack_values: &mut [usize]) { 184 + fn handle_next_event(stack_values: &mut StackFrame) { 114 185 let next_event = critical_section::with(|cs| EVENT_QUEUE.borrow_ref_mut(cs).pop()); 115 - let mut x = 0; 116 - let mut y = 0; 117 - let sc_ev_type = match next_event { 118 - None => ScEventType::NoEvent, 119 - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Down, x: px, y: py })) => { 120 - x = px; 121 - y = py; 122 - ScEventType::TouchDown 123 - }, 124 - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Up, x: px, y: py })) => { 125 - x = px; 126 - y = py; 127 - ScEventType::TouchUp 128 - }, 129 - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Move, x: px, y: py })) => { 130 - x = px; 131 - y = py; 132 - ScEventType::TouchMove 133 - }, 134 - Some(Event::RefreshFinished) => ScEventType::RefreshFinished, 135 - }; 136 - stack_values[0] = sc_ev_type as usize; 137 - stack_values[1] = x as usize; 138 - stack_values[2] = y as usize; 186 + let ptr = stack_values.r1 as *mut SafeOption<Event>; 187 + unsafe { ptr.write(next_event.into()) }; 139 188 } 140 189 141 - fn handle_set_touch_enabled(stack_values: &mut [usize]) { 142 - let enable = stack_values[1] != 0; 190 + fn handle_set_touch_enabled(stack_values: &mut StackFrame) { 191 + let enable = stack_values.r1 != 0; 143 192 TOUCH_ENABLED.store(enable, Ordering::Relaxed); 144 193 if !enable { 145 194 critical_section::with(|cs| EVENT_QUEUE.borrow_ref_mut(cs).clear()); 146 195 } 147 196 } 148 197 149 - fn handle_has_event(stack_values: &mut [usize]) { 198 + fn handle_has_event(stack_values: &mut StackFrame) { 150 199 let empty = critical_section::with(|cs| EVENT_QUEUE.borrow_ref(cs).is_empty()); 151 - stack_values[0] = (!empty) as usize; 200 + stack_values.r0 = (!empty) as usize; 152 201 } 153 202 }
+9 -8
eepy/src/syscall.s
··· 1 1 .global SVCall 2 - .equ SVCall, _SVCall + 1 /* thumb bit */ 3 2 4 - _SVCall: 3 + .thumb_func 4 + SVCall: 5 + ldr r2, =handle_syscall 5 6 movs r0, #4 6 7 mov r1, lr 7 8 tst r0, r1 8 - beq .use_msp 9 + beq 100f // use_msp 9 10 mrs r0, psp 10 - ldr r1, =handle_syscall 11 - bx r1 12 - .use_msp: 11 + movs r1, #1 12 + bx r2 13 + 100: // use_msp 13 14 mrs r0, msp 14 - ldr r1, =handle_syscall 15 - bx r1 15 + movs r1, #0 16 + bx r2
+50
launcher.x
··· 1 + EXTERN(entry); 2 + ENTRY(entry); 3 + 4 + MEMORY { 5 + RAM : ORIGIN = 0x20020000, LENGTH = 136K 6 + SLOT: ORIGIN = 0x10000000 + 128K, LENGTH = 128K 7 + } 8 + 9 + SECTIONS { 10 + .header ORIGIN(SLOT) : { 11 + KEEP(*(.header)); 12 + } > SLOT 13 + 14 + .text : ALIGN(4) { 15 + . = ALIGN(4); 16 + *(.text .text.*); 17 + . = ALIGN(4); 18 + } > SLOT 19 + 20 + .rodata : ALIGN(4) { 21 + . = ALIGN(4); 22 + *(.rodata .rodata.*); 23 + . = ALIGN(4); 24 + } > SLOT 25 + 26 + .data : ALIGN(4) { 27 + . = ALIGN(4); 28 + KEEP(*(.data .data.*)); 29 + . = ALIGN(4); 30 + } > RAM AT>SLOT 31 + 32 + .bss (NOLOAD) : ALIGN(4) { 33 + . = ALIGN(4); 34 + *(.bss .bss.*); 35 + *(COMMON); 36 + . = ALIGN(4); 37 + } > RAM 38 + 39 + .uninit (NOLOAD) : ALIGN(4) { 40 + . = ALIGN(4); 41 + *(.uninit .uninit.*); 42 + . = ALIGN(4); 43 + } > RAM 44 + 45 + /DISCARD/ : { 46 + *(.ARM.exidx); 47 + *(.ARM.exidx.*); 48 + *(.ARM.extab.*); 49 + } 50 + }
+7 -5
memory.x
··· 1 1 MEMORY { 2 2 BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 3 - FLASH : ORIGIN = 0x10000100, LENGTH = 256K - 0x100 3 + FLASH : ORIGIN = 0x10000100, LENGTH = 128K - 0x100 4 + LAUNCHER: ORIGIN = 0x10020000, LENGTH = 128K 4 5 RESERVED: ORIGIN = 0x10040000, LENGTH = 256K 5 - PROGRAM_SLOT_1: ORIGIN = 0x10080000, LENGTH = 512K 6 + SLOTS: ORIGIN = 0x10080000, LENGTH = 16M - 512K 6 7 RAM : ORIGIN = 0x20000000, LENGTH = 128K 7 8 PROGRAM_RAM : ORIGIN = 0x20020000, LENGTH = 136K 8 9 } ··· 18 19 } INSERT BEFORE .text; 19 20 20 21 SECTIONS { 21 - .prog1 ORIGIN(PROGRAM_SLOT_1) : { 22 - KEEP(*(.prog1)); 23 - } > PROGRAM_SLOT_1 22 + .launcher ORIGIN(LAUNCHER) : 23 + { 24 + KEEP(*(.launcher)); 25 + } > LAUNCHER 24 26 } INSERT AFTER .text;