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