diff --git a/.idea/epd_firmware.iml b/.idea/epd_firmware.iml index 7a925a2..c23e579 100644 --- a/.idea/epd_firmware.iml +++ b/.idea/epd_firmware.iml @@ -14,6 +14,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index aabe5dc..c248a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ "eepy-example-app", "elf2epb", "pervasive-spi", - "tp370pgh01", + "tp370pgh01", "eepy-launcher", ] [workspace.dependencies] @@ -32,4 +32,4 @@ embedded-graphics = "0.8.1" heapless = "0.8" once_cell = { version = "1.20", default-features = false, features = ["critical-section", "portable-atomic"] } postcard = "1.1" -serde = { version = "1.0", default-features = false, features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/eepy-example-app/build.sh b/eepy-example-app/build.sh index 235df35..60920b1 100755 --- a/eepy-example-app/build.sh +++ b/eepy-example-app/build.sh @@ -1,6 +1,6 @@ #!/bin/sh -rm -r out +rm -rf out mkdir out mkdir out/bins diff --git a/eepy-example-app/src/main.rs b/eepy-example-app/src/main.rs index 24f5b6a..8726a42 100644 --- a/eepy-example-app/src/main.rs +++ b/eepy-example-app/src/main.rs @@ -21,7 +21,7 @@ use eepy_sys::input::{has_event, next_event, set_touch_enabled}; #[link_section = ".header"] #[used] static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( - "Example TESTING FOO BAR BAZ", + "ExampleApp", env!("CARGO_PKG_VERSION"), entry, ); @@ -33,19 +33,26 @@ pub extern "C" fn entry() { let mut draw_target = EpdDrawTarget::new(); let mut button = Button::with_default_style_auto_sized(Point::new(10, 40), "Click me", true); + let mut exit_button = Button::with_default_style_auto_sized(Point::new(10, 386), "Exit", false); button.draw_init(&mut draw_target); + exit_button.draw_init(&mut draw_target); draw_target.refresh(false, RefreshBlockMode::BlockAcknowledge); let mut counter = 0; loop { while let Some(ev) = next_event() { + if exit_button.tick(&mut draw_target, ev).clicked { + return; + } + let mut needs_refresh = false; let response = button.tick(&mut draw_target, ev); if response.clicked { draw_target.clear(BinaryColor::Off).unwrap(); button.draw_init(&mut draw_target); + exit_button.draw_init(&mut draw_target); counter += 1; let mut s = String::<16>::new(); diff --git a/eepy-launcher/.cargo/config.toml b/eepy-launcher/.cargo/config.toml new file mode 100644 index 0000000..f1fa78a --- /dev/null +++ b/eepy-launcher/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "thumbv6m-none-eabi" + +[target.thumbv6m-none-eabi] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlauncher.x", +] \ No newline at end of file diff --git a/eepy-launcher/.gitignore b/eepy-launcher/.gitignore new file mode 100644 index 0000000..466e248 --- /dev/null +++ b/eepy-launcher/.gitignore @@ -0,0 +1 @@ +out/ \ No newline at end of file diff --git a/eepy-launcher/Cargo.toml b/eepy-launcher/Cargo.toml new file mode 100644 index 0000000..703d12e --- /dev/null +++ b/eepy-launcher/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "eepy-launcher" +version = "0.1.0" +edition = "2021" + +[dependencies] +eepy-sys = { path = "../eepy-sys" } +eepy-gui = { path = "../eepy-gui" } +embedded-graphics.workspace = true +panic-halt = "1.0" \ No newline at end of file diff --git a/eepy-launcher/build.sh b/eepy-launcher/build.sh new file mode 100755 index 0000000..2072fc3 --- /dev/null +++ b/eepy-launcher/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +rm -rf out +mkdir out +cargo build --release +elf2epb -i ../target/thumbv6m-none-eabi/release/eepy-launcher -o out/eepy-launcher.s00.epb +truncate -s 128K out/eepy-launcher.s00.epb \ No newline at end of file diff --git a/eepy/src/gui.rs b/eepy-launcher/src/main.rs similarity index 71% rename from eepy/src/gui.rs rename to eepy-launcher/src/main.rs index b43ea70..73c656d 100644 --- a/eepy/src/gui.rs +++ b/eepy-launcher/src/main.rs @@ -1,4 +1,9 @@ -use defmt::debug; +#![no_std] +#![no_main] + +extern crate panic_halt; + +use core::arch::asm; use embedded_graphics::geometry::AnchorPoint; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::*; @@ -7,8 +12,18 @@ use eepy_gui::draw_target::EpdDrawTarget; use eepy_gui::element::button::Button; use eepy_gui::element::Gui; use eepy_gui::element::slider::Slider; +use eepy_sys::exec::exec; use eepy_sys::image::RefreshBlockMode; use eepy_sys::input::{has_event, next_event, set_touch_enabled, Event, TouchEventType}; +use eepy_sys::header::{ProgramSlotHeader, Programs}; + +#[link_section = ".header"] +#[used] +static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( + "Launcher", + env!("CARGO_PKG_VERSION"), + entry, +); enum Page { MainPage, @@ -17,38 +32,34 @@ enum Page { struct MainPage { scratchpad_button: Button<'static>, - test_buttons: [Button<'static>; 16], - s1: Slider, - s2: Slider, + app_buttons: [Option<(Button<'static>, u8)>; 32], } impl MainPage { fn new() -> Self { - let test_buttons = [ - Button::with_default_style(Rectangle::new(Point::new(10, 50), Size::new(40, 40)), "0", false), - Button::with_default_style(Rectangle::new(Point::new(60, 50), Size::new(40, 40)), "1", false), - Button::with_default_style(Rectangle::new(Point::new(110, 50), Size::new(40, 40)), "2", false), - Button::with_default_style(Rectangle::new(Point::new(160, 50), Size::new(40, 40)), "3", false), - Button::with_default_style(Rectangle::new(Point::new(10, 100), Size::new(40, 40)), "4", false), - Button::with_default_style(Rectangle::new(Point::new(60, 100), Size::new(40, 40)), "5", false), - Button::with_default_style(Rectangle::new(Point::new(110, 100), Size::new(40, 40)), "6", false), - Button::with_default_style(Rectangle::new(Point::new(160, 100), Size::new(40, 40)), "7", false), - Button::with_default_style(Rectangle::new(Point::new(10, 150), Size::new(40, 40)), "8", false), - Button::with_default_style(Rectangle::new(Point::new(60, 150), Size::new(40, 40)), "9", false), - Button::with_default_style(Rectangle::new(Point::new(110, 150), Size::new(40, 40)), "A", false), - Button::with_default_style(Rectangle::new(Point::new(160, 150), Size::new(40, 40)), "B", false), - Button::with_default_style(Rectangle::new(Point::new(10, 200), Size::new(40, 40)), "C", false), - Button::with_default_style(Rectangle::new(Point::new(60, 200), Size::new(40, 40)), "D", false), - Button::with_default_style(Rectangle::new(Point::new(110, 200), Size::new(40, 40)), "E", false), - Button::with_default_style(Rectangle::new(Point::new(160, 200), Size::new(40, 40)), "F", false), - ]; - + let mut buttons = [const { None }; 32]; + let mut programs = Programs::new(); + + for y in 0..16 { + for x in 0..2 { + if let Some(prog) = programs.next() { + let bi = y * 2 + x; + let x_coord = if x == 0 { 10 } else { 125 }; + let y_coord = 35 + 23 * y as i32; + let button = Button::with_default_style( + Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), + unsafe { (*prog).name().unwrap() }, + false, + ); + let slot_num = unsafe { (&*prog).slot() }; + buttons[bi] = Some((button, slot_num)) + } + } + } Self { scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), - test_buttons, - s1: Slider::with_default_style(Point::new(10, 300), 220, 1, 20, 10), - s2: Slider::with_default_style(Point::new(10, 350), 220, 1, 20, 10), + app_buttons: buttons, } } } @@ -58,10 +69,10 @@ impl Gui for MainPage { fn draw_init(&self, draw_target: &mut EpdDrawTarget) { self.scratchpad_button.draw_init(draw_target); - self.s1.draw_init(draw_target); - self.s2.draw_init(draw_target); - for button in &self.test_buttons { - button.draw_init(draw_target); + for b in &self.app_buttons { + if let Some((button, _)) = b { + button.draw_init(draw_target); + } } } @@ -75,22 +86,16 @@ impl Gui for MainPage { draw_target.refresh(true, RefreshBlockMode::BlockAcknowledge); } - for button in &mut self.test_buttons { - needs_refresh |= button.tick(draw_target, ev).needs_refresh; - } + for b in &mut self.app_buttons { + if let Some((button, s)) = b { + let response = button.tick(draw_target, ev); - if self.s1.tick(draw_target, ev) { - needs_refresh = true; - draw_target.fill_solid(&self.s2.bounding_box(), BinaryColor::Off).unwrap(); - self.s2.marker_radius = self.s1.value; - self.s2.draw_init(draw_target); - } + if response.clicked { + exec(*s); + } - if self.s2.tick(draw_target, ev) { - needs_refresh = true; - draw_target.fill_solid(&self.s1.bounding_box(), BinaryColor::Off).unwrap(); - self.s1.marker_radius = self.s2.value; - self.s1.draw_init(draw_target); + needs_refresh |= response.needs_refresh; + } } if needs_refresh { @@ -184,7 +189,6 @@ impl Gui for ScratchpadPage { if self.slider.tick(draw_target, ev) { refresh = Some(RefreshBlockMode::NonBlocking); - debug!("stroke width = {}", self.slider.value); handle_drawing = false; } @@ -266,7 +270,6 @@ impl Gui for MainGui { } fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { - debug!("gui tick"); if let Some(page) = self.get_current_page_mut().tick(draw_target, ev) { self.current_page = page; draw_target.clear(BinaryColor::Off).unwrap(); @@ -276,9 +279,10 @@ impl Gui for MainGui { } } -pub(crate) fn gui_main(mut draw_target: EpdDrawTarget) -> ! { - debug!("gui_main"); +#[no_mangle] +pub extern "C" fn entry() { + let mut draw_target = EpdDrawTarget::new(); set_touch_enabled(true); let mut gui = MainGui::new(); gui.draw_init(&mut draw_target); @@ -292,8 +296,7 @@ pub(crate) fn gui_main(mut draw_target: EpdDrawTarget) -> ! { if !has_event() { // has_event() is a syscall. The SVCall exception is a WFE wakeup event, so we need two // WFEs so we don't immediately wake up. - cortex_m::asm::wfe(); - cortex_m::asm::wfe(); + unsafe { asm!("wfe", "wfe") }; } } } \ No newline at end of file diff --git a/eepy-sys/src/exec.rs b/eepy-sys/src/exec.rs new file mode 100644 index 0000000..69d739d --- /dev/null +++ b/eepy-sys/src/exec.rs @@ -0,0 +1,18 @@ +use core::hint::unreachable_unchecked; +use crate::syscall; +use crate::syscall::SyscallNumber; + +pub fn exec(slot: u8) -> ! { + unsafe { + syscall!( + SyscallNumber::Exec, + in slot, + ); + + unreachable_unchecked() + } +} + +pub fn exit() -> ! { + exec(0); +} \ No newline at end of file diff --git a/eepy-sys/src/header.rs b/eepy-sys/src/header.rs index ac190b1..0ef0cdd 100644 --- a/eepy-sys/src/header.rs +++ b/eepy-sys/src/header.rs @@ -1,10 +1,68 @@ use core::str::Utf8Error; +#[cfg(feature = "defmt")] +use defmt::{warn, debug}; + pub const XIP_BASE: *const u8 = 0x10000000 as *const u8; pub const PROGRAM_RAM_AREA_BASE: *mut u8 = 0x20020000 as *mut u8; pub const SLOT_SIZE: usize = 0x80000; +pub const unsafe fn slot(id: u8) -> *const ProgramSlotHeader { + if id > 31 { + panic!("slot ID must be between 0 and 31"); + } + + if id == 0 { + // "Slot 0" is used for the launcher, which is stored 128K into the flash + XIP_BASE.add(128 * 1024).cast() + } else { + XIP_BASE.add(SLOT_SIZE * id as usize).cast() + } +} + + +pub struct Programs { + id: u8, +} + +impl Programs { + pub fn new() -> Self { + Self { id: 0 } + } +} + +impl Iterator for Programs { + type Item = *const ProgramSlotHeader; + + fn next(&mut self) -> Option { + loop { + self.id += 1; + + if self.id > 31 { + return None; + } + + let s = unsafe { slot(self.id) }; + + unsafe { + if (*s).is_valid() { + #[cfg(feature = "defmt")] + debug!("Found program {} version {} in slot {}", (*s).name().unwrap(), (*s).version().unwrap(), self.id); + return Some(s); + } else { + #[cfg(feature = "defmt")] + debug!("No program found in slot {}", self.id); + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(32 - self.id as usize)) + } +} + #[repr(C)] pub struct ProgramSlotHeader { pub block_erase_cycles: usize, @@ -13,10 +71,10 @@ pub struct ProgramSlotHeader { pub data_len: usize, pub data_lma: *const u8, - pub data_vma: *const u8, + pub data_vma: *mut u8, pub bss_len: usize, - pub bss_vma: *const u8, + pub bss_vma: *mut u8, pub name_len: usize, pub name_ptr: *const u8, @@ -37,9 +95,9 @@ impl ProgramSlotHeader { len: 0, data_len: 0, data_lma: core::ptr::null(), - data_vma: core::ptr::null(), + data_vma: core::ptr::null_mut(), bss_len: 0, - bss_vma: core::ptr::null(), + bss_vma: core::ptr::null_mut(), name_len: name.len(), name_ptr: name.as_ptr(), version_len: version.len(), @@ -48,6 +106,72 @@ impl ProgramSlotHeader { } } + pub fn is_valid(&self) -> bool { + // Erased flash contains all 1s + if self.len == 0 || self.len == usize::MAX { + return false; + } + + if self.len > SLOT_SIZE { + #[cfg(feature = "defmt")] + warn!("Program header has invalid size"); + return false; + } + + if !self.check_crc() { + #[cfg(feature = "defmt")] + warn!("Program has invalid CRC"); + return false; + } + + if self.name().is_err() { + #[cfg(feature = "defmt")] + warn!("Program name is not valid UTF-8"); + return false; + } + + if self.version().is_err() { + #[cfg(feature = "defmt")] + warn!("Program version string is not valid UTF-8"); + return false; + } + + let slot_min = (&raw const *self) as usize; + let slot_max = slot_min + SLOT_SIZE; + let slot_range = slot_min..slot_max; + let ram_min = PROGRAM_RAM_AREA_BASE as usize; + let ram_max = ram_min + 136 * 1024; + let ram_range = ram_min..ram_max; + + if self.data_len > 0 { + if !slot_range.contains(&(self.data_lma as usize)) || !slot_range.contains(&(self.data_lma as usize + self.data_len - 1)) { + #[cfg(feature = "defmt")] + warn!("Program has invalid data section addresses"); + return false; + } + + if !ram_range.contains(&(self.data_vma as usize)) || !ram_range.contains(&(self.data_vma as usize + self.data_len - 1)) { + #[cfg(feature = "defmt")] + warn!("Program has invalid data section load addresses"); + return false; + } + } + + if self.bss_len > 0 { + if !ram_range.contains(&(self.bss_vma as usize)) || !ram_range.contains(&(self.data_vma as usize + self.data_len - 1)) { + #[cfg(feature = "defmt")] + warn!("Program has invalid bss section addresses"); + return false; + } + } + + true + } + + pub fn slot(&self) -> u8 { + (((&raw const *self as usize) - XIP_BASE as usize) / SLOT_SIZE) as u8 + } + pub fn check_crc(&self) -> bool { if self.len >= SLOT_SIZE || self.len < size_of::() { return false; @@ -70,4 +194,14 @@ impl ProgramSlotHeader { core::str::from_utf8(core::slice::from_raw_parts(self.version_ptr, self.version_len)) } } + + pub unsafe fn load(&self) { + if self.data_len > 0 { + core::ptr::copy_nonoverlapping(self.data_lma, self.data_vma, self.data_len); + } + + if self.bss_len > 0 { + self.bss_vma.write_bytes(0, self.bss_len); + } + } } \ No newline at end of file diff --git a/eepy-sys/src/input.rs b/eepy-sys/src/input.rs index 97eb5d3..71bf651 100644 --- a/eepy-sys/src/input.rs +++ b/eepy-sys/src/input.rs @@ -1,5 +1,6 @@ use core::fmt::{Display, Formatter}; -use crate::syscall; +use core::mem::MaybeUninit; +use crate::{syscall, SafeOption}; use crate::syscall::SyscallNumber; #[repr(usize)] @@ -76,53 +77,17 @@ impl Display for TouchEvent { } } -#[repr(usize)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ScEventType { - NoEvent = 0, - TouchUp = 1, - TouchDown = 2, - TouchMove = 3, - RefreshFinished = 4, -} - -impl TryFrom for ScEventType { - type Error = (); - - fn try_from(value: usize) -> Result { - match value { - x if x == ScEventType::NoEvent as usize => Ok(ScEventType::NoEvent), - x if x == ScEventType::TouchUp as usize => Ok(ScEventType::TouchUp), - x if x == ScEventType::TouchDown as usize => Ok(ScEventType::TouchDown), - x if x == ScEventType::TouchMove as usize => Ok(ScEventType::TouchMove), - x if x == ScEventType::RefreshFinished as usize => Ok(ScEventType::RefreshFinished), - _ => Err(()), - } - } -} - pub fn next_event() -> Option { - let mut ev_type: usize; - let mut x: u16; - let mut y: u16; + let mut event: MaybeUninit> = MaybeUninit::uninit(); unsafe { syscall!( SyscallNumber::Input, - out ev_type in InputSyscall::NextEvent, - out x, - out y, + in InputSyscall::NextEvent, + in event.as_mut_ptr(), ); - } - match ScEventType::try_from(ev_type) { - Ok(ScEventType::NoEvent) => None, - Ok(ScEventType::TouchUp) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Up, x, y })), - Ok(ScEventType::TouchDown) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Down, x, y })), - Ok(ScEventType::TouchMove) => Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Move, x, y })), - Ok(ScEventType::RefreshFinished) => Some(Event::RefreshFinished), - Err(_) => panic!("invalid touch event"), + event.assume_init().into() } } diff --git a/eepy-sys/src/lib.rs b/eepy-sys/src/lib.rs index 90b9586..0c6e7cf 100644 --- a/eepy-sys/src/lib.rs +++ b/eepy-sys/src/lib.rs @@ -6,5 +6,35 @@ pub mod misc; pub mod image; pub mod input; pub mod usb; +pub mod exec; -pub use tp370pgh01::IMAGE_BYTES; \ No newline at end of file +pub use tp370pgh01::IMAGE_BYTES; + +/// FFI-safe version of the standard `Option` type. +/// +/// Convert to/from a standard `Option` using `.into()`. +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SafeOption { + None, + Some(T), +} + +impl From> for SafeOption { + fn from(value: Option) -> Self { + match value { + None => SafeOption::None, + Some(v) => SafeOption::Some(v), + } + } +} + +impl From> for Option { + fn from(value: SafeOption) -> Self { + match value { + SafeOption::None => None, + SafeOption::Some(v) => Some(v), + } + } +} \ No newline at end of file diff --git a/eepy-sys/src/syscall.rs b/eepy-sys/src/syscall.rs index 18485e5..3594ac1 100644 --- a/eepy-sys/src/syscall.rs +++ b/eepy-sys/src/syscall.rs @@ -6,6 +6,7 @@ pub enum SyscallNumber { Image = 1, Input = 2, Usb = 3, + Exec = 4, } impl TryFrom for SyscallNumber { @@ -17,6 +18,7 @@ impl TryFrom for SyscallNumber { x if x == SyscallNumber::Image as u8 => Ok(SyscallNumber::Image), x if x == SyscallNumber::Input as u8 => Ok(SyscallNumber::Input), x if x == SyscallNumber::Usb as u8 => Ok(SyscallNumber::Usb), + x if x == SyscallNumber::Exec as u8 => Ok(SyscallNumber::Exec), _ => Err(()), } } diff --git a/eepy/src/launcher.rs b/eepy/src/launcher.rs new file mode 100644 index 0000000..d0864d1 --- /dev/null +++ b/eepy/src/launcher.rs @@ -0,0 +1,3 @@ +#[link_section = ".launcher"] +#[used] +static LAUNCHER: [u8; 0x20000] = *include_bytes!("../../eepy-launcher/out/eepy-launcher.s00.epb"); \ No newline at end of file diff --git a/eepy/src/main.rs b/eepy/src/main.rs index b941447..0985fd7 100644 --- a/eepy/src/main.rs +++ b/eepy/src/main.rs @@ -1,18 +1,19 @@ #![no_main] #![no_std] -mod programs; -mod gui; mod serial; mod ringbuffer; mod syscall; +mod launcher; extern crate panic_probe; extern crate defmt_rtt; +use core::arch::asm; use core::cell::RefCell; use critical_section::Mutex; use defmt::{debug, info, trace, warn}; +use embedded_hal::delay::DelayNs; use embedded_hal::digital::OutputPin; use embedded_hal::i2c::I2c; use mcp9808::MCP9808; @@ -25,6 +26,7 @@ use portable_atomic::Ordering; use usb_device::bus::UsbBusAllocator; use usb_device::prelude::*; use usbd_serial::SerialPort; +use eepy_sys::exec::exec; use fw16_epd_bsp::{entry, hal, pac, EpdBusy, EpdCs, EpdDc, EpdPowerSwitch, EpdReset, EpdSck, EpdSdaWrite, EpdTouchInt, EpdTouchReset, I2CScl, I2CSda, LaptopSleep, Pins}; use fw16_epd_bsp::hal::{Sio, Timer, I2C}; use fw16_epd_bsp::hal::clocks::ClockSource; @@ -34,14 +36,13 @@ use fw16_epd_bsp::hal::multicore::{Multicore, Stack}; use fw16_epd_bsp::hal::timer::{Alarm, Alarm0}; use fw16_epd_bsp::pac::I2C0; use fw16_epd_bsp::pac::interrupt; -use eepy_gui::draw_target::EpdDrawTarget; use eepy_sys::input::{Event, TouchEvent, TouchEventType}; use eepy_sys::misc::get_serial; use tp370pgh01::rp2040::{Rp2040PervasiveSpiDelays, IoPin}; use tp370pgh01::{Tp370pgh01, IMAGE_BYTES}; -use crate::programs::{slot, Programs}; use crate::ringbuffer::RingBuffer; -//use crate::programs::Programs; + +const SRAM_END: *mut u8 = 0x20042000 as *mut u8; static CORE1_STACK: Stack<8192> = Stack::new(); @@ -107,6 +108,55 @@ fn main() -> ! { core.SCB.set_sleepdeep(); + // Set MPU regions for non-privileged code + unsafe { + // Region 0: Unprivileged RAM region (first 128K) - start 0x20020000, length 128K + core.MPU.rnr.write(0); + core.MPU.rbar.write(0x20020000); + core.MPU.rasr.write( + (0b1 << 28) // XN + | (0b011 << 24) // AP: full access + | (0b000 << 19) // TEX + | (0b011 << 16) // S, C, B: non-shareable, writeback + | (0b00000000 << 8) // all subregions enabled + | (16 << 1) // size: 2^(16 + 1) = 128K + | (0b1 << 0) // enable + ); + + // Region 1: Unprivileged RAM region (final 8K) - start 0x20040000, length 8K + core.MPU.rnr.write(1); + core.MPU.rbar.write(0x20040000); + core.MPU.rasr.write( + (0b1 << 28) // XN + | (0b011 << 24) // AP: full access + | (0b000 << 19) // TEX + | (0b011 << 16) // S, C, B: non-shareable, writeback + | (0b00000000 << 8) // all subregions enabled + | (12 << 1) // size: 2^(12 + 1) = 8K + | (0b1 << 0) // enable + ); + + // Region 3: Flash - start: 0x10000000, length 16M + core.MPU.rnr.write(3); + core.MPU.rbar.write(0x10000000); + core.MPU.rasr.write( + (0b0 << 28) // disable XN + | (0b110 << 24) // AP: privileged or unprivileged read-only + | (0b000 << 19) // TEX + | (0b011 << 16) // S, C, B: non-shareable, writeback + | (0b00000000 << 8) // all subregions enabled + | (23 << 1) // size: 2^(23 + 1) = 16M + | (0b1 << 0) // enable + ); + + // Enable MPU + core.MPU.ctrl.write( + (0b1 << 2) // PRIVDEFENA: enable default memory map for privileged access + | (0b0 << 1) // HFNMIENA: disable MPU for HardFault and NMI + | (0b1 << 0) // ENABLE: enable MPU + ); + } + // Read flash unique ID cortex_m::interrupt::disable(); let mut id = [0u8; 8]; @@ -185,7 +235,7 @@ fn main() -> ! { let mut timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); // make sure temperature sensor has read temperature - //timer.delay_ms(35); + timer.delay_ms(35); let mut alarm = timer.alarm_0().unwrap(); alarm.enable_interrupt(); critical_section::with(|cs| GLOBAL_ALARM0.borrow_ref_mut(cs).replace(alarm)); @@ -281,16 +331,17 @@ fn main() -> ! { pac::NVIC::unmask(interrupt::SW0_IRQ); }; - // testing program loading unsafe { - let prog = slot(1); - debug!("{}", (*prog).name().unwrap()); - debug!("{}", (*prog).version().unwrap()); - ((*prog).entry)(); + asm!( + "msr psp, {sram_end}", + "msr control, {control_0b10}", + "isb", + sram_end = in(reg) SRAM_END, + control_0b10 = in(reg) 0b10, + ); + + exec(0); } - - let draw_target = EpdDrawTarget::new(); - gui::gui_main(draw_target); } #[interrupt] diff --git a/eepy/src/programs.rs b/eepy/src/programs.rs deleted file mode 100644 index 9ab8e1c..0000000 --- a/eepy/src/programs.rs +++ /dev/null @@ -1,50 +0,0 @@ -use eepy_sys::header::*; - -pub(crate) const unsafe fn slot(id: u8) -> *const ProgramSlotHeader { - if id < 1 || id > 31 { - panic!("slot ID must be between 1 and 31"); - } - - XIP_BASE.add(SLOT_SIZE * id as usize).cast::() -} - - -pub(crate) struct Programs { - id: u8, -} - -impl Programs { - pub(crate) fn new() -> Self { - Self { id: 0 } - } -} - -impl Iterator for Programs { - type Item = *const ProgramSlotHeader; - - fn next(&mut self) -> Option { - return None; - /* - loop { - self.id += 1; - - if self.id == 32 { - return None; - } - - let s = unsafe { slot(self.id) }; - - if s.is_valid_program() { - debug!("Found program {} version {} in slot {}", s.name().unwrap(), s.version_string().unwrap(), self.id); - return Some(s); - } else { - debug!("No program found in slot {}", self.id); - } - - }*/ - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(32 - self.id as usize)) - } -} \ No newline at end of file diff --git a/eepy/src/syscall.rs b/eepy/src/syscall.rs index 188ab1c..5e40010 100644 --- a/eepy/src/syscall.rs +++ b/eepy/src/syscall.rs @@ -1,9 +1,41 @@ -use core::arch::global_asm; -use defmt::trace; +use core::arch::{asm, global_asm}; +use defmt::{debug, trace, Formatter}; +use eepy_sys::exec::exec; +use eepy_sys::header::slot; use eepy_sys::syscall::SyscallNumber; +use crate::SRAM_END; global_asm!(include_str!("syscall.s")); +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct StackFrame { + r0: usize, + r1: usize, + r2: usize, + r3: usize, + r12: usize, + lr: *const u8, + pc: *const u8, + xpsr: usize, +} + +impl defmt::Format for StackFrame { + fn format(&self, fmt: Formatter) { + defmt::write!( + fmt, + "r0=0x{:x} r1=0x{:x} r2=0x{:x} r3=0x{:x} r12=0x{:x} lr={:x} pc={:x} xpsr=0x{:x}", + self.r0, + self.r1, + self.r2, + self.r3, + self.r12, + self.lr, + self.pc, + self.xpsr, + ) + } +} + /// Main syscall (SVC) handler. /// /// This function is called by the assembly trampoline code in `syscall.s`. @@ -15,47 +47,83 @@ global_asm!(include_str!("syscall.s")); /// The pc value from the stack can be used to extract the literal operand from the SVC instruction /// which triggered the syscall. #[no_mangle] -extern "C" fn handle_syscall(sp: *mut usize) { +extern "C" fn handle_syscall(sp: *mut StackFrame, using_psp: bool) { // Stack contains R0, R1, R2, R3, R12, LR, ReturnAddress, xPSR - let stack_values = unsafe { core::slice::from_raw_parts_mut(sp, 8) }; - let syscall_num = unsafe { (stack_values[6] as *const u8).sub(2).read() }; - - trace!( - "syscall: sp={} imm={:x} r0={:x} r1={:x} r2={:x} r3={:x} r12={:x} lr={:x} pc={:x} xpsr={:x}", - sp, - syscall_num, - stack_values[0], - stack_values[1], - stack_values[2], - stack_values[3], - stack_values[4], - stack_values[5], - stack_values[6], - stack_values[7], - ); + let stack_values = unsafe { &mut *sp }; + let syscall_num = unsafe { *stack_values.pc.sub(2) }; + + trace!("syscall: sp={} imm={:x} {}", sp, syscall_num, stack_values); match SyscallNumber::try_from(syscall_num) { Ok(SyscallNumber::Misc) => misc::handle_misc(stack_values), Ok(SyscallNumber::Image) => image::handle_image(stack_values), Ok(SyscallNumber::Input) => input::handle_input(stack_values), Ok(SyscallNumber::Usb) => todo!("usb syscalls"), - _ => panic!("illegal syscall"), + Ok(SyscallNumber::Exec) => handle_exec(stack_values, using_psp), + Err(_) => panic!("illegal syscall"), + } +} + +fn handle_exec(stack_values: &mut StackFrame, using_psp: bool) { + // disable privileged mode + unsafe { + asm!( + "msr control, {control_0b11}", + "isb", + control_0b11 = in(reg) 0b11, + ); + } + + let slot_n = stack_values.r0 as u8; + unsafe { + let program = slot(slot_n); + if !(*program).is_valid() { + panic!("tried to exec invalid program"); + } + + (*program).load(); + stack_values.pc = core::mem::transmute((*program).entry); + stack_values.lr = program_return_handler as *const u8; + + // Move the saved registers to the top of program memory + // This makes sure all programs start with an empty program stack + if using_psp { + let ptr: *mut StackFrame = SRAM_END.sub(size_of::()).cast(); + ptr.write(*stack_values); + asm!( + "msr psp, {ptr}", + ptr = in(reg) ptr, + ); + } else { + // If the saved registers are on the main stack, just set the PSP to + // the top of program memory + asm!( + "msr psp, {ptr}", + ptr = in(reg) SRAM_END, + ) + } } } +// NOTE: this function runs in unprivileged thread mode +extern "C" fn program_return_handler() { + exec(0); +} + mod misc { use eepy_sys::misc::MiscSyscall; use crate::SERIAL_NUMBER; + use super::StackFrame; - pub(super) fn handle_misc(stack_values: &mut [usize]) { - match MiscSyscall::try_from(stack_values[0]) { + pub(super) fn handle_misc(stack_values: &mut StackFrame) { + match MiscSyscall::try_from(stack_values.r0) { Ok(MiscSyscall::GetSerial) => handle_get_serial(stack_values), _ => panic!("illegal syscall"), } } - fn handle_get_serial(stack_values: &mut [usize]) { - stack_values[0] = (&raw const *SERIAL_NUMBER.get().unwrap()) as usize; + fn handle_get_serial(stack_values: &mut StackFrame) { + stack_values.r0 = (&raw const *SERIAL_NUMBER.get().unwrap()) as usize; } } @@ -64,23 +132,24 @@ mod image { use eepy_sys::image::{ImageSyscall, RefreshBlockMode}; use tp370pgh01::IMAGE_BYTES; use crate::{DO_REFRESH, FAST_REFRESH, IMAGE_BUFFER, REFRESHING}; + use super::StackFrame; - pub(super) fn handle_image(stack_values: &mut [usize]) { - match ImageSyscall::try_from(stack_values[0]) { + pub(super) fn handle_image(stack_values: &mut StackFrame) { + match ImageSyscall::try_from(stack_values.r0) { Ok(ImageSyscall::WriteImage) => handle_write_image(stack_values), Ok(ImageSyscall::Refresh) => handle_refresh(stack_values), _ => panic!("illegal syscall"), } } - fn handle_write_image(stack_values: &mut [usize]) { - let image: &[u8; IMAGE_BYTES] = unsafe { &*(stack_values[1] as *const [u8; IMAGE_BYTES]) }; + fn handle_write_image(stack_values: &mut StackFrame) { + let image: &[u8; IMAGE_BYTES] = unsafe { &*(stack_values.r1 as *const [u8; IMAGE_BYTES]) }; critical_section::with(|cs| IMAGE_BUFFER.borrow_ref_mut(cs).copy_from_slice(image)); } - fn handle_refresh(stack_values: &mut [usize]) { - let fast_refresh = stack_values[1] != 0; - let blocking_mode = RefreshBlockMode::try_from(stack_values[2]).expect("illegal refresh blocking mode"); + fn handle_refresh(stack_values: &mut StackFrame) { + let fast_refresh = stack_values.r1 != 0; + let blocking_mode = RefreshBlockMode::try_from(stack_values.r2).expect("illegal refresh blocking mode"); DO_REFRESH.store(true, Ordering::Relaxed); FAST_REFRESH.store(fast_refresh, Ordering::Relaxed); @@ -98,11 +167,13 @@ mod image { mod input { use core::sync::atomic::Ordering; - use eepy_sys::input::{Event, InputSyscall, ScEventType, TouchEvent, TouchEventType}; + use eepy_sys::input::{Event, InputSyscall}; + use eepy_sys::SafeOption; use crate::{EVENT_QUEUE, TOUCH_ENABLED}; + use super::StackFrame; - pub(super) fn handle_input(stack_values: &mut [usize]) { - match InputSyscall::try_from(stack_values[0]) { + pub(super) fn handle_input(stack_values: &mut StackFrame) { + match InputSyscall::try_from(stack_values.r0) { Ok(InputSyscall::NextEvent) => handle_next_event(stack_values), Ok(InputSyscall::SetTouchEnabled) => handle_set_touch_enabled(stack_values), Ok(InputSyscall::HasEvent) => handle_has_event(stack_values), @@ -110,44 +181,22 @@ mod input { } } - fn handle_next_event(stack_values: &mut [usize]) { + fn handle_next_event(stack_values: &mut StackFrame) { let next_event = critical_section::with(|cs| EVENT_QUEUE.borrow_ref_mut(cs).pop()); - let mut x = 0; - let mut y = 0; - let sc_ev_type = match next_event { - None => ScEventType::NoEvent, - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Down, x: px, y: py })) => { - x = px; - y = py; - ScEventType::TouchDown - }, - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Up, x: px, y: py })) => { - x = px; - y = py; - ScEventType::TouchUp - }, - Some(Event::Touch(TouchEvent { ev_type: TouchEventType::Move, x: px, y: py })) => { - x = px; - y = py; - ScEventType::TouchMove - }, - Some(Event::RefreshFinished) => ScEventType::RefreshFinished, - }; - stack_values[0] = sc_ev_type as usize; - stack_values[1] = x as usize; - stack_values[2] = y as usize; + let ptr = stack_values.r1 as *mut SafeOption; + unsafe { ptr.write(next_event.into()) }; } - fn handle_set_touch_enabled(stack_values: &mut [usize]) { - let enable = stack_values[1] != 0; + fn handle_set_touch_enabled(stack_values: &mut StackFrame) { + let enable = stack_values.r1 != 0; TOUCH_ENABLED.store(enable, Ordering::Relaxed); if !enable { critical_section::with(|cs| EVENT_QUEUE.borrow_ref_mut(cs).clear()); } } - fn handle_has_event(stack_values: &mut [usize]) { + fn handle_has_event(stack_values: &mut StackFrame) { let empty = critical_section::with(|cs| EVENT_QUEUE.borrow_ref(cs).is_empty()); - stack_values[0] = (!empty) as usize; + stack_values.r0 = (!empty) as usize; } } \ No newline at end of file diff --git a/eepy/src/syscall.s b/eepy/src/syscall.s index 8f671c5..3aa4d59 100644 --- a/eepy/src/syscall.s +++ b/eepy/src/syscall.s @@ -1,15 +1,16 @@ .global SVCall -.equ SVCall, _SVCall + 1 /* thumb bit */ -_SVCall: + .thumb_func +SVCall: + ldr r2, =handle_syscall movs r0, #4 mov r1, lr tst r0, r1 - beq .use_msp + beq 100f // use_msp mrs r0, psp - ldr r1, =handle_syscall - bx r1 -.use_msp: + movs r1, #1 + bx r2 +100: // use_msp mrs r0, msp - ldr r1, =handle_syscall - bx r1 \ No newline at end of file + movs r1, #0 + bx r2 \ No newline at end of file diff --git a/launcher.x b/launcher.x new file mode 100644 index 0000000..e2f7001 --- /dev/null +++ b/launcher.x @@ -0,0 +1,50 @@ +EXTERN(entry); +ENTRY(entry); + +MEMORY { + RAM : ORIGIN = 0x20020000, LENGTH = 136K + SLOT: ORIGIN = 0x10000000 + 128K, LENGTH = 128K +} + +SECTIONS { + .header ORIGIN(SLOT) : { + KEEP(*(.header)); + } > SLOT + + .text : ALIGN(4) { + . = ALIGN(4); + *(.text .text.*); + . = ALIGN(4); + } > SLOT + + .rodata : ALIGN(4) { + . = ALIGN(4); + *(.rodata .rodata.*); + . = ALIGN(4); + } > SLOT + + .data : ALIGN(4) { + . = ALIGN(4); + KEEP(*(.data .data.*)); + . = ALIGN(4); + } > RAM AT>SLOT + + .bss (NOLOAD) : ALIGN(4) { + . = ALIGN(4); + *(.bss .bss.*); + *(COMMON); + . = ALIGN(4); + } > RAM + + .uninit (NOLOAD) : ALIGN(4) { + . = ALIGN(4); + *(.uninit .uninit.*); + . = ALIGN(4); + } > RAM + + /DISCARD/ : { + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} \ No newline at end of file diff --git a/memory.x b/memory.x index ab3c8c8..25ea7ec 100644 --- a/memory.x +++ b/memory.x @@ -1,8 +1,9 @@ MEMORY { BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 - FLASH : ORIGIN = 0x10000100, LENGTH = 256K - 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 128K - 0x100 + LAUNCHER: ORIGIN = 0x10020000, LENGTH = 128K RESERVED: ORIGIN = 0x10040000, LENGTH = 256K - PROGRAM_SLOT_1: ORIGIN = 0x10080000, LENGTH = 512K + SLOTS: ORIGIN = 0x10080000, LENGTH = 16M - 512K RAM : ORIGIN = 0x20000000, LENGTH = 128K PROGRAM_RAM : ORIGIN = 0x20020000, LENGTH = 136K } @@ -18,7 +19,8 @@ SECTIONS { } INSERT BEFORE .text; SECTIONS { - .prog1 ORIGIN(PROGRAM_SLOT_1) : { - KEEP(*(.prog1)); - } > PROGRAM_SLOT_1 + .launcher ORIGIN(LAUNCHER) : + { + KEEP(*(.launcher)); + } > LAUNCHER } INSERT AFTER .text; \ No newline at end of file