diff --git a/eepy-example-app/src/main.rs b/eepy-example-app/src/main.rs index 8726a42..1b1c1a7 100644 --- a/eepy-example-app/src/main.rs +++ b/eepy-example-app/src/main.rs @@ -32,8 +32,11 @@ pub extern "C" fn entry() { let mut draw_target = EpdDrawTarget::new(); + let text = Text::new("Example App", Point::new(10, 20), DEFAULT_TEXT_STYLE); 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); + + text.draw(&mut draw_target).unwrap(); button.draw_init(&mut draw_target); exit_button.draw_init(&mut draw_target); draw_target.refresh(false, RefreshBlockMode::BlockAcknowledge); @@ -51,6 +54,7 @@ pub extern "C" fn entry() { let response = button.tick(&mut draw_target, ev); if response.clicked { draw_target.clear(BinaryColor::Off).unwrap(); + text.draw(&mut draw_target).unwrap(); button.draw_init(&mut draw_target); exit_button.draw_init(&mut draw_target); diff --git a/eepy-launcher/Cargo.toml b/eepy-launcher/Cargo.toml index 72262fc..454f8d2 100644 --- a/eepy-launcher/Cargo.toml +++ b/eepy-launcher/Cargo.toml @@ -6,7 +6,10 @@ edition = "2021" [dependencies] eepy-sys = { path = "../eepy-sys", features = ["critical-section-impl"] } eepy-gui = { path = "../eepy-gui" } +eepy-serial = { path = "../eepy-serial" } embedded-graphics.workspace = true usb-device.workspace = true usbd-serial.workspace = true +portable-atomic.workspace = true +heapless.workspace = true panic-halt = "1.0" \ No newline at end of file diff --git a/eepy-launcher/src/main.rs b/eepy-launcher/src/main.rs index 4c95854..aeaa670 100644 --- a/eepy-launcher/src/main.rs +++ b/eepy-launcher/src/main.rs @@ -1,9 +1,12 @@ #![no_std] #![no_main] +mod serial; + extern crate panic_halt; use core::arch::asm; +use core::sync::atomic::Ordering; use embedded_graphics::geometry::AnchorPoint; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::*; @@ -17,11 +20,12 @@ 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}; -use eepy_sys::misc::{get_serial, info, trace}; +use eepy_sys::misc::{get_serial}; use eepy_sys::usb; use eepy_sys::usb::UsbBus; use usb_device::prelude::*; use usbd_serial::SerialPort; +use crate::serial::NEEDS_REFRESH_PROGRAMS; #[link_section = ".header"] #[used] @@ -31,6 +35,7 @@ static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( entry, ); +#[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Page { MainPage, ScratchpadPage, @@ -42,8 +47,7 @@ struct MainPage { } impl MainPage { - fn new() -> Self { - let mut buttons = [const { None }; 32]; + fn refresh_buttons(&mut self) { let mut programs = Programs::new(); for y in 0..16 { @@ -58,15 +62,19 @@ impl MainPage { false, ); let slot_num = unsafe { (&*prog).slot() }; - buttons[bi] = Some((button, slot_num)) + self.app_buttons[bi] = Some((button, slot_num)) } } } + } - Self { + fn new() -> Self { + let mut res = Self { scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), - app_buttons: buttons, - } + app_buttons: [const { None }; 32], + }; + res.refresh_buttons(); + res } } @@ -97,7 +105,7 @@ impl Gui for MainPage { let response = button.tick(draw_target, ev); if response.clicked { - cleanup_usb(); + unsafe { cleanup_usb() }; exec(*s); } @@ -290,29 +298,7 @@ static mut USB: Option> = None; static mut USB_DEVICE: Option> = None; static mut USB_SERIAL: Option> = None; -#[allow(static_mut_refs)] -pub extern "C" fn testing_usb_handler() { - let dev: &mut UsbDevice = unsafe { USB_DEVICE.as_mut().unwrap() }; - let serial: &mut SerialPort = unsafe { USB_SERIAL.as_mut().unwrap() }; - - trace("Launcher USB handler"); - - if dev.poll(&mut [serial]) { - let mut buf = [0u8; 64]; - match serial.read(&mut buf) { - Err(_) => {}, - Ok(0) => {}, - Ok(_) => { - let s = core::str::from_utf8(&buf); - if let Ok(s) = s { - info(s); - } - } - } - } -} - -fn cleanup_usb() { +unsafe fn cleanup_usb() { #[allow(static_mut_refs)] unsafe { let _ = USB.take(); @@ -344,7 +330,7 @@ pub extern "C" fn entry() { USB_DEVICE = Some(usb_dev); } - usb::set_handler(testing_usb_handler); + usb::set_handler(serial::usb_handler); let mut draw_target = EpdDrawTarget::new(); set_touch_enabled(true); @@ -362,5 +348,13 @@ pub extern "C" fn entry() { // WFEs so we don't immediately wake up. unsafe { asm!("wfe", "wfe") }; } + + if NEEDS_REFRESH_PROGRAMS.swap(false, Ordering::Relaxed) { + gui.main_page.refresh_buttons(); + if gui.current_page == Page::MainPage { + gui.draw_init(&mut draw_target); + draw_target.refresh(false, RefreshBlockMode::BlockAcknowledge); + } + } } } \ No newline at end of file diff --git a/eepy/src/serial.rs b/eepy-launcher/src/serial.rs similarity index 54% rename from eepy/src/serial.rs rename to eepy-launcher/src/serial.rs index 8c79331..d0558da 100644 --- a/eepy/src/serial.rs +++ b/eepy-launcher/src/serial.rs @@ -1,14 +1,17 @@ use core::sync::atomic::Ordering; -use crate::{interrupt, FLASHING, FLASHING_ACK}; -use defmt::{debug, trace}; +use core::fmt::Write; +use portable_atomic::AtomicBool; +use usb_device::device::UsbDevice; use usbd_serial::SerialPort; -use fw16_epd_bsp::hal::usb::UsbBus; -use fw16_epd_bsp::pac; -use eepy_sys::header::ProgramSlotHeader; use eepy_serial::{Response, SerialCommand}; +use eepy_sys::flash::{erase_and_program, invalidate_cache}; use eepy_sys::image::{refresh, write_image, RefreshBlockMode}; -use tp370pgh01::IMAGE_BYTES; -#[derive(Copy, Clone, Debug, defmt::Format)] +use eepy_sys::IMAGE_BYTES; +use eepy_sys::misc::{debug, trace}; +use eepy_sys::usb::UsbBus; +use crate::{USB_DEVICE, USB_SERIAL}; + +#[derive(Copy, Clone, Debug)] enum SerialState { ReadyForCommand, @@ -25,66 +28,38 @@ enum SerialState { }, } +unsafe fn write_flash(buf: &[u8], slot: u8, page: usize) { + erase_and_program((slot as u32) * 512 * 1024 + (page as u32) * 4096, buf); +} + fn write_all(serial: &mut SerialPort, mut buf: &[u8]) { while !buf.is_empty() { let _ = serial.write(buf).map(|len| buf = &buf[len..]); } } -/// Safety: -/// -/// This function takes care of the main safety requirements of flashing, but the -/// caller must ensure that the `slot` and `page` parameters are valid and do -/// not produce an address outside the flash's range. Additionally, do not write -/// to slot 0 as this contains the firmware. -unsafe fn write_flash(buf: &[u8], slot: u8, page: usize) { - debug!("Begin write slot {} page {}", slot, page); - - // Make sure core1 is running code from RAM with interrupts disabled - FLASHING.store(true, Ordering::Relaxed); - cortex_m::asm::sev(); - // Wait until core1 has acknowledged that it is now in RAM code - while !FLASHING_ACK.load(Ordering::Relaxed) {} - // Disable interrupts on this core - cortex_m::interrupt::disable(); - - unsafe { - rp2040_flash::flash::flash_range_erase_and_program( - (slot as u32) * 512 * 1024 + (page as u32) * 4096, - buf, - true - ); - } - - // Enable interrupts - unsafe { cortex_m::interrupt::enable() } - // Wake up core1 - FLASHING.store(false, Ordering::Relaxed); - cortex_m::asm::sev(); +pub(crate) static NEEDS_REFRESH_PROGRAMS: AtomicBool = AtomicBool::new(false); - debug!("End write slot {} page {}", slot, page); -} +pub(crate) extern "C" fn usb_handler() { + trace("USB handler"); -/* -#[interrupt] -fn USBCTRL_IRQ() { static mut STATE: SerialState = SerialState::ReadyForCommand; + #[allow(static_mut_refs)] + let state = unsafe { &mut STATE }; + + #[allow(static_mut_refs)] + let dev: &mut UsbDevice = unsafe { USB_DEVICE.as_mut().unwrap() }; + #[allow(static_mut_refs)] + let serial: &mut SerialPort = unsafe { USB_SERIAL.as_mut().unwrap() }; // Receive buffer. Size equal to IMAGE_BYTES so it can store an entire frame; also used for // receiving flash applications. static mut BUF: [u8; IMAGE_BYTES] = [0; IMAGE_BYTES]; - - trace!("USBCTRL_IRQ"); - - // Safety: These are only accessed within this interrupt handler, or in main() before the - // interrupt is enabled. - #[allow(static_mut_refs)] - let usb_dev = unsafe { GLOBAL_USB_DEVICE.as_mut().unwrap() }; #[allow(static_mut_refs)] - let serial = unsafe { GLOBAL_USB_SERIAL.as_mut().unwrap() }; + let buf = unsafe { &mut BUF }; - if usb_dev.poll(&mut [serial]) { - match STATE { + if dev.poll(&mut [serial]) { + match state { SerialState::ReadyForCommand => { let mut cmd_buf = [0u8]; if let Ok(count) = serial.read(&mut cmd_buf) { @@ -93,9 +68,9 @@ fn USBCTRL_IRQ() { } match SerialCommand::try_from(cmd_buf[0]) { - Ok(SerialCommand::RefreshNormal) => *STATE = SerialState::ReceivingImage { fast_refresh: false, index: 0 }, - Ok(SerialCommand::RefreshFast) => *STATE = SerialState::ReceivingImage { fast_refresh: true, index: 0 }, - Ok(SerialCommand::UploadProgram) => *STATE = SerialState::FlashingProgram { index: 0, page: 0, num_pages: None, remainder: None }, + Ok(SerialCommand::RefreshNormal) => *state = SerialState::ReceivingImage { fast_refresh: false, index: 0 }, + Ok(SerialCommand::RefreshFast) => *state = SerialState::ReceivingImage { fast_refresh: true, index: 0 }, + Ok(SerialCommand::UploadProgram) => *state = SerialState::FlashingProgram { index: 0, page: 0, num_pages: None, remainder: None }, Ok(_) => write_all(serial, &[Response::UnknownCommand as u8]), Err(_) => write_all(serial, &[Response::UnknownCommand as u8]), } @@ -103,32 +78,30 @@ fn USBCTRL_IRQ() { } SerialState::ReceivingImage { fast_refresh, index } => { - if let Ok(count) = serial.read(&mut BUF[*index..]) { + if let Ok(count) = serial.read(&mut buf[*index..]) { *index += count; if *index == IMAGE_BYTES { - write_image(BUF); + write_image(buf); refresh(*fast_refresh, RefreshBlockMode::NonBlocking); write_all(serial, &[Response::Ack as u8]); - *STATE = SerialState::ReadyForCommand; + *state = SerialState::ReadyForCommand; } } } SerialState::FlashingProgram { index, page, num_pages, remainder } => { - debug!("Flashing program - page {}", *page); // Write page 0 last - this is the header, so we only want to write it once everything // else is written successfully // Keep page 0 in the first 4096 bytes of BUF for the end - debug!("{} {} {} {}", index, page, num_pages, remainder); if *page == 0 { - if let Ok(count) = serial.read(&mut BUF[*index..4096]) { + debug("receiving page 0"); + if let Ok(count) = serial.read(&mut buf[*index..4096]) { *index += count; if num_pages.is_none() && *index >= 12 { let mut b = [0u8; 4]; - b.copy_from_slice(&BUF[8..12]); + b.copy_from_slice(&buf[8..12]); let num_bytes = usize::from_le_bytes(b); - debug!("Program is {} bytes ({} pages) long", num_bytes, num_bytes.div_ceil(4096)); *num_pages = Some(num_bytes.div_ceil(4096)); *remainder = Some(num_bytes % 4096); } @@ -139,7 +112,11 @@ fn USBCTRL_IRQ() { } } } else { - if let Ok(count) = serial.read(&mut BUF[(4096 + *index)..8192]) { + if let Ok(count) = serial.read(&mut buf[(4096 + *index)..8192]) { + let mut message = heapless::String::<32>::new(); + write!(message, "receiving page {page}").unwrap(); + debug(&message); + *index += count; let num_pages = num_pages.unwrap(); @@ -150,27 +127,22 @@ fn USBCTRL_IRQ() { // Actually write the flash page // TODO: get next slot instead of always using slot 1 // TODO: wear levelling - unsafe { write_flash(&BUF[4096..8192], 1, *page) }; + unsafe { write_flash(&buf[4096..8192], 1, *page) }; *page += 1; // If this is the last page, also flash the first page which we didn't // do at the start if *page == num_pages { - unsafe { write_flash(&BUF[0..4096], 1, 0) }; + unsafe { write_flash(&buf[0..4096], 1, 0) }; // Invalidate the XIP cache, in case something from the flash area // we just wrote is in there - unsafe { - // FIXME: steal - let xip = pac::Peripherals::steal().XIP_CTRL; - xip.flush().write(|w| w.flush().set_bit()); - xip.flush().read(); - }; + unsafe { invalidate_cache() } - let program = unsafe { &*(0x10080000 as *const ProgramSlotHeader) }; - debug!("{} {}", program.name().unwrap(), program.version().unwrap()); + NEEDS_REFRESH_PROGRAMS.store(true, Ordering::Relaxed); + debug("Finished writing program"); - *STATE = SerialState::ReadyForCommand; + *state = SerialState::ReadyForCommand; } } } @@ -178,5 +150,4 @@ fn USBCTRL_IRQ() { } } } -} - */ \ No newline at end of file +} \ No newline at end of file diff --git a/eepy-sys/src/critical_section_impl.rs b/eepy-sys/src/critical_section_impl.rs index 939b230..a8a19c2 100644 --- a/eepy-sys/src/critical_section_impl.rs +++ b/eepy-sys/src/critical_section_impl.rs @@ -7,12 +7,12 @@ critical_section::set_impl!(EepyCs); unsafe impl critical_section::Impl for EepyCs { unsafe fn acquire() -> bool { - let mut state: bool; + let mut state: usize; syscall!( SyscallNumber::CriticalSection, out state in CsSyscall::Acquire, ); - state + state != 0 } unsafe fn release(state: bool) { diff --git a/eepy-sys/src/flash.rs b/eepy-sys/src/flash.rs new file mode 100644 index 0000000..e8373f8 --- /dev/null +++ b/eepy-sys/src/flash.rs @@ -0,0 +1,62 @@ +use crate::syscall; +use crate::syscall::SyscallNumber; + +#[repr(usize)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlashSyscall { + Erase = 0, + Program = 1, + EraseAndProgram = 2, + InvalidateCache = 3, +} + +impl TryFrom for FlashSyscall { + type Error = (); + + fn try_from(value: usize) -> Result { + match value { + x if x == FlashSyscall::Erase as usize => Ok(FlashSyscall::Erase), + x if x == FlashSyscall::Program as usize => Ok(FlashSyscall::Program), + x if x == FlashSyscall::EraseAndProgram as usize => Ok(FlashSyscall::EraseAndProgram), + x if x == FlashSyscall::InvalidateCache as usize => Ok(FlashSyscall::InvalidateCache), + _ => Err(()), + } + } +} + +pub unsafe fn erase(start_addr: u32, len: u32) { + syscall!( + SyscallNumber::Flash, + in FlashSyscall::Erase, + in start_addr, + in len, + ); +} + +pub unsafe fn program(start_addr: u32, data: &[u8]) { + syscall!( + SyscallNumber::Flash, + in FlashSyscall::Program, + in start_addr, + in data.len(), + in data.as_ptr(), + ); +} + +pub unsafe fn erase_and_program(start_addr: u32, data: &[u8]) { + syscall!( + SyscallNumber::Flash, + in FlashSyscall::EraseAndProgram, + in start_addr, + in data.len(), + in data.as_ptr(), + ); +} + +pub unsafe fn invalidate_cache() { + syscall!( + SyscallNumber::Flash, + in FlashSyscall::InvalidateCache, + ); +} \ No newline at end of file diff --git a/eepy-sys/src/lib.rs b/eepy-sys/src/lib.rs index a4221c0..783e183 100644 --- a/eepy-sys/src/lib.rs +++ b/eepy-sys/src/lib.rs @@ -8,6 +8,7 @@ pub mod input; pub mod usb; pub mod exec; pub mod critical_section; +pub mod flash; #[cfg(feature = "critical-section-impl")] mod critical_section_impl; diff --git a/eepy-sys/src/syscall.rs b/eepy-sys/src/syscall.rs index bfc421d..82d928c 100644 --- a/eepy-sys/src/syscall.rs +++ b/eepy-sys/src/syscall.rs @@ -8,6 +8,7 @@ pub enum SyscallNumber { Usb = 3, Exec = 4, CriticalSection = 5, + Flash = 6, } impl TryFrom for SyscallNumber { @@ -21,6 +22,7 @@ impl TryFrom for SyscallNumber { x if x == SyscallNumber::Usb as u8 => Ok(SyscallNumber::Usb), x if x == SyscallNumber::Exec as u8 => Ok(SyscallNumber::Exec), x if x == SyscallNumber::CriticalSection as u8 => Ok(SyscallNumber::CriticalSection), + x if x == SyscallNumber::Flash as u8 => Ok(SyscallNumber::Flash), _ => Err(()), } } diff --git a/eepy/src/syscall.rs b/eepy/src/syscall.rs index 51c2533..0207824 100644 --- a/eepy/src/syscall.rs +++ b/eepy/src/syscall.rs @@ -33,6 +33,7 @@ extern "C" fn handle_syscall(sp: *mut StackFrame, using_psp: bool) { Ok(SyscallNumber::Usb) => crate::usb::handle_usb(stack_values), Ok(SyscallNumber::Exec) => handle_exec(stack_values, using_psp), Ok(SyscallNumber::CriticalSection) => cs::handle_cs(stack_values), + Ok(SyscallNumber::Flash) => flash::handle_flash(stack_values), Err(_) => panic!("illegal syscall"), } } @@ -235,4 +236,114 @@ mod cs { } core::sync::atomic::compiler_fence(Ordering::SeqCst); } +} + +mod flash { + use core::sync::atomic::Ordering; + use eepy_sys::flash::FlashSyscall; + use eepy_sys::header::{SLOT_SIZE, XIP_BASE}; + use fw16_epd_bsp::pac; + use crate::exception::StackFrame; + use crate::{FLASHING, FLASHING_ACK}; + + pub(super) fn handle_flash(stack_values: &mut StackFrame) { + match FlashSyscall::try_from(stack_values.r0) { + Ok(FlashSyscall::Erase) => handle_erase(stack_values), + Ok(FlashSyscall::Program) => handle_program(stack_values), + Ok(FlashSyscall::EraseAndProgram) => handle_erase_and_program(stack_values), + Ok(FlashSyscall::InvalidateCache) => handle_invalidate_cache(), + Err(_) => panic!("illegal syscall"), + } + } + + fn assert_permissions(stack_values: &mut StackFrame, start: u32, len: u32) { + let slot_n = (stack_values.pc as usize - XIP_BASE as usize) / SLOT_SIZE; + let start_addr_xip = start as usize + XIP_BASE as usize; + let end_addr_xip = start_addr_xip + len as usize; + + if slot_n == 0 { + // Slot 0 (launcher) can write any flash except kernel + if start_addr_xip < (XIP_BASE as usize + 128 * 1024) { + panic!("illegal flash write"); + } + return; + } + + let slot_start = XIP_BASE as usize + (slot_n * SLOT_SIZE); + let slot_end = slot_start + SLOT_SIZE; + if start_addr_xip < slot_start || end_addr_xip > slot_end { + panic!("illegal flash write"); + } + } + + fn begin() { + // Make sure core1 is running code from RAM with interrupts disabled + FLASHING.store(true, Ordering::Relaxed); + cortex_m::asm::sev(); + // Wait until core1 has acknowledged that it is now in RAM code + while !FLASHING_ACK.load(Ordering::Relaxed) {} + // Disable interrupts on this core + cortex_m::interrupt::disable(); + } + + fn end() { + // Enable interrupts + unsafe { cortex_m::interrupt::enable() } + // Wake up core1 + FLASHING.store(false, Ordering::Relaxed); + cortex_m::asm::sev(); + } + + fn handle_erase(stack_values: &mut StackFrame) { + let start_addr = stack_values.r1 as u32; + let len = stack_values.r2 as u32; + assert_permissions(stack_values, start_addr, len); + if start_addr % 4096 != 0 || len % 4096 != 0 { + panic!("unaligned flash erase"); + } + + begin(); + unsafe { + rp2040_flash::flash::flash_range_erase(start_addr, len, true); + } + end(); + } + + fn handle_program(stack_values: &mut StackFrame) { + let start_addr = stack_values.r1 as u32; + let data = unsafe { core::slice::from_raw_parts(stack_values.r3 as *const u8, stack_values.r2) }; + assert_permissions(stack_values, start_addr, data.len() as u32); + if start_addr % 256 != 0 || data.len() % 256 != 0 { + panic!("unaligned flash program"); + } + + begin(); + unsafe { + rp2040_flash::flash::flash_range_program(start_addr, data, true); + } + end(); + } + + fn handle_erase_and_program(stack_values: &mut StackFrame) { + let start_addr = stack_values.r1 as u32; + let data = unsafe { core::slice::from_raw_parts(stack_values.r3 as *const u8, stack_values.r2) }; + assert_permissions(stack_values, start_addr, data.len() as u32); + if start_addr % 4096 != 0 || data.len() % 4096 != 0 { + panic!("unaligned flash erase"); + } + + begin(); + unsafe { + rp2040_flash::flash::flash_range_erase_and_program(start_addr, data, true); + } + end(); + } + + fn handle_invalidate_cache() { + unsafe { + let xip = pac::Peripherals::steal().XIP_CTRL; + xip.flush().write(|w| w.flush().set_bit()); + xip.flush().read(); + }; + } } \ No newline at end of file