From 0f4b4f8ffc4db90298ffcf616ebed3f8d4063dbd Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 19 Jul 2025 23:15:27 -0600 Subject: [PATCH 01/27] setup basic file structure --- Cargo.lock | 25 +++- Cargo.toml | 76 +--------- abi/Cargo.toml | 7 + abi/src/lib.rs | 9 ++ kernel/Cargo.toml | 83 +++++++++++ build.rs => kernel/build.rs | 2 +- {src => kernel/src}/display.rs | 0 {src => kernel/src}/main.rs | 17 ++- kernel/src/peripherals/keyboard.rs | 59 ++++++++ {src => kernel/src}/peripherals/mod.rs | 0 shared/Cargo.toml | 11 ++ shared/src/lib.rs | 142 ++++++++++++++++++ src/peripherals/keyboard.rs | 197 ------------------------- user-apps/calculator/Cargo.toml | 6 + user-apps/calculator/src/main.rs | 3 + 15 files changed, 362 insertions(+), 275 deletions(-) create mode 100644 abi/Cargo.toml create mode 100644 abi/src/lib.rs create mode 100644 kernel/Cargo.toml rename build.rs => kernel/build.rs (96%) rename {src => kernel/src}/display.rs (100%) rename {src => kernel/src}/main.rs (87%) create mode 100644 kernel/src/peripherals/keyboard.rs rename {src => kernel/src}/peripherals/mod.rs (100%) create mode 100644 shared/Cargo.toml create mode 100644 shared/src/lib.rs delete mode 100644 src/peripherals/keyboard.rs create mode 100644 user-apps/calculator/Cargo.toml create mode 100644 user-apps/calculator/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 13db5ac..dc4cf18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,13 @@ dependencies = [ "regex", ] +[[package]] +name = "abi" +version = "0.1.0" +dependencies = [ + "shared", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -192,6 +199,10 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "calculator" +version = "0.1.0" + [[package]] name = "cfg-if" version = "1.0.1" @@ -1159,9 +1170,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags 2.9.1", "libc", @@ -1356,6 +1367,7 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" name = "picocalc-os-rs" version = "0.1.0" dependencies = [ + "abi", "bitflags 2.9.1", "bt-hci", "cortex-m", @@ -1378,6 +1390,7 @@ dependencies = [ "heapless", "panic-probe", "portable-atomic", + "shared", "spin", "st7365p-lcd", "static_cell", @@ -1726,6 +1739,14 @@ dependencies = [ "keccak", ] +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.1", + "defmt 1.0.1", +] + [[package]] name = "siphasher" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 6477884..e58fa93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ -[package] -name = "picocalc-os-rs" -version = "0.1.0" -edition = "2024" +[workspace] +resolver = "3" +members = ["kernel", "abi", "shared", "user-apps/calculator"] [profile.release] debug = 2 @@ -9,72 +8,3 @@ debug = 2 [profile.dev] lto = true opt-level = "z" - -[features] -default = ["rp235x", "defmt"] -rp2040 = ["embassy-rp/rp2040"] -rp235x = ["embassy-rp/rp235xb"] -trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] -defmt = [ - "dep:defmt", - "panic-probe/print-defmt", - "embassy-executor/defmt", - "embassy-time/defmt", - "embassy-time/defmt-timestamp-uptime", - "embassy-rp/defmt", - "embassy-sync/defmt", - "embedded-graphics/defmt", - "embedded-sdmmc/defmt-log", - # "bt-hci/defmt", - # "cyw43/defmt", - # "cyw43-pio/defmt", -] - -[dependencies] -embassy-executor = { version = "0.7", features = [ - "arch-cortex-m", - "executor-interrupt", - "executor-thread", - "nightly", -] } -embassy-rp = { version = "0.4.0", features = [ - "critical-section-impl", - "unstable-pac", - "time-driver", - "binary-info", -] } -embassy-futures = "0.1.1" -embassy-time = "0.4.0" -embassy-embedded-hal = "0.3.0" -embassy-sync = { version = "0.7" } -trouble-host = { version = "0.1", features = [ - "derive", - "scan", -], optional = true } -bt-hci = { version = "0.2", default-features = false, optional = true } -cyw43 = { version = "0.3.0", features = [ - "firmware-logs", - "bluetooth", -], optional = true } -cyw43-pio = { version = "0.3.0", optional = true } - -embedded-hal-bus = { version = "0.3.0", features = ["async"] } -embedded-hal = "0.2.7" -embedded-hal-async = "1.0.0" -cortex-m = { version = "0.7.7" } -cortex-m-rt = "0.7.5" -panic-probe = "0.3" -portable-atomic = { version = "1.11", features = ["critical-section"] } - -defmt = { version = "0.3", optional = true } -defmt-rtt = "0.4.2" - -embedded-graphics = { version = "0.8.1" } -embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } -st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } - -static_cell = "2.1.1" -bitflags = "2.9.1" -talc = "4.4.3" -spin = "0.10.0" -heapless = "0.8.0" diff --git a/abi/Cargo.toml b/abi/Cargo.toml new file mode 100644 index 0000000..80cc90d --- /dev/null +++ b/abi/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "abi" +version = "0.1.0" +edition = "2024" + +[dependencies] +shared = { path = "../shared" } diff --git a/abi/src/lib.rs b/abi/src/lib.rs new file mode 100644 index 0000000..0411192 --- /dev/null +++ b/abi/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +use core::ffi::c_void; +use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; + +#[repr(C)] +pub enum Syscall { + DrawPixels { x: u32, y: u32, color: u32 }, +} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..61fe107 --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "picocalc-os-rs" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "picocalc-os-rs" +path = "src/main.rs" +test = false +doctest = false +bench = false + +[features] +default = ["rp235x", "defmt"] +rp2040 = ["embassy-rp/rp2040"] +rp235x = ["embassy-rp/rp235xb"] +trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] +defmt = [ + "dep:defmt", + "panic-probe/print-defmt", + "embassy-executor/defmt", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", + "embassy-rp/defmt", + "embassy-sync/defmt", + "embedded-graphics/defmt", + "embedded-sdmmc/defmt-log", + # "bt-hci/defmt", + # "cyw43/defmt", + # "cyw43-pio/defmt", +] + +[dependencies] +embassy-executor = { version = "0.7", features = [ + "arch-cortex-m", + "executor-interrupt", + "executor-thread", + "nightly", +] } +embassy-rp = { version = "0.4.0", features = [ + "critical-section-impl", + "unstable-pac", + "time-driver", + "binary-info", +] } +embassy-futures = "0.1.1" +embassy-time = "0.4.0" +embassy-embedded-hal = "0.3.0" +embassy-sync = { version = "0.7" } +trouble-host = { version = "0.1", features = [ + "derive", + "scan", +], optional = true } +bt-hci = { version = "0.2", default-features = false, optional = true } +cyw43 = { version = "0.3.0", features = [ + "firmware-logs", + "bluetooth", +], optional = true } +cyw43-pio = { version = "0.3.0", optional = true } + +embedded-hal-bus = { version = "0.3.0", features = ["async"] } +embedded-hal = "0.2.7" +embedded-hal-async = "1.0.0" +cortex-m = { version = "0.7.7" } +cortex-m-rt = "0.7.5" +panic-probe = "0.3" +portable-atomic = { version = "1.11", features = ["critical-section"] } + +defmt = { version = "0.3", optional = true } +defmt-rtt = "0.4.2" + +embedded-graphics = { version = "0.8.1" } +embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } + +shared = { path = "../shared" } +abi = { path = "../abi" } + +static_cell = "2.1.1" +bitflags = "2.9.1" +talc = "4.4.3" +spin = "0.10.0" +heapless = "0.8.0" diff --git a/build.rs b/kernel/build.rs similarity index 96% rename from build.rs rename to kernel/build.rs index 30691aa..e0ded4a 100644 --- a/build.rs +++ b/kernel/build.rs @@ -19,7 +19,7 @@ fn main() { let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); File::create(out.join("memory.x")) .unwrap() - .write_all(include_bytes!("memory.x")) + .write_all(include_bytes!("../memory.x")) .unwrap(); println!("cargo:rustc-link-search={}", out.display()); diff --git a/src/display.rs b/kernel/src/display.rs similarity index 100% rename from src/display.rs rename to kernel/src/display.rs diff --git a/src/main.rs b/kernel/src/main.rs similarity index 87% rename from src/main.rs rename to kernel/src/main.rs index e754b93..80184e9 100644 --- a/src/main.rs +++ b/kernel/src/main.rs @@ -1,7 +1,7 @@ #![feature(impl_trait_in_assoc_type)] #![feature(ascii_char)] -#![no_std] -#![no_main] +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] use crate::{ display::DISPLAY_SIGNAL, @@ -80,3 +80,16 @@ async fn main(_spawner: Spawner) { ) .await; } + +use abi::Syscall; + +#[no_mangle] +pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { + let call = unsafe { &*call }; + match call { + Syscall::DrawPixel { x, y, color } => { + draw_pixel(*x, *y, *color); + 0 + } + } +} diff --git a/kernel/src/peripherals/keyboard.rs b/kernel/src/peripherals/keyboard.rs new file mode 100644 index 0000000..a9ef605 --- /dev/null +++ b/kernel/src/peripherals/keyboard.rs @@ -0,0 +1,59 @@ +use crate::peripherals::PERIPHERAL_BUS; +pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; + +const REG_ID_KEY: u8 = 0x04; +const REG_ID_FIF: u8 = 0x09; + +const KEY_CAPSLOCK: u8 = 1 << 5; +const KEY_NUMLOCK: u8 = 1 << 6; +const KEY_COUNT_MASK: u8 = 0x1F; // 0x1F == 31 + +pub async fn read_keyboard_fifo() -> Option { + let mut i2c = PERIPHERAL_BUS.get().lock().await; + let i2c = i2c.as_mut().unwrap(); + + let mut key_status = [0_u8; 1]; + + if i2c + .write_read_async(super::MCU_ADDR, [REG_ID_KEY], &mut key_status) + .await + .is_ok() + { + let _caps = key_status[0] & KEY_CAPSLOCK == KEY_CAPSLOCK; + let _num = key_status[0] & KEY_NUMLOCK == KEY_NUMLOCK; + let fifo_count = key_status[0] & KEY_COUNT_MASK; + + if fifo_count >= 1 { + let mut event = [0_u8; 2]; + + if i2c + .write_read_async(super::MCU_ADDR, [REG_ID_FIF], &mut event) + .await + .is_ok() + { + return Some(KeyEvent { + state: KeyState::from(event[0]), + key: KeyCode::from(event[1]), + mods: Modifiers::NONE, + }); + } + } + } + None +} + +const REG_ID_DEB: u8 = 0x06; +const REG_ID_FRQ: u8 = 0x07; + +pub async fn configure_keyboard(debounce: u8, poll_freq: u8) { + let mut i2c = PERIPHERAL_BUS.get().lock().await; + let i2c = i2c.as_mut().unwrap(); + + let _ = i2c + .write_read_async(super::MCU_ADDR, [REG_ID_DEB], &mut [debounce]) + .await; + + let _ = i2c + .write_read_async(super::MCU_ADDR, [REG_ID_FRQ], &mut [poll_freq]) + .await; +} diff --git a/src/peripherals/mod.rs b/kernel/src/peripherals/mod.rs similarity index 100% rename from src/peripherals/mod.rs rename to kernel/src/peripherals/mod.rs diff --git a/shared/Cargo.toml b/shared/Cargo.toml new file mode 100644 index 0000000..4ceba1e --- /dev/null +++ b/shared/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "shared" +version = "0.1.0" +edition = "2024" + +[features] +defmt = ["dep:defmt"] + +[dependencies] +bitflags = "2.9.1" +defmt = { version = "1.0.1", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs new file mode 100644 index 0000000..3823869 --- /dev/null +++ b/shared/src/lib.rs @@ -0,0 +1,142 @@ +#![no_std] + +pub mod keyboard { + bitflags::bitflags! { + #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + pub struct Modifiers: u8 { + const NONE = 0; + const CTRL = 1; + const ALT = 2; + const LSHIFT = 4; + const RSHIFT = 8; + const SYM = 16; + } + } + + #[derive(Debug)] + pub struct KeyEvent { + pub key: KeyCode, + pub state: KeyState, + pub mods: Modifiers, + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum KeyState { + Idle = 0, + Pressed = 1, + Hold = 2, + Released = 3, + } + + impl From for KeyState { + fn from(value: u8) -> Self { + match value { + 1 => KeyState::Pressed, + 2 => KeyState::Hold, + 3 => KeyState::Released, + 0 | _ => KeyState::Idle, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(C)] + #[repr(u8)] + pub enum KeyCode { + JoyUp = 0x01, + JoyDown = 0x02, + JoyLeft = 0x03, + JoyRight = 0x04, + JoyCenter = 0x05, + BtnLeft1 = 0x06, + BtnRight1 = 0x07, + BtnLeft2 = 0x11, + BtnRight2 = 0x12, + Backspace = 0x08, + Tab = 0x09, + Enter = 0x0A, + ModAlt = 0xA1, + ModShiftLeft = 0xA2, + ModShiftRight = 0xA3, + ModSym = 0xA4, + ModCtrl = 0xA5, + Esc = 0xB1, + Left = 0xB4, + Up = 0xB5, + Down = 0xB6, + Right = 0xB7, + Break = 0xD0, + Insert = 0xD1, + Home = 0xD2, + Del = 0xD4, + End = 0xD5, + PageUp = 0xD6, + PageDown = 0xD7, + CapsLock = 0xC1, + F1 = 0x81, + F2 = 0x82, + F3 = 0x83, + F4 = 0x84, + F5 = 0x85, + F6 = 0x86, + F7 = 0x87, + F8 = 0x88, + F9 = 0x89, + F10 = 0x90, + Char(char), + Unknown(u8), + } + + impl From for KeyCode { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::JoyUp, + 0x02 => Self::JoyDown, + 0x03 => Self::JoyLeft, + 0x04 => Self::JoyRight, + 0x05 => Self::JoyCenter, + 0x06 => Self::BtnLeft1, + 0x07 => Self::BtnRight1, + 0x08 => Self::Backspace, + 0x09 => Self::Tab, + 0x0A => Self::Enter, + 0x11 => Self::BtnLeft2, + 0x12 => Self::BtnRight2, + 0xA1 => Self::ModAlt, + 0xA2 => Self::ModShiftLeft, + 0xA3 => Self::ModShiftRight, + 0xA4 => Self::ModSym, + 0xA5 => Self::ModCtrl, + 0xB1 => Self::Esc, + 0xB4 => Self::Left, + 0xB5 => Self::Up, + 0xB6 => Self::Down, + 0xB7 => Self::Right, + 0xC1 => Self::CapsLock, + 0xD0 => Self::Break, + 0xD1 => Self::Insert, + 0xD2 => Self::Home, + 0xD4 => Self::Del, + 0xD5 => Self::End, + 0xD6 => Self::PageUp, + 0xD7 => Self::PageDown, + 0x81 => Self::F1, + 0x82 => Self::F2, + 0x83 => Self::F3, + 0x84 => Self::F4, + 0x85 => Self::F5, + 0x86 => Self::F6, + 0x87 => Self::F7, + 0x88 => Self::F8, + 0x89 => Self::F9, + 0x90 => Self::F10, + _ => match char::from_u32(value as u32) { + Some(c) => Self::Char(c), + None => Self::Unknown(value), + }, + } + } + } +} diff --git a/src/peripherals/keyboard.rs b/src/peripherals/keyboard.rs deleted file mode 100644 index 208a335..0000000 --- a/src/peripherals/keyboard.rs +++ /dev/null @@ -1,197 +0,0 @@ - -use crate::peripherals::PERIPHERAL_BUS; - -const REG_ID_KEY: u8 = 0x04; -const REG_ID_FIF: u8 = 0x09; - -const KEY_CAPSLOCK: u8 = 1 << 5; -const KEY_NUMLOCK: u8 = 1 << 6; -const KEY_COUNT_MASK: u8 = 0x1F; // 0x1F == 31 - -pub async fn read_keyboard_fifo() -> Option { - let mut i2c = PERIPHERAL_BUS.get().lock().await; - let i2c = i2c.as_mut().unwrap(); - - let mut key_status = [0_u8; 1]; - - if i2c - .write_read_async(super::MCU_ADDR, [REG_ID_KEY], &mut key_status) - .await - .is_ok() - { - let _caps = key_status[0] & KEY_CAPSLOCK == KEY_CAPSLOCK; - let _num = key_status[0] & KEY_NUMLOCK == KEY_NUMLOCK; - let fifo_count = key_status[0] & KEY_COUNT_MASK; - - if fifo_count >= 1 { - let mut event = [0_u8; 2]; - - if i2c - .write_read_async(super::MCU_ADDR, [REG_ID_FIF], &mut event) - .await - .is_ok() - { - return Some(KeyEvent { - state: KeyState::from(event[0]), - key: KeyCode::from(event[1]), - mods: Modifiers::NONE, - }); - } - } - } - None -} - -const REG_ID_DEB: u8 = 0x06; -const REG_ID_FRQ: u8 = 0x07; - -pub async fn configure_keyboard(debounce: u8, poll_freq: u8) { - let mut i2c = PERIPHERAL_BUS.get().lock().await; - let i2c = i2c.as_mut().unwrap(); - - let _ = i2c - .write_read_async(super::MCU_ADDR, [REG_ID_DEB], &mut [debounce]) - .await; - - let _ = i2c - .write_read_async(super::MCU_ADDR, [REG_ID_FRQ], &mut [poll_freq]) - .await; -} - -bitflags::bitflags! { - #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] - pub struct Modifiers: u8 { - const NONE = 0; - const CTRL = 1; - const ALT = 2; - const LSHIFT = 4; - const RSHIFT = 8; - const SYM = 16; - } -} - -#[derive(Debug)] -pub struct KeyEvent { - pub key: KeyCode, - pub state: KeyState, - pub mods: Modifiers, -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum KeyState { - Idle = 0, - Pressed = 1, - Hold = 2, - Released = 3, -} - -impl From for KeyState { - fn from(value: u8) -> Self { - match value { - 1 => KeyState::Pressed, - 2 => KeyState::Hold, - 3 => KeyState::Released, - 0 | _ => KeyState::Idle, - } - } -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum KeyCode { - JoyUp = 0x01, - JoyDown = 0x02, - JoyLeft = 0x03, - JoyRight = 0x04, - JoyCenter = 0x05, - BtnLeft1 = 0x06, - BtnRight1 = 0x07, - BtnLeft2 = 0x11, - BtnRight2 = 0x12, - Backspace = 0x08, - Tab = 0x09, - Enter = 0x0A, - ModAlt = 0xA1, - ModShiftLeft = 0xA2, - ModShiftRight = 0xA3, - ModSym = 0xA4, - ModCtrl = 0xA5, - Esc = 0xB1, - Left = 0xB4, - Up = 0xB5, - Down = 0xB6, - Right = 0xB7, - Break = 0xD0, - Insert = 0xD1, - Home = 0xD2, - Del = 0xD4, - End = 0xD5, - PageUp = 0xD6, - PageDown = 0xD7, - CapsLock = 0xC1, - F1 = 0x81, - F2 = 0x82, - F3 = 0x83, - F4 = 0x84, - F5 = 0x85, - F6 = 0x86, - F7 = 0x87, - F8 = 0x88, - F9 = 0x89, - F10 = 0x90, - Char(char), - Unknown(u8), -} - -impl From for KeyCode { - fn from(value: u8) -> Self { - match value { - 0x01 => Self::JoyUp, - 0x02 => Self::JoyDown, - 0x03 => Self::JoyLeft, - 0x04 => Self::JoyRight, - 0x05 => Self::JoyCenter, - 0x06 => Self::BtnLeft1, - 0x07 => Self::BtnRight1, - 0x08 => Self::Backspace, - 0x09 => Self::Tab, - 0x0A => Self::Enter, - 0x11 => Self::BtnLeft2, - 0x12 => Self::BtnRight2, - 0xA1 => Self::ModAlt, - 0xA2 => Self::ModShiftLeft, - 0xA3 => Self::ModShiftRight, - 0xA4 => Self::ModSym, - 0xA5 => Self::ModCtrl, - 0xB1 => Self::Esc, - 0xB4 => Self::Left, - 0xB5 => Self::Up, - 0xB6 => Self::Down, - 0xB7 => Self::Right, - 0xC1 => Self::CapsLock, - 0xD0 => Self::Break, - 0xD1 => Self::Insert, - 0xD2 => Self::Home, - 0xD4 => Self::Del, - 0xD5 => Self::End, - 0xD6 => Self::PageUp, - 0xD7 => Self::PageDown, - 0x81 => Self::F1, - 0x82 => Self::F2, - 0x83 => Self::F3, - 0x84 => Self::F4, - 0x85 => Self::F5, - 0x86 => Self::F6, - 0x87 => Self::F7, - 0x88 => Self::F8, - 0x89 => Self::F9, - 0x90 => Self::F10, - _ => match char::from_u32(value as u32) { - Some(c) => Self::Char(c), - None => Self::Unknown(value), - }, - } - } -} diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml new file mode 100644 index 0000000..ed5cba5 --- /dev/null +++ b/user-apps/calculator/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "calculator" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/user-apps/calculator/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From a53929f511c283ce2792e8bedf009c96e3ad8d92 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 30 Jul 2025 15:21:44 -0600 Subject: [PATCH 02/27] Merge usb-mass-storage branch --- Cargo.lock | 156 ++++++++++++++--- kernel/Cargo.toml | 8 +- kernel/src/display.rs | 21 +-- kernel/src/main.rs | 80 +++++---- kernel/src/scsi/mod.rs | 308 ++++++++++++++++++++++++++++++++++ kernel/src/scsi/scsi_types.rs | 164 ++++++++++++++++++ kernel/src/storage.rs | 100 +++++++++++ kernel/src/usb.rs | 36 ++++ kernel/src/utils.rs | 11 ++ 9 files changed, 801 insertions(+), 83 deletions(-) create mode 100644 kernel/src/scsi/mod.rs create mode 100644 kernel/src/scsi/scsi_types.rs create mode 100644 kernel/src/storage.rs create mode 100644 kernel/src/usb.rs create mode 100644 kernel/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index dc4cf18..0643f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,18 @@ dependencies = [ "shared", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -82,21 +94,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "bisync" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7" -dependencies = [ - "bisync_macros", -] - -[[package]] -name = "bisync_macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6" - [[package]] name = "bit-set" version = "0.5.3" @@ -133,6 +130,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -226,7 +229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield", + "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", ] @@ -672,6 +675,7 @@ dependencies = [ "defmt 0.3.100", "document-features", "embassy-time-driver", + "embassy-time-queue-utils", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -697,6 +701,21 @@ dependencies = [ "heapless", ] +[[package]] +name = "embassy-usb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + [[package]] name = "embassy-usb-driver" version = "0.1.1" @@ -795,17 +814,14 @@ dependencies = [ [[package]] name = "embedded-sdmmc" -version = "0.8.0" -source = "git+https://github.com/Be-ing/embedded-sdmmc-rs?branch=bisync#835b2e4f9d3482b6287f674d7ecf6ae5d0618c18" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3c7f9ea039eeafc4a49597b7bd5ae3a1c8e51b2803a381cb0f29ce90fe1ec6" dependencies = [ - "bisync", "byteorder", "defmt 0.3.100", - "embassy-futures", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", - "embedded-io-async", "heapless", ] @@ -833,6 +849,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.2" @@ -1007,6 +1029,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -1042,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -1382,12 +1413,14 @@ dependencies = [ "embassy-rp 0.4.0", "embassy-sync 0.7.0", "embassy-time", + "embassy-usb", "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal-async", "embedded-hal-bus", "embedded-sdmmc", "heapless", + "num_enum 0.7.4", "panic-probe", "portable-atomic", "shared", @@ -1723,6 +1756,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -1786,6 +1839,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "st7365p-lcd" version = "0.11.0" @@ -2004,6 +2067,53 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "uuid" version = "1.17.0" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 61fe107..d74fd95 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -43,9 +43,10 @@ embassy-rp = { version = "0.4.0", features = [ "time-driver", "binary-info", ] } +embassy-usb = "0.4.0" embassy-futures = "0.1.1" -embassy-time = "0.4.0" -embassy-embedded-hal = "0.3.0" +embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } +embassy-embedded-hal = "0.3.1" embassy-sync = { version = "0.7" } trouble-host = { version = "0.1", features = [ "derive", @@ -70,7 +71,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" embedded-graphics = { version = "0.8.1" } -embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } +embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } shared = { path = "../shared" } @@ -81,3 +82,4 @@ bitflags = "2.9.1" talc = "4.4.3" spin = "0.10.0" heapless = "0.8.0" +num_enum = { version = "0.7.4", default-features = false } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 7cf9ab4..6766b8a 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -21,8 +21,6 @@ use embedded_hal_bus::spi::ExclusiveDevice; use portable_atomic::AtomicBool; use st7365p_lcd::{FrameBuffer, ST7365P}; -use crate::LAST_TEXT_RECT; - const SCREEN_WIDTH: usize = 320; const SCREEN_HEIGHT: usize = 320; @@ -58,24 +56,7 @@ pub async fn display_handler( loop { DISPLAY_SIGNAL.wait().await; - let text_string = crate::STRING.lock().await.clone(); - - let text = Text::with_alignment( - &text_string, - Point::new(160, 160), - MonoTextStyle::new(&FONT_10X20, Rgb565::RED), - Alignment::Center, - ); - - { - let rect = LAST_TEXT_RECT.lock().await; - if let Some(rect) = *rect.borrow() { - framebuffer.fill_solid(&rect, Rgb565::BLACK).unwrap(); - } - *rect.borrow_mut() = Some(text.bounding_box()); - } - - text.draw(&mut framebuffer).unwrap(); + // text.draw(&mut framebuffer).unwrap(); let start = Instant::now(); framebuffer diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 80184e9..cc25387 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -6,6 +6,8 @@ use crate::{ display::DISPLAY_SIGNAL, peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + storage::SdCard, + usb::usb_handler, }; use {defmt_rtt as _, panic_probe as _}; @@ -13,72 +15,76 @@ use {defmt_rtt as _, panic_probe as _}; use core::cell::RefCell; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_rp::peripherals::I2C1; +use embassy_rp::{ + gpio::{Input, Level, Output, Pull}, + peripherals::{I2C1, USB}, + spi::Spi, + usb as embassy_rp_usb, +}; use embassy_rp::{i2c, i2c::I2c, spi}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; -use embassy_time::Timer; +use embassy_time::{Delay, Timer}; use embedded_graphics::primitives::Rectangle; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::SdCard as SdmmcSdCard; use heapless::String; mod peripherals; use peripherals::conf_peripherals; mod display; use display::display_handler; +mod scsi; +mod storage; +mod usb; +mod utils; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; + USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); -static STRING: Mutex> = Mutex::new(String::new()); -static LAST_TEXT_RECT: Mutex>> = - Mutex::new(RefCell::new(None)); - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - STRING.lock().await.push_str("Press Del").unwrap(); - - // configure keyboard event handler + // MCU i2c bus for peripherals let mut config = i2c::Config::default(); config.frequency = 400_000; let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); conf_peripherals(i2c1).await; + // SPI1 bus display let mut config = spi::Config::default(); config.frequency = 16_000_000; let spi1 = spi::Spi::new( p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, ); - join( - async { - loop { - Timer::after_millis(20).await; - if let Some(key) = read_keyboard_fifo().await - && key.state == KeyState::Pressed - { - let mut string = STRING.lock().await; - match key.key { - KeyCode::Backspace => { - string.pop().unwrap(); - } - KeyCode::Del => { - string.clear(); - } - KeyCode::Char(c) => { - string.push(c).unwrap(); - } - _ => (), - } - DISPLAY_SIGNAL.signal(()); - } - } - }, - display_handler(spi1, p.PIN_13, p.PIN_14, p.PIN_15), - ) - .await; + let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + + let sdcard = { + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new_blocking( + p.SPI0, + p.PIN_18, // clk + p.PIN_19, // mosi + p.PIN_16, // miso + config.clone(), + ); + let cs = Output::new(p.PIN_17, Level::High); + let det = Input::new(p.PIN_22, Pull::None); + + let device = ExclusiveDevice::new(spi, cs, Delay).unwrap(); + let sdcard = SdmmcSdCard::new(device, Delay); + + config.frequency = 32_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); + SdCard::new(sdcard, det) + }; + + usb_handler(usb, sdcard).await; } use abi::Syscall; @@ -87,7 +93,7 @@ use abi::Syscall; pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { let call = unsafe { &*call }; match call { - Syscall::DrawPixel { x, y, color } => { + Syscall::DrawPixels { x, y, color } => { draw_pixel(*x, *y, *color); 0 } diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs new file mode 100644 index 0000000..a50ff26 --- /dev/null +++ b/kernel/src/scsi/mod.rs @@ -0,0 +1,308 @@ +use crate::format; +use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; +use embassy_usb::types::StringIndex; +use embassy_usb::{Builder, Config}; +use embedded_sdmmc::{Block, BlockIdx}; +use heapless::Vec; + +mod scsi_types; +use scsi_types::*; + +use crate::storage::SdCard; + +const BULK_ENDPOINT_PACKET_SIZE: usize = 64; + +pub struct MassStorageClass<'d, D: Driver<'d>> { + sdcard: SdCard, + bulk_out: D::EndpointOut, + bulk_in: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> MassStorageClass<'d, D> { + pub fn new(builder: &mut Builder<'d, D>, sdcard: SdCard) -> Self { + let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); + + let bulk_out = alt.endpoint_bulk_out(BULK_ENDPOINT_PACKET_SIZE as u16); + let bulk_in = alt.endpoint_bulk_in(BULK_ENDPOINT_PACKET_SIZE as u16); + + Self { + bulk_out, + bulk_in, + sdcard, + } + } + + pub async fn poll(&mut self) { + loop { + let mut cbw_buf = [0u8; 31]; + if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { + if n == 31 { + if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { + // TODO: validate cbw + if self.handle_command(&cbw.CBWCB).await.is_ok() { + self.send_csw_success(cbw.dCBWTag).await + } else { + self.send_csw_fail(cbw.dCBWTag).await + } + } + } + } + } + } + + async fn handle_command(&mut self, cbw: &[u8]) -> Result<(), ()> { + let mut response: Vec = Vec::new(); + let mut block = [Block::new(); 1]; + + match parse_cb(cbw) { + ScsiCommand::Unknown => { + #[cfg(feature = "defmt")] + defmt::warn!("Got unexpected scsi command: {}", cbw); + Err(()) + } + ScsiCommand::Inquiry { + evpd, + page_code, + alloc_len, + } => { + if !evpd { + response.push(0x00).map_err(|_| ())?; // Direct-access block device + response.push(0x80).map_err(|_| ())?; // Removable + response.push(0x05).map_err(|_| ())?; // SPC-3 compliance + response.push(0x02).map_err(|_| ())?; // Response data format + response.push(0x00).map_err(|_| ())?; // Additional length - edited later + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0).map_err(|_| ())?; // FLAGS + assert!(response.len() == 8); + + let vendor = b"LEGTCMPR"; + assert!(vendor.len() == 8); + response.extend_from_slice(vendor)?; + + let product = b"Pico Calc Sdcard"; + assert!(product.len() == 16); + response.extend_from_slice(product)?; + + let version = b"1.00"; + assert!(version.len() == 4); + response.extend_from_slice(version)?; // 4-byte firmware version + + let addl_len = response.len() - 5; + response[4] = addl_len as u8; + assert!(response.len() == 36); + } else { + match page_code { + 0x00 => { + response + .extend_from_slice(&[ + 0x00, // Peripheral Qualifier + Peripheral Device Type (0x00 = Direct-access block device) + 0x00, // Page Code (same as requested: 0x00) + 0x00, 0x03, // Page Length: 3 bytes follow + 0x00, // Supported VPD Page: 0x00 (this one — the "Supported VPD Pages" page itself) + 0x80, // Supported VPD Page: 0x80 (Unit Serial Number) + 0x83, // Supported VPD Page: 0x83 (Device Identification) + ]) + .map_err(|_| ())? + } + 0x80 => { + let serial = b"Pico Calc"; + response.extend_from_slice(&[ + 0x00, // Peripheral Qualifier & Device Type + 0x80, // Page Code = 0x80 (Unit Serial Number) + 0x00, // Reserved + serial.len() as u8, + ])?; + response.extend_from_slice(serial)?; + } + 0x83 => { + let id = b"SdCard"; + response.extend_from_slice(&[ + 0x00, + 0x83, // Page code + 0x00, + (4 + id.len()) as u8, // Length + 0x02, // ASCII identifier + 0x01, // Identifier type + 0x00, // Reserved + id.len() as u8, + ])?; + response.extend_from_slice(id)?; + } + _ => (), + } + }; + + let len = core::cmp::min(alloc_len as usize, response.len()); + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::TestUnitReady => { + if self.sdcard.is_attached() { + Ok(()) + } else { + Err(()) + } + } + ScsiCommand::RequestSense { desc, alloc_len } => Ok(()), + ScsiCommand::ModeSense6 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + // DBD=0, no block descriptors; total length = 4 + let response = [ + 0x03, // Mode data length (excluding this byte): 3 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, // Block descriptor length = 0 (DBD = 1) + ]; + + let len = alloc_len.min(response.len() as u8) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ModeSense10 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + let response = [ + 0x00, 0x06, // Mode data length = 6 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, 0x00, // Reserved + 0x00, 0x00, // Block descriptor length = 0 + ]; + + let len = alloc_len.min(response.len() as u16) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity10 => { + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = self.sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&(last_lba as u32).to_be_bytes())?; + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; + + self.bulk_in.write(&response).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity16 { alloc_len } => { + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = self.sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&last_lba.to_be_bytes())?; // 8 bytes last LBA + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; // 4 bytes block length + response.extend_from_slice(&[0u8; 20])?; // 20 reserved bytes zeroed + + let len = alloc_len.min(response.len() as u32) as usize; + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::Read { lba, len } => { + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + self.sdcard.read_blocks(&mut block, block_idx)?; + for chunk in block[0].contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { + self.bulk_in.write(chunk).await.map_err(|_| ())?; + } + } + Ok(()) + } + ScsiCommand::Write { lba, len } => { + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + for chunk in block[0] + .contents + .chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into()) + { + self.bulk_out.read(chunk).await.map_err(|_| ())?; + } + self.sdcard.write_blocks(&mut block, block_idx)?; + } + Ok(()) + } + ScsiCommand::ReadFormatCapacities { alloc_len } => { + let block_size = SdCard::BLOCK_SIZE as u32; + let num_blocks = (self.sdcard.size() / block_size as u64) as u32; + + let mut response = [0u8; 12]; + + // Capacity List Length (8 bytes follows) + response[3] = 8; + + // Descriptor + response[4..8].copy_from_slice(&num_blocks.to_be_bytes()); + response[8] = 0x03; // formatted media + response[9..12].copy_from_slice(&block_size.to_be_bytes()[1..4]); // only 3 bytes + + let response_len = alloc_len.min(response.len() as u16) as usize; + self.bulk_in + .write(&response[..response_len]) + .await + .map_err(|_| ()) + } + ScsiCommand::PreventAllowMediumRemoval { prevent: _prevent } => Ok(()), + ScsiCommand::StartStopUnit { start, load_eject } => Ok(()), + } + } + + pub async fn send_csw_success(&mut self, tag: u32) { + self.send_csw(tag, 0x00, 0).await; + } + + pub async fn send_csw_fail(&mut self, tag: u32) { + defmt::error!("Command Failed"); + self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed + } + + pub async fn send_csw(&mut self, tag: u32, status: u8, residue: u32) { + let mut csw = [0u8; 13]; + csw[0..4].copy_from_slice(&0x53425355u32.to_le_bytes()); // Signature "USBS" + csw[4..8].copy_from_slice(&tag.to_le_bytes()); + csw[8..12].copy_from_slice(&residue.to_le_bytes()); + csw[12] = status; + let _ = self.bulk_in.write(&csw).await; + } +} + +#[repr(C, packed)] +struct CommandBlockWrapper { + dCBWSignature: u32, + dCBWTag: u32, + dCBWDataTransferLength: u32, + bmCBWFlags: u8, + bCBWLUN: u8, + bCBWCBLength: u8, + CBWCB: [u8; 16], +} + +impl CommandBlockWrapper { + fn parse(buf: &[u8]) -> Option { + if buf.len() < 31 { + return None; + } + let dCBWSignature = u32::from_le_bytes(buf[0..4].try_into().ok()?); + if dCBWSignature != 0x43425355 { + return None; // invalid signature + } + Some(Self { + dCBWSignature, + dCBWTag: u32::from_le_bytes(buf[4..8].try_into().ok()?), + dCBWDataTransferLength: u32::from_le_bytes(buf[8..12].try_into().ok()?), + bmCBWFlags: buf[12], + bCBWLUN: buf[13], + bCBWCBLength: buf[14], + CBWCB: buf[15..31].try_into().ok()?, + }) + } +} diff --git a/kernel/src/scsi/scsi_types.rs b/kernel/src/scsi/scsi_types.rs new file mode 100644 index 0000000..dbd27dd --- /dev/null +++ b/kernel/src/scsi/scsi_types.rs @@ -0,0 +1,164 @@ +use num_enum::TryFromPrimitive; + +#[derive(Debug, Clone, Copy)] +pub enum ScsiError { + NotReady, +} + +/// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs + +/// SCSI device subclass code +pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set + +/* SCSI codes */ + +/* SPC */ +const TEST_UNIT_READY: u8 = 0x00; +const REQUEST_SENSE: u8 = 0x03; +const INQUIRY: u8 = 0x12; +const MODE_SENSE_6: u8 = 0x1A; +const MODE_SENSE_10: u8 = 0x5A; + +/* SBC */ +const READ_10: u8 = 0x28; +const READ_16: u8 = 0x88; +const READ_CAPACITY_10: u8 = 0x25; +const READ_CAPACITY_16: u8 = 0x9E; +const WRITE_10: u8 = 0x2A; + +/* MMC */ +const READ_FORMAT_CAPACITIES: u8 = 0x23; + +const PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E; +const START_STOP_UNIT: u8 = 0x1B; + +/// SCSI command +/// +/// Refer to specifications (SPC,SAM,SBC,MMC,etc.) +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ScsiCommand { + Unknown, + + /* SPC */ + Inquiry { + evpd: bool, + page_code: u8, + alloc_len: u16, + }, + TestUnitReady, + RequestSense { + desc: bool, + alloc_len: u8, + }, + ModeSense6 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u8, + }, + ModeSense10 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u16, + }, + + /* SBC */ + ReadCapacity10, + ReadCapacity16 { + alloc_len: u32, + }, + Read { + lba: u64, + len: u64, + }, + Write { + lba: u64, + len: u64, + }, + + /* MMC */ + ReadFormatCapacities { + alloc_len: u16, + }, + + PreventAllowMediumRemoval { + prevent: bool, + }, + + StartStopUnit { + start: bool, + load_eject: bool, + }, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PageControl { + CurrentValues = 0b00, + ChangeableValues = 0b01, + DefaultValues = 0b10, + SavedValues = 0b11, +} + +#[allow(dead_code)] +pub fn parse_cb(cb: &[u8]) -> ScsiCommand { + match cb[0] { + TEST_UNIT_READY => ScsiCommand::TestUnitReady, + INQUIRY => ScsiCommand::Inquiry { + evpd: (cb[1] & 0b00000001) != 0, + page_code: cb[2], + alloc_len: u16::from_be_bytes([cb[3], cb[4]]), + }, + REQUEST_SENSE => ScsiCommand::RequestSense { + desc: (cb[1] & 0b00000001) != 0, + alloc_len: cb[4], + }, + READ_CAPACITY_10 => ScsiCommand::ReadCapacity10, + READ_CAPACITY_16 => ScsiCommand::ReadCapacity16 { + alloc_len: u32::from_be_bytes([cb[10], cb[11], cb[12], cb[13]]), + }, + READ_10 => ScsiCommand::Read { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + READ_16 => ScsiCommand::Read { + lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()), + len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()) as u64, + }, + WRITE_10 => ScsiCommand::Write { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + MODE_SENSE_6 => ScsiCommand::ModeSense6 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: cb[4], + }, + MODE_SENSE_10 => ScsiCommand::ModeSense10 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + READ_FORMAT_CAPACITIES => ScsiCommand::ReadFormatCapacities { + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + PREVENT_ALLOW_MEDIUM_REMOVAL => ScsiCommand::PreventAllowMediumRemoval { + prevent: (cb[1] & 0b00000001) != 0, + }, + START_STOP_UNIT => ScsiCommand::StartStopUnit { + start: (cb[4] & 0b00000001) != 0, + load_eject: (cb[4] & 0b00000010) != 0, + }, + _ => ScsiCommand::Unknown, + } +} diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs new file mode 100644 index 0000000..04b4353 --- /dev/null +++ b/kernel/src/storage.rs @@ -0,0 +1,100 @@ +use embassy_rp::gpio::{Input, Output}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::{ + Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, + Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, +}; + +pub const MAX_DIRS: usize = 4; +pub const MAX_FILES: usize = 5; +pub const MAX_VOLUMES: usize = 1; + +type Device = ExclusiveDevice, Output<'static>, embassy_time::Delay>; +type SD = SdmmcSdCard; +type VolMgr = VolumeManager; +type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; + +pub struct DummyTimeSource {} +impl TimeSource for DummyTimeSource { + fn get_timestamp(&self) -> Timestamp { + Timestamp::from_calendar(2022, 1, 1, 0, 0, 0).unwrap() + } +} + +pub struct SdCard { + det: Input<'static>, + volume_mgr: VolMgr, +} + +impl SdCard { + pub const BLOCK_SIZE: u16 = 512; + + pub fn new(sdcard: SD, det: Input<'static>) -> Self { + let volume_mgr = VolumeManager::<_, _, MAX_DIRS, MAX_FILES, MAX_VOLUMES>::new_with_limits( + sdcard, + DummyTimeSource {}, + 5000, + ); + Self { + det: det, + volume_mgr, + } + } + + /// Returns true if an SD card is inserted. + /// The DET pin is active-low via mechanical switch in the socket. + pub fn is_attached(&self) -> bool { + self.det.is_low() + } + + pub fn open_volume(&mut self) -> Result, ()> { + if self.is_attached() { + return Ok(self.volume_mgr.open_volume(VolumeIdx(0)).map_err(|_| ())?); + } + Err(()) + } + + pub fn size(&self) -> u64 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_bytes().unwrap_or(0); + DummyTimeSource {} + }); + + result + } + + pub fn num_blocks(&self) -> u32 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_blocks().unwrap_or(BlockCount(0)).0; + DummyTimeSource {} + }); + + result + } + + pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + res = sd.read(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } + + pub fn write_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + let res = sd.write(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } +} diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs new file mode 100644 index 0000000..acf1d4c --- /dev/null +++ b/kernel/src/usb.rs @@ -0,0 +1,36 @@ +use crate::{scsi::MassStorageClass, storage::SdCard}; +use embassy_futures::select::select; +use embassy_rp::{peripherals::USB, usb::Driver}; +use embassy_usb::{Builder, Config}; + +pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("LegitCamper"); + config.product = Some("PicoCalc"); + config.serial_number = Some("01001100"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 64]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + let mut scsi = MassStorageClass::new(&mut builder, sdcard); + let mut usb = builder.build(); + + loop { + select(usb.run(), scsi.poll()).await; + + defmt::warn!("rebuilding usb"); + usb.disable().await; + } +} diff --git a/kernel/src/utils.rs b/kernel/src/utils.rs new file mode 100644 index 0000000..62683c6 --- /dev/null +++ b/kernel/src/utils.rs @@ -0,0 +1,11 @@ +#[macro_export] +macro_rules! format { + ($len:literal, $($arg:tt)*) => {{ + use heapless::String; + use core::fmt::Write; + + let mut s: String<$len> = String::new(); + let _ = write!(&mut s, $($arg)*); + s + }} +} From 6a5ba29df1a6f1e069758c2bfcf691413dbca0f9 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 30 Jul 2025 16:31:02 -0600 Subject: [PATCH 03/27] fix display dep --- Cargo.lock | 2 +- kernel/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0643f42..ccfebe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1852,7 +1852,7 @@ dependencies = [ [[package]] name = "st7365p-lcd" version = "0.11.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs?branch=async#87abf450404865dcb535292e9e1a6a2457fd4599" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=87abf450404865dcb535292e9e1a6a2457fd4599#87abf450404865dcb535292e9e1a6a2457fd4599" dependencies = [ "bitvec", "embedded-graphics-core", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d74fd95..0fefc21 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -72,7 +72,7 @@ defmt-rtt = "0.4.2" embedded-graphics = { version = "0.8.1" } embedded-sdmmc = { version = "0.9", default-features = false } -st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch shared = { path = "../shared" } abi = { path = "../abi" } From 078e1f6931843b17f5d35fdc587cdac97196627f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 30 Jul 2025 17:44:52 -0600 Subject: [PATCH 04/27] global access to framebuff --- kernel/src/abi.rs | 12 ++++++ kernel/src/display.rs | 51 ++++++++++++++++--------- kernel/src/main.rs | 89 ++++++++++++++++++++++++------------------- 3 files changed, 95 insertions(+), 57 deletions(-) create mode 100644 kernel/src/abi.rs diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs new file mode 100644 index 0000000..833661d --- /dev/null +++ b/kernel/src/abi.rs @@ -0,0 +1,12 @@ +use abi::Syscall; + +#[unsafe(no_mangle)] +pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { + let call = unsafe { &*call }; + match call { + Syscall::DrawPixels { x, y, color } => { + draw_pixel(*x, *y, *color); + 0 + } + } +} diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 6766b8a..d284575 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,4 +1,4 @@ -use core::sync::atomic::Ordering; +use core::{cell::RefCell, sync::atomic::Ordering}; use defmt::info; use embassy_rp::{ @@ -6,7 +6,7 @@ use embassy_rp::{ peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex, signal::Signal}; use embassy_time::{Delay, Instant, Timer}; use embedded_graphics::{ Drawable, @@ -20,19 +20,31 @@ use embedded_graphics::{ use embedded_hal_bus::spi::ExclusiveDevice; use portable_atomic::AtomicBool; use st7365p_lcd::{FrameBuffer, ST7365P}; +use static_cell::StaticCell; + +type DISPLAY = ST7365P< + ExclusiveDevice, Output<'static>, Delay>, + Output<'static>, + Output<'static>, + Delay, +>; const SCREEN_WIDTH: usize = 320; const SCREEN_HEIGHT: usize = 320; -pub static DISPLAY_SIGNAL: Signal = Signal::new(); +type FB = FrameBuffer; +static FRAMEBUFFER_CELL: StaticCell = StaticCell::new(); +pub static FRAMEBUFFER: Mutex>> = + Mutex::new(RefCell::new(None)); -pub async fn display_handler( +pub async fn init_display( spi: Spi<'static, SPI1, Async>, cs: PIN_13, data: PIN_14, reset: PIN_15, -) { +) -> DISPLAY { let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); + defmt::info!("spi made"); let mut display = ST7365P::new( spi_device, Output::new(data, Level::Low), @@ -41,28 +53,31 @@ pub async fn display_handler( true, Delay, ); - let mut framebuffer: FrameBuffer< - SCREEN_WIDTH, - SCREEN_HEIGHT, - { SCREEN_WIDTH * SCREEN_HEIGHT }, - > = FrameBuffer::new(); + let framebuffer = FRAMEBUFFER_CELL.init(FrameBuffer::new()); display.init().await.unwrap(); display.set_custom_orientation(0x40).await.unwrap(); framebuffer.draw(&mut display).await.unwrap(); display.set_on().await.unwrap(); + FRAMEBUFFER + .lock() + .await + .swap(&RefCell::new(Some(framebuffer))); - DISPLAY_SIGNAL.signal(()); + display +} +pub async fn display_handler(mut display: DISPLAY) { loop { - DISPLAY_SIGNAL.wait().await; - - // text.draw(&mut framebuffer).unwrap(); - - let start = Instant::now(); - framebuffer + defmt::info!("drawing"); + FRAMEBUFFER + .lock() + .await + .borrow_mut() + .as_mut() + .unwrap() .partial_draw_batched(&mut display) .await .unwrap(); - info!("Elapsed {}ms", start.elapsed().as_millis()); + Timer::after_millis(32).await; // 30 fps } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index cc25387..7b24eab 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,9 +3,19 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] +mod display; +mod peripherals; +mod scsi; +mod storage; +mod usb; +mod utils; + use crate::{ - display::DISPLAY_SIGNAL, - peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + display::{FRAMEBUFFER, display_handler, init_display}, + peripherals::{ + conf_peripherals, + keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + }, storage::SdCard, usb::usb_handler, }; @@ -14,7 +24,7 @@ use {defmt_rtt as _, panic_probe as _}; use core::cell::RefCell; use embassy_executor::Spawner; -use embassy_futures::join::join; +use embassy_futures::join::{join, join3}; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, peripherals::{I2C1, USB}, @@ -25,20 +35,18 @@ use embassy_rp::{i2c, i2c::I2c, spi}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Delay, Timer}; -use embedded_graphics::primitives::Rectangle; +use embedded_graphics::{ + Drawable, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::{BinaryColor, Rgb565}, + prelude::{Point, RgbColor}, + primitives::Rectangle, + text::{Text, TextStyle}, +}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; use heapless::String; -mod peripherals; -use peripherals::conf_peripherals; -mod display; -use display::display_handler; -mod scsi; -mod storage; -mod usb; -mod utils; - embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; @@ -54,25 +62,30 @@ async fn main(_spawner: Spawner) { let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); conf_peripherals(i2c1).await; - // SPI1 bus display - let mut config = spi::Config::default(); - config.frequency = 16_000_000; - let spi1 = spi::Spi::new( - p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, - ); + Timer::after_millis(250).await; - let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + let display_fut = { + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + let spi = Spi::new( + p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, + ); + let cs = p.PIN_13; + let data = p.PIN_14; + let reset = p.PIN_15; + + let display = init_display(spi, cs, data, reset).await; + display_handler(display) + }; + defmt::info!("ready"); let sdcard = { let mut config = spi::Config::default(); config.frequency = 400_000; - let spi = Spi::new_blocking( - p.SPI0, - p.PIN_18, // clk - p.PIN_19, // mosi - p.PIN_16, // miso - config.clone(), - ); + let clk = p.PIN_18; + let mosi = p.PIN_19; + let miso = p.PIN_16; + let spi = Spi::new_blocking(p.SPI0, clk, mosi, miso, config.clone()); let cs = Output::new(p.PIN_17, Level::High); let det = Input::new(p.PIN_22, Pull::None); @@ -84,18 +97,16 @@ async fn main(_spawner: Spawner) { SdCard::new(sdcard, det) }; - usb_handler(usb, sdcard).await; -} + let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + let usb_fut = usb_handler(usb, sdcard); -use abi::Syscall; + Text::new( + "Framebuffer works", + Point::new(100, 100), + MonoTextStyle::new(&FONT_6X10, Rgb565::GREEN), + ) + .draw(*FRAMEBUFFER.lock().await.get_mut().as_mut().unwrap()) + .unwrap(); -#[no_mangle] -pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { - let call = unsafe { &*call }; - match call { - Syscall::DrawPixels { x, y, color } => { - draw_pixel(*x, *y, *color); - 0 - } - } + display_fut.await; } From 602e47f57b7e383d58de430d631ddd77429c91a9 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 30 Jul 2025 18:00:47 -0600 Subject: [PATCH 05/27] getting simple display abi --- abi/src/lib.rs | 2 +- kernel/src/abi.rs | 21 ++++++++++++++++++++- kernel/src/display.rs | 20 +++----------------- kernel/src/main.rs | 28 ++++------------------------ 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 0411192..1dbc74a 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -5,5 +5,5 @@ use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; #[repr(C)] pub enum Syscall { - DrawPixels { x: u32, y: u32, color: u32 }, + DrawPixel { x: u32, y: u32, color: u16 }, } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 833661d..4cf56ca 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,12 +1,31 @@ use abi::Syscall; +use embassy_futures::block_on; +use embedded_graphics::{ + Drawable, + pixelcolor::Rgb565, + prelude::{Point, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, +}; + +use crate::display::FRAMEBUFFER; #[unsafe(no_mangle)] pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { let call = unsafe { &*call }; match call { - Syscall::DrawPixels { x, y, color } => { + Syscall::DrawPixel { x, y, color } => { draw_pixel(*x, *y, *color); 0 } } } + +fn draw_pixel(x: u32, y: u32, color: u16) { + let framebuffer = block_on(FRAMEBUFFER.lock()); + Rectangle::new(Point::new(x as i32, y as i32), Size::new(1, 1)) + .draw_styled( + &PrimitiveStyle::with_fill(Rgb565::RED), + *framebuffer.borrow_mut().as_mut().unwrap(), + ) + .unwrap(); +} diff --git a/kernel/src/display.rs b/kernel/src/display.rs index d284575..5615e03 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,24 +1,12 @@ -use core::{cell::RefCell, sync::atomic::Ordering}; - -use defmt::info; +use core::cell::RefCell; use embassy_rp::{ gpio::{Level, Output}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex, signal::Signal}; -use embassy_time::{Delay, Instant, Timer}; -use embedded_graphics::{ - Drawable, - draw_target::DrawTarget, - mono_font::{MonoTextStyle, ascii::FONT_10X20}, - pixelcolor::Rgb565, - prelude::{Dimensions, Point, RgbColor, Size}, - primitives::Rectangle, - text::{Alignment, Text}, -}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex}; +use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; -use portable_atomic::AtomicBool; use st7365p_lcd::{FrameBuffer, ST7365P}; use static_cell::StaticCell; @@ -44,7 +32,6 @@ pub async fn init_display( reset: PIN_15, ) -> DISPLAY { let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); - defmt::info!("spi made"); let mut display = ST7365P::new( spi_device, Output::new(data, Level::Low), @@ -68,7 +55,6 @@ pub async fn init_display( pub async fn display_handler(mut display: DISPLAY) { loop { - defmt::info!("drawing"); FRAMEBUFFER .lock() .await diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 7b24eab..ec81cb6 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,6 +3,7 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] +mod abi; mod display; mod peripherals; mod scsi; @@ -11,7 +12,7 @@ mod usb; mod utils; use crate::{ - display::{FRAMEBUFFER, display_handler, init_display}, + display::{display_handler, init_display}, peripherals::{ conf_peripherals, keyboard::{KeyCode, KeyState, read_keyboard_fifo}, @@ -22,9 +23,8 @@ use crate::{ use {defmt_rtt as _, panic_probe as _}; -use core::cell::RefCell; use embassy_executor::Spawner; -use embassy_futures::join::{join, join3}; +use embassy_futures::join::join3; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, peripherals::{I2C1, USB}, @@ -32,20 +32,9 @@ use embassy_rp::{ usb as embassy_rp_usb, }; use embassy_rp::{i2c, i2c::I2c, spi}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::mutex::Mutex; use embassy_time::{Delay, Timer}; -use embedded_graphics::{ - Drawable, - mono_font::{MonoTextStyle, ascii::FONT_6X10}, - pixelcolor::{BinaryColor, Rgb565}, - prelude::{Point, RgbColor}, - primitives::Rectangle, - text::{Text, TextStyle}, -}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; -use heapless::String; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; @@ -77,7 +66,6 @@ async fn main(_spawner: Spawner) { let display = init_display(spi, cs, data, reset).await; display_handler(display) }; - defmt::info!("ready"); let sdcard = { let mut config = spi::Config::default(); @@ -100,13 +88,5 @@ async fn main(_spawner: Spawner) { let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); let usb_fut = usb_handler(usb, sdcard); - Text::new( - "Framebuffer works", - Point::new(100, 100), - MonoTextStyle::new(&FONT_6X10, Rgb565::GREEN), - ) - .draw(*FRAMEBUFFER.lock().await.get_mut().as_mut().unwrap()) - .unwrap(); - - display_fut.await; + join3(async { loop {} }, usb_fut, display_fut).await; } From 98fb7361272faafbbeaaaf6fdf6bee6e83023b16 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 31 Jul 2025 17:23:24 -0600 Subject: [PATCH 06/27] enhance usb control loop --- kernel/src/main.rs | 9 +++++++-- kernel/src/usb.rs | 22 ++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index ec81cb6..23a5291 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -24,7 +24,7 @@ use crate::{ use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; -use embassy_futures::join::join3; +use embassy_futures::join::{join, join3}; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, peripherals::{I2C1, USB}, @@ -32,6 +32,7 @@ use embassy_rp::{ usb as embassy_rp_usb, }; use embassy_rp::{i2c, i2c::I2c, spi}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -41,6 +42,9 @@ embassy_rp::bind_interrupts!(struct Irqs { USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); +// Controls the usb running to prevents sdcard writes via scsi and by the kernel +static USB_ENABLED: Signal = Signal::new(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -86,7 +90,8 @@ async fn main(_spawner: Spawner) { }; let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + USB_ENABLED.signal(true); let usb_fut = usb_handler(usb, sdcard); - join3(async { loop {} }, usb_fut, display_fut).await; + join(usb_fut, display_fut).await; } diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index acf1d4c..d058e23 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,5 +1,5 @@ -use crate::{scsi::MassStorageClass, storage::SdCard}; -use embassy_futures::select::select; +use crate::{USB_ENABLED, scsi::MassStorageClass, storage::SdCard}; +use embassy_futures::{join::join, select::select3}; use embassy_rp::{peripherals::USB, usb::Driver}; use embassy_usb::{Builder, Config}; @@ -28,9 +28,19 @@ pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { let mut usb = builder.build(); loop { - select(usb.run(), scsi.poll()).await; - - defmt::warn!("rebuilding usb"); - usb.disable().await; + if USB_ENABLED.wait().await { + select3( + async { + loop { + // stop usb task until usb is enabled again + USB_ENABLED.wait().await; + return; // breaks out of select + } + }, + usb.run(), + scsi.poll(), + ) + .await; + } } } From db814705312b60acab2379e73e116f633deb7b4e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 1 Aug 2025 01:29:03 -0600 Subject: [PATCH 07/27] only enable scsi when sdcard is detected --- kernel/src/main.rs | 4 ---- kernel/src/usb.rs | 46 ++++++++++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 23a5291..09acd98 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -42,9 +42,6 @@ embassy_rp::bind_interrupts!(struct Irqs { USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); -// Controls the usb running to prevents sdcard writes via scsi and by the kernel -static USB_ENABLED: Signal = Signal::new(); - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -90,7 +87,6 @@ async fn main(_spawner: Spawner) { }; let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); - USB_ENABLED.signal(true); let usb_fut = usb_handler(usb, sdcard); join(usb_fut, display_fut).await; diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index d058e23..161f5da 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,7 +1,17 @@ -use crate::{USB_ENABLED, scsi::MassStorageClass, storage::SdCard}; -use embassy_futures::{join::join, select::select3}; +use core::sync::atomic::Ordering; + +use crate::{scsi::MassStorageClass, storage::SdCard}; +use embassy_futures::{ + join::join, + select::{select, select3}, +}; use embassy_rp::{peripherals::USB, usb::Driver}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_usb::{Builder, Config}; +use portable_atomic::AtomicBool; + +static RESTART_USB: Signal = Signal::new(); +static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { let mut config = Config::new(0xc0de, 0xcafe); @@ -24,23 +34,27 @@ pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { &mut control_buf, ); + if sdcard.is_attached() { + ENABLE_SCSI.store(true, Ordering::Relaxed); + } let mut scsi = MassStorageClass::new(&mut builder, sdcard); let mut usb = builder.build(); loop { - if USB_ENABLED.wait().await { - select3( - async { - loop { - // stop usb task until usb is enabled again - USB_ENABLED.wait().await; - return; // breaks out of select - } - }, - usb.run(), - scsi.poll(), - ) - .await; - } + select3( + async { + loop { + RESTART_USB.wait().await; + return; + } + }, + usb.run(), + async { + if ENABLE_SCSI.load(Ordering::Acquire) { + scsi.poll().await + } + }, + ) + .await; } } From 8ca55fcdaf087de58bd921d05786e8570e14c773 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 1 Aug 2025 16:24:37 -0600 Subject: [PATCH 08/27] working on bin selection ui --- Cargo.lock | 66 ++++++++++++++++++ abi/src/lib.rs | 4 ++ kernel/Cargo.toml | 11 +-- kernel/src/abi.rs | 3 +- kernel/src/display.rs | 4 +- kernel/src/main.rs | 9 +-- kernel/src/scsi/mod.rs | 8 +-- kernel/src/storage.rs | 6 ++ kernel/src/ui.rs | 117 ++++++++++++++++++++++++++++++++ kernel/src/usb.rs | 12 +++- user-apps/calculator/Cargo.toml | 1 + 11 files changed, 222 insertions(+), 19 deletions(-) create mode 100644 kernel/src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index ccfebe5..de4d866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,6 +205,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "calculator" version = "0.1.0" +dependencies = [ + "abi", +] [[package]] name = "cfg-if" @@ -419,6 +422,17 @@ dependencies = [ "defmt 0.3.100", ] +[[package]] +name = "delegate" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6178a82cf56c836a3ba61a7935cdb1c49bfaa6fa4327cd5bf554a503087de26b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "diff" version = "0.1.13" @@ -471,6 +485,26 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elf_loader" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a0da8db95cff71e500b3d7015c2441a4eb628e0df788b23d1b8d1243314342" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "delegate", + "elf", + "portable-atomic", + "portable-atomic-util", +] + [[package]] name = "embassy-embedded-hal" version = "0.3.1" @@ -812,6 +846,27 @@ dependencies = [ "embedded-io", ] +[[package]] +name = "embedded-layout" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a90553247f2b05c59ac7894ea13d830636c2b1203fa03bff400eddbd1fa9f52" +dependencies = [ + "embedded-graphics", + "embedded-layout-macros", +] + +[[package]] +name = "embedded-layout-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6e621fe4c7e05b695274b722dc0a60bacd1c8696b58191baa0154713d52400" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "embedded-sdmmc" version = "0.9.0" @@ -1407,6 +1462,7 @@ dependencies = [ "cyw43-pio", "defmt 0.3.100", "defmt-rtt", + "elf_loader", "embassy-embedded-hal", "embassy-executor", "embassy-futures", @@ -1418,6 +1474,7 @@ dependencies = [ "embedded-hal 0.2.7", "embedded-hal-async", "embedded-hal-bus", + "embedded-layout", "embedded-sdmmc", "heapless", "num_enum 0.7.4", @@ -1540,6 +1597,15 @@ dependencies = [ "critical-section", ] +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "precomputed-hash" version = "0.1.1" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 1dbc74a..398e386 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,6 +3,10 @@ use core::ffi::c_void; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +unsafe extern "C" { + fn call_abi(call: *const Syscall); +} + #[repr(C)] pub enum Syscall { DrawPixel { x: u32, y: u32, color: u16 }, diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 0fefc21..39dcdca 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -70,12 +70,10 @@ portable-atomic = { version = "1.11", features = ["critical-section"] } defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" -embedded-graphics = { version = "0.8.1" } embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch - -shared = { path = "../shared" } -abi = { path = "../abi" } +embedded-graphics = { version = "0.8.1" } +embedded-layout = "0.4.2" static_cell = "2.1.1" bitflags = "2.9.1" @@ -83,3 +81,8 @@ talc = "4.4.3" spin = "0.10.0" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } +elf_loader = {version ="0.12.0", default-features = false, features = ["portable-atomic"]} + +shared = { path = "../shared" } +abi = { path = "../abi" } + diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 4cf56ca..8bb55ec 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -10,12 +10,11 @@ use embedded_graphics::{ use crate::display::FRAMEBUFFER; #[unsafe(no_mangle)] -pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { +pub extern "C" fn call_abi(call: *const Syscall) { let call = unsafe { &*call }; match call { Syscall::DrawPixel { x, y, color } => { draw_pixel(*x, *y, *color); - 0 } } } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 5615e03..388f9bf 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -17,8 +17,8 @@ type DISPLAY = ST7365P< Delay, >; -const SCREEN_WIDTH: usize = 320; -const SCREEN_HEIGHT: usize = 320; +pub const SCREEN_WIDTH: usize = 320; +pub const SCREEN_HEIGHT: usize = 320; type FB = FrameBuffer; static FRAMEBUFFER_CELL: StaticCell = StaticCell::new(); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 09acd98..4b89055 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -8,6 +8,7 @@ mod display; mod peripherals; mod scsi; mod storage; +mod ui; mod usb; mod utils; @@ -17,7 +18,7 @@ use crate::{ conf_peripherals, keyboard::{KeyCode, KeyState, read_keyboard_fifo}, }, - storage::SdCard, + storage::{SDCARD, SdCard}, usb::usb_handler, }; @@ -68,7 +69,7 @@ async fn main(_spawner: Spawner) { display_handler(display) }; - let sdcard = { + { let mut config = spi::Config::default(); config.frequency = 400_000; let clk = p.PIN_18; @@ -83,11 +84,11 @@ async fn main(_spawner: Spawner) { config.frequency = 32_000_000; sdcard.spi(|dev| dev.bus_mut().set_config(&config)); - SdCard::new(sdcard, det) + SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); }; let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); - let usb_fut = usb_handler(usb, sdcard); + let usb_fut = usb_handler(usb); join(usb_fut, display_fut).await; } diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index a50ff26..7859fa5 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -12,14 +12,14 @@ use crate::storage::SdCard; const BULK_ENDPOINT_PACKET_SIZE: usize = 64; -pub struct MassStorageClass<'d, D: Driver<'d>> { - sdcard: SdCard, +pub struct MassStorageClass<'d, 's, D: Driver<'d>> { + sdcard: &'s SdCard, bulk_out: D::EndpointOut, bulk_in: D::EndpointIn, } -impl<'d, D: Driver<'d>> MassStorageClass<'d, D> { - pub fn new(builder: &mut Builder<'d, D>, sdcard: SdCard) -> Self { +impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { + pub fn new(builder: &mut Builder<'d, D>, sdcard: &'s SdCard) -> Self { let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class let mut interface = function.interface(); let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 04b4353..ea698b0 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -1,6 +1,9 @@ use embassy_rp::gpio::{Input, Output}; use embassy_rp::peripherals::SPI0; use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::lazy_lock::LazyLock; +use embassy_sync::mutex::Mutex; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::{ @@ -18,6 +21,9 @@ type VolMgr = VolumeManager = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +pub static SDCARD: LazyLock>> = + LazyLock::new(|| Mutex::new(None)); + pub struct DummyTimeSource {} impl TimeSource for DummyTimeSource { fn get_timestamp(&self) -> Timestamp { diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs new file mode 100644 index 0000000..acb80fd --- /dev/null +++ b/kernel/src/ui.rs @@ -0,0 +1,117 @@ +use crate::{ + display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, + format, +}; +use core::fmt::Debug; +use defmt::info; +use embassy_rp::{ + gpio::{Level, Output}, + peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, + spi::{Async, Spi}, +}; +use embassy_time::{Delay, Timer}; +use embedded_graphics::{ + Drawable, + draw_target::DrawTarget, + mono_font::{ + MonoTextStyle, + ascii::{FONT_6X10, FONT_9X15, FONT_10X20}, + }, + pixelcolor::Rgb565, + prelude::{Dimensions, Point, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, + text::Text, +}; +use embedded_hal_async::spi::SpiDevice; +use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::LinearLayout, + object_chain::Chain, + prelude::*, +}; +use heapless::{String, Vec}; + +pub struct UI { + pub selections_list: SelectionList, +} + +impl UI { + pub fn new() -> Self { + Self { + selections_list: SelectionList::new(Vec::new()), + } + } + + pub async fn draw>(&mut self) + where + ::Error: Debug, + { + self.draw_selection().await; + } + + async fn draw_selection(&mut self) { + let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + + let selection = Rectangle::new( + Point::new(0, 0), + Size::new(SCREEN_WIDTH as u32 - 1, SCREEN_HEIGHT as u32 - 1), + ); + + let mut file_names = self.selections_list.selections.iter(); + + let Some(first) = file_names.next() else { + Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory", + Point::zero(), text_style).draw(*FRAMEBUFFER.lock().await.borrow_mut().as_mut().unwrap()).unwrap(); + + return; + }; + + let chain = Chain::new(Text::new(first, Point::zero(), text_style)); + + LinearLayout::vertical(chain) + .with_alignment(horizontal::Center) + .arrange() + .align_to( + &FRAMEBUFFER + .lock() + .await + .borrow_mut() + .as_mut() + .unwrap() + .bounding_box(), + horizontal::Center, + vertical::Center, + ) + .draw(*FRAMEBUFFER.lock().await.borrow_mut().as_mut().unwrap()) + .unwrap(); + } +} + +pub struct SelectionList { + current_selection: u16, + selections: Vec, MAX_SELECTION>, +} + +impl + SelectionList +{ + pub fn new(selections: Vec, MAX_SELECTION>) -> Self { + Self { + selections, + current_selection: 0, + } + } + + pub fn down(&mut self) { + if self.current_selection + 1 < self.selections.len() as u16 { + self.current_selection += 1 + } + } + + pub fn up(&mut self) { + if self.current_selection > self.selections.len() as u16 { + self.current_selection -= 1 + } + } +} diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 161f5da..50dd4e7 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,6 +1,9 @@ use core::sync::atomic::Ordering; -use crate::{scsi::MassStorageClass, storage::SdCard}; +use crate::{ + scsi::MassStorageClass, + storage::{SDCARD, SdCard}, +}; use embassy_futures::{ join::join, select::{select, select3}, @@ -13,7 +16,7 @@ use portable_atomic::AtomicBool; static RESTART_USB: Signal = Signal::new(); static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); -pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { +pub async fn usb_handler(driver: Driver<'static, USB>) { let mut config = Config::new(0xc0de, 0xcafe); config.manufacturer = Some("LegitCamper"); config.product = Some("PicoCalc"); @@ -34,10 +37,13 @@ pub async fn usb_handler(driver: Driver<'static, USB>, sdcard: SdCard) { &mut control_buf, ); + let lock = SDCARD.get().lock().await; + let mut sdcard = lock.as_ref().unwrap(); + if sdcard.is_attached() { ENABLE_SCSI.store(true, Ordering::Relaxed); } - let mut scsi = MassStorageClass::new(&mut builder, sdcard); + let mut scsi = MassStorageClass::new(&mut builder, &sdcard); let mut usb = builder.build(); loop { diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index ed5cba5..13d39be 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +abi = { path ="../../abi" } From 620339240767722d41bbeb5d1369922910cd9255 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 1 Aug 2025 17:58:10 -0600 Subject: [PATCH 09/27] create core1 for userland --- kernel/src/main.rs | 140 ++++++++++++++++++++++++++++++++++++++------- kernel/src/usb.rs | 9 +-- 2 files changed, 122 insertions(+), 27 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 4b89055..6caa9d6 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -19,21 +19,36 @@ use crate::{ keyboard::{KeyCode, KeyState, read_keyboard_fifo}, }, storage::{SDCARD, SdCard}, - usb::usb_handler, + usb::{ENABLE_SCSI, usb_handler}, }; +use defmt::unwrap; +use elf_loader::{Loader, object::ElfBinary}; +use static_cell::StaticCell; +use talc::*; + +static mut ARENA: [u8; 10000] = [0; 10000]; + +#[global_allocator] +static ALLOCATOR: Talck, ClaimOnOom> = + Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) + .lock(); + use {defmt_rtt as _, panic_probe as _}; -use embassy_executor::Spawner; -use embassy_futures::join::{join, join3}; +use embassy_executor::{Executor, Spawner}; +use embassy_futures::join::join; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, - peripherals::{I2C1, USB}, - spi::Spi, + i2c::{self, I2c}, + multicore::{Stack, spawn_core1}, + peripherals::{ + DMA_CH0, DMA_CH1, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15, + PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, SPI0, SPI1, USB, + }, + spi::{self, Spi}, usb as embassy_rp_usb, }; -use embassy_rp::{i2c, i2c::I2c, spi}; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -43,14 +58,97 @@ embassy_rp::bind_interrupts!(struct Irqs { USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(userland_task()))); + }, + ); + + let display = Display { + spi: p.SPI1, + clk: p.PIN_10, + mosi: p.PIN_11, + miso: p.PIN_12, + dma1: p.DMA_CH0, + dma2: p.DMA_CH1, + cs: p.PIN_13, + data: p.PIN_14, + reset: p.PIN_15, + }; + let sd = Sd { + spi: p.SPI0, + clk: p.PIN_18, + mosi: p.PIN_19, + miso: p.PIN_16, + cs: p.PIN_17, + det: p.PIN_22, + }; + let mcu = Mcu { + i2c: p.I2C1, + clk: p.PIN_7, + data: p.PIN_6, + }; + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(display, sd, mcu, p.USB)))); +} + +// runs dynamically loaded elf files +#[embassy_executor::task] +async fn userland_task() { + // let loader = Loader::new(); + + // let binary_data: &[u8] = &[0; 10]; //include_bytes!("example.bin"); + // let bin = loader.load_exec(binary_data, None).unwrap(); + // let entry = bin.entry(); + + // let entry_fn: extern "C" fn() = unsafe { core::mem::transmute(entry) }; + // entry_fn(); // jump into user code +} + +struct Display { + spi: SPI1, + clk: PIN_10, + mosi: PIN_11, + miso: PIN_12, + dma1: DMA_CH0, + dma2: DMA_CH1, + cs: PIN_13, + data: PIN_14, + reset: PIN_15, +} + +struct Sd { + spi: SPI0, + clk: PIN_18, + mosi: PIN_19, + miso: PIN_16, + cs: PIN_17, + det: PIN_22, +} + +struct Mcu { + i2c: I2C1, + clk: PIN_7, + data: PIN_6, +} + +#[embassy_executor::task] +async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { // MCU i2c bus for peripherals let mut config = i2c::Config::default(); config.frequency = 400_000; - let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); + let i2c1 = I2c::new_async(mcu.i2c, mcu.clk, mcu.data, Irqs, config); conf_peripherals(i2c1).await; Timer::after_millis(250).await; @@ -59,25 +157,24 @@ async fn main(_spawner: Spawner) { let mut config = spi::Config::default(); config.frequency = 16_000_000; let spi = Spi::new( - p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, + display.spi, + display.clk, + display.mosi, + display.miso, + display.dma1, + display.dma2, + config, ); - let cs = p.PIN_13; - let data = p.PIN_14; - let reset = p.PIN_15; - - let display = init_display(spi, cs, data, reset).await; + let display = init_display(spi, display.cs, display.data, display.reset).await; display_handler(display) }; { let mut config = spi::Config::default(); config.frequency = 400_000; - let clk = p.PIN_18; - let mosi = p.PIN_19; - let miso = p.PIN_16; - let spi = Spi::new_blocking(p.SPI0, clk, mosi, miso, config.clone()); - let cs = Output::new(p.PIN_17, Level::High); - let det = Input::new(p.PIN_22, Pull::None); + let spi = Spi::new_blocking(sd.spi, sd.clk, sd.mosi, sd.miso, config.clone()); + let cs = Output::new(sd.cs, Level::High); + let det = Input::new(sd.det, Pull::None); let device = ExclusiveDevice::new(spi, cs, Delay).unwrap(); let sdcard = SdmmcSdCard::new(device, Delay); @@ -87,8 +184,9 @@ async fn main(_spawner: Spawner) { SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); }; - let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + let usb = embassy_rp_usb::Driver::new(usb, Irqs); let usb_fut = usb_handler(usb); + ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); join(usb_fut, display_fut).await; } diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 50dd4e7..b657211 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -13,8 +13,8 @@ use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_usb::{Builder, Config}; use portable_atomic::AtomicBool; -static RESTART_USB: Signal = Signal::new(); -static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); +pub static RESTART_USB: Signal = Signal::new(); +pub static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); pub async fn usb_handler(driver: Driver<'static, USB>) { let mut config = Config::new(0xc0de, 0xcafe); @@ -38,11 +38,8 @@ pub async fn usb_handler(driver: Driver<'static, USB>) { ); let lock = SDCARD.get().lock().await; - let mut sdcard = lock.as_ref().unwrap(); + let sdcard = lock.as_ref().unwrap(); - if sdcard.is_attached() { - ENABLE_SCSI.store(true, Ordering::Relaxed); - } let mut scsi = MassStorageClass::new(&mut builder, &sdcard); let mut usb = builder.build(); From aa00e9728d47dc188f59203f52078b1c54403f38 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 1 Aug 2025 21:32:35 -0600 Subject: [PATCH 10/27] load elf file --- kernel/src/main.rs | 18 ++++++++++-------- user-apps/calculator/src/main.rs | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 6caa9d6..fb64a01 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -23,7 +23,11 @@ use crate::{ }; use defmt::unwrap; -use elf_loader::{Loader, object::ElfBinary}; +use elf_loader::{ + Loader, load_exec, + mmap::MmapImpl, + object::{ElfBinary, ElfObject}, +}; use static_cell::StaticCell; use talc::*; @@ -106,14 +110,12 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - // let loader = Loader::new(); + let binary_data: &[u8] = include_bytes!("../../example.bin"); + let bin = load_exec!("example", binary_data).unwrap(); + let entry = bin.entry(); - // let binary_data: &[u8] = &[0; 10]; //include_bytes!("example.bin"); - // let bin = loader.load_exec(binary_data, None).unwrap(); - // let entry = bin.entry(); - - // let entry_fn: extern "C" fn() = unsafe { core::mem::transmute(entry) }; - // entry_fn(); // jump into user code + let entry_fn: extern "C" fn() = unsafe { core::mem::transmute(entry) }; + entry_fn(); // jump into user code } struct Display { diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index e7a11a9..4fc2132 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,3 +1,14 @@ -fn main() { - println!("Hello, world!"); +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} // or call your ABI trap, or `abort()` +} + +#[unsafe(no_mangle)] +fn main() { + loop {} } From 6dcdd88a0feb74cc25c92a2bec0089525685de1a Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 3 Aug 2025 18:55:26 -0600 Subject: [PATCH 11/27] can run entrypoint on user elf syscalls via call_abi are still not working --- Cargo.lock | 87 ++++++++++++++------------------ abi/src/lib.rs | 5 +- kernel/Cargo.toml | 8 +-- kernel/build.rs | 2 +- memory.x => kernel/memory.x | 0 kernel/src/abi.rs | 7 ++- kernel/src/elf.rs | 85 +++++++++++++++++++++++++++++++ kernel/src/main.rs | 43 ++++++++-------- user-apps/calculator/src/main.rs | 18 +++++-- user-apps/link.x | 21 ++++++++ 10 files changed, 193 insertions(+), 83 deletions(-) rename memory.x => kernel/memory.x (100%) create mode 100644 kernel/src/elf.rs create mode 100644 user-apps/link.x diff --git a/Cargo.lock b/Cargo.lock index de4d866..8d737a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,17 +422,6 @@ dependencies = [ "defmt 0.3.100", ] -[[package]] -name = "delegate" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6178a82cf56c836a3ba61a7935cdb1c49bfaa6fa4327cd5bf554a503087de26b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "diff" version = "0.1.13" @@ -485,26 +474,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elf" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" - -[[package]] -name = "elf_loader" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a0da8db95cff71e500b3d7015c2441a4eb628e0df788b23d1b8d1243314342" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "delegate", - "elf", - "portable-atomic", - "portable-atomic-util", -] - [[package]] name = "embassy-embedded-hal" version = "0.3.1" @@ -662,7 +631,6 @@ dependencies = [ "nb 1.1.0", "pio 0.3.0", "rand_core", - "rp-binary-info", "rp-pac", "rp2040-boot2", "sha2-const-stable", @@ -1065,6 +1033,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "goblin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e961b33649994dcf69303af6b3a332c1228549e604d455d61ec5d2ab5e68d3a" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "half" version = "2.6.0" @@ -1456,13 +1435,13 @@ dependencies = [ "abi", "bitflags 2.9.1", "bt-hci", + "bumpalo", "cortex-m", "cortex-m-rt", "cyw43", "cyw43-pio", "defmt 0.3.100", "defmt-rtt", - "elf_loader", "embassy-embedded-hal", "embassy-executor", "embassy-futures", @@ -1476,6 +1455,7 @@ dependencies = [ "embedded-hal-bus", "embedded-layout", "embedded-sdmmc", + "goblin", "heapless", "num_enum 0.7.4", "panic-probe", @@ -1588,6 +1568,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1597,15 +1583,6 @@ dependencies = [ "critical-section", ] -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1752,12 +1729,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "rp-binary-info" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ed2051a0bf2c726df01cfce378ed8a367be2a6e402fc183857f429a346d429" - [[package]] name = "rp-pac" version = "7.0.0" @@ -1807,6 +1778,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "semver" version = "0.9.0" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 398e386..b39cd25 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,9 +3,8 @@ use core::ffi::c_void; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -unsafe extern "C" { - fn call_abi(call: *const Syscall); -} +#[unsafe(no_mangle)] +pub unsafe fn call_abi(_call: *const Syscall) {} #[repr(C)] pub enum Syscall { diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 39dcdca..d6d0173 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -41,7 +41,6 @@ embassy-rp = { version = "0.4.0", features = [ "critical-section-impl", "unstable-pac", "time-driver", - "binary-info", ] } embassy-usb = "0.4.0" embassy-futures = "0.1.1" @@ -77,11 +76,12 @@ embedded-layout = "0.4.2" static_cell = "2.1.1" bitflags = "2.9.1" -talc = "4.4.3" -spin = "0.10.0" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } -elf_loader = {version ="0.12.0", default-features = false, features = ["portable-atomic"]} +goblin = { version = "0.10.0", default-features = false, features = ["elf32", "elf64", "alloc", "endian_fd"] } +bumpalo = "3.19.0" +talc = "4.4.3" +spin = "0.10.0" shared = { path = "../shared" } abi = { path = "../abi" } diff --git a/kernel/build.rs b/kernel/build.rs index e0ded4a..30691aa 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -19,7 +19,7 @@ fn main() { let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); File::create(out.join("memory.x")) .unwrap() - .write_all(include_bytes!("../memory.x")) + .write_all(include_bytes!("memory.x")) .unwrap(); println!("cargo:rustc-link-search={}", out.display()); diff --git a/memory.x b/kernel/memory.x similarity index 100% rename from memory.x rename to kernel/memory.x diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 8bb55ec..5eba4df 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,4 +1,5 @@ use abi::Syscall; +use defmt::info; use embassy_futures::block_on; use embedded_graphics::{ Drawable, @@ -9,8 +10,9 @@ use embedded_graphics::{ use crate::display::FRAMEBUFFER; -#[unsafe(no_mangle)] -pub extern "C" fn call_abi(call: *const Syscall) { +#[allow(unused)] +pub fn call_abi(call: *const Syscall) { + info!("called abi"); let call = unsafe { &*call }; match call { Syscall::DrawPixel { x, y, color } => { @@ -20,6 +22,7 @@ pub extern "C" fn call_abi(call: *const Syscall) { } fn draw_pixel(x: u32, y: u32, color: u16) { + info!("draw pixel abi called"); let framebuffer = block_on(FRAMEBUFFER.lock()); Rectangle::new(Point::new(x as i32, y as i32), Size::new(1, 1)) .draw_styled( diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs new file mode 100644 index 0000000..13943f2 --- /dev/null +++ b/kernel/src/elf.rs @@ -0,0 +1,85 @@ +#![allow(static_mut_refs)] +use abi::Syscall; +use bumpalo::Bump; +use core::{alloc::Layout, ffi::c_void, ptr::NonNull, slice::from_raw_parts_mut}; +use goblin::{ + elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}, + elf32, +}; + +use crate::abi::call_abi; + +pub fn load_elf(elf_bytes: &[u8], bump: &mut Bump) -> Result !, ()> { + let elf = Elf::parse(elf_bytes).map_err(|_| ())?; + + if elf.is_64 + || elf.is_lib + || elf.is_object_file() + || !elf.little_endian + || elf.header.e_type != ET_DYN + || elf.interpreter.is_some() + { + return Err(()); + } + + // Find base address (lowest virtual address of PT_LOAD segments) + let base_vaddr = elf + .program_headers + .iter() + .filter(|ph| ph.p_type == PT_LOAD) + .map(|ph| ph.p_vaddr) + .min() + .ok_or(())?; + + // Determine total memory needed for all PT_LOAD segments + let total_size = elf + .program_headers + .iter() + .filter(|ph| ph.p_type == PT_LOAD) + .map(|ph| { + let start = ph.p_vaddr; + let end = ph.p_vaddr + ph.p_memsz; + end - base_vaddr + }) + .max() + .unwrap_or(0) as usize; + + // Allocate one big block from the bump heap + let layout = Layout::from_size_align(total_size, 0x1000).map_err(|_| ())?; + let base_ptr = bump.alloc_layout(layout).as_ptr(); + + for ph in &elf.program_headers { + if ph.p_type != PT_LOAD { + continue; + } + + let file_offset = ph.p_offset as usize; + let file_size = ph.p_filesz as usize; + let mem_size = ph.p_memsz as usize; + let virt_offset = (ph.p_vaddr - base_vaddr) as usize; + + let src = &elf_bytes[file_offset..file_offset + file_size]; + let dst = unsafe { base_ptr.add(virt_offset) }; + + unsafe { + core::ptr::copy_nonoverlapping(src.as_ptr(), dst, file_size); + if mem_size > file_size { + core::ptr::write_bytes(dst.add(file_size), 0, mem_size - file_size); + } + } + } + + // Patch `call_abi` symbol + for sym in elf.syms.iter() { + let name = elf.strtab.get_at(sym.st_name).ok_or(())?; + if name == "call_abi" && sym.st_bind() == sym::STB_GLOBAL { + let offset = (sym.st_value - base_vaddr) as usize; + let ptr = unsafe { base_ptr.add(offset) as *mut usize }; + unsafe { *ptr = call_abi as usize }; + } + } + + // Compute relocated entry point + let relocated_entry = unsafe { base_ptr.add((elf.entry - base_vaddr) as usize) }; + Ok(unsafe { core::mem::transmute(relocated_entry) }) +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index fb64a01..d0b34f5 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -5,6 +5,7 @@ mod abi; mod display; +mod elf; mod peripherals; mod scsi; mod storage; @@ -14,6 +15,7 @@ mod utils; use crate::{ display::{display_handler, init_display}, + elf::load_elf, peripherals::{ conf_peripherals, keyboard::{KeyCode, KeyState, read_keyboard_fifo}, @@ -22,24 +24,10 @@ use crate::{ usb::{ENABLE_SCSI, usb_handler}, }; -use defmt::unwrap; -use elf_loader::{ - Loader, load_exec, - mmap::MmapImpl, - object::{ElfBinary, ElfObject}, -}; -use static_cell::StaticCell; -use talc::*; - -static mut ARENA: [u8; 10000] = [0; 10000]; - -#[global_allocator] -static ALLOCATOR: Talck, ClaimOnOom> = - Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) - .lock(); - use {defmt_rtt as _, panic_probe as _}; +use bumpalo::Bump; +use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::join::join; use embassy_rp::{ @@ -56,16 +44,25 @@ use embassy_rp::{ use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; +use static_cell::StaticCell; +use talc::*; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); -static mut CORE1_STACK: Stack<4096> = Stack::new(); +static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); +static mut ARENA: [u8; 50_000] = [0; 50_000]; + +#[global_allocator] +static ALLOCATOR: Talck, ClaimOnOom> = + Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) + .lock(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -110,12 +107,14 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - let binary_data: &[u8] = include_bytes!("../../example.bin"); - let bin = load_exec!("example", binary_data).unwrap(); - let entry = bin.entry(); + let mut bump = Bump::with_capacity(25_000); - let entry_fn: extern "C" fn() = unsafe { core::mem::transmute(entry) }; - entry_fn(); // jump into user code + let binary_data: &[u8] = + include_bytes!("../../target/thumbv8m.main-none-eabihf/debug/calculator"); + let entry = load_elf(binary_data, &mut bump).unwrap(); + + entry(); + bump.reset(); // clear heap arena } struct Display { diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 4fc2132..3d219e5 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,14 +1,26 @@ #![no_std] #![no_main] +use abi::{Syscall, call_abi}; use core::panic::PanicInfo; #[panic_handler] fn panic(_info: &PanicInfo) -> ! { - loop {} // or call your ABI trap, or `abort()` + loop {} } #[unsafe(no_mangle)] -fn main() { - loop {} +pub extern "C" fn _start() -> ! { + loop { + for i in 0..300 { + for o in 0..300 { + let call = Syscall::DrawPixel { + x: i, + y: o, + color: 0, + }; + unsafe { call_abi(&call) }; + } + } + } } diff --git a/user-apps/link.x b/user-apps/link.x new file mode 100644 index 0000000..cdfc9f7 --- /dev/null +++ b/user-apps/link.x @@ -0,0 +1,21 @@ +MEMORY +{ + RAM : ORIGIN = 0x00000000, LENGTH = 256K +} + +SECTIONS +{ + .text : { + *(.text .text.*); + *(.rodata .rodata.*); + } > RAM + + .data : { + *(.data .data.*); + } > RAM + + .bss : { + *(.bss .bss.*); + *(COMMON); + } > RAM +} From c4f2c6cffb0c386b3eb20eb4c2b4d75d812495b8 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 26 Aug 2025 18:44:46 -0600 Subject: [PATCH 12/27] user app can invoke kernel syscall!!!! --- Cargo.lock | 79 ++++++++++---------- abi/src/lib.rs | 10 ++- justfile | 4 ++ kernel/Cargo.toml | 12 ++-- kernel/memory.x | 28 ++++---- kernel/src/abi.rs | 2 +- kernel/src/elf.rs | 119 ++++++++++++++----------------- kernel/src/main.rs | 15 ++-- user-apps/calculator/build.rs | 28 ++++++++ user-apps/calculator/src/main.rs | 20 +++--- user-apps/link.x | 21 ------ user-apps/memory.x | 33 +++++++++ 12 files changed, 201 insertions(+), 170 deletions(-) create mode 100644 justfile create mode 100644 user-apps/calculator/build.rs delete mode 100644 user-apps/link.x create mode 100644 user-apps/memory.x diff --git a/Cargo.lock b/Cargo.lock index 8d737a4..130e238 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,45 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "abi", + "bitflags 2.9.1", + "bt-hci", + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt 0.3.100", + "defmt-rtt", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", + "embassy-rp 0.4.0", + "embassy-sync 0.7.0", + "embassy-time", + "embassy-usb", + "embedded-graphics", + "embedded-hal 0.2.7", + "embedded-hal-async", + "embedded-hal-bus", + "embedded-layout", + "embedded-sdmmc", + "goblin", + "heapless", + "num_enum 0.7.4", + "panic-probe", + "portable-atomic", + "shared", + "spin", + "st7365p-lcd", + "static_cell", + "talc", + "trouble-host", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -1428,46 +1467,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "picocalc-os-rs" -version = "0.1.0" -dependencies = [ - "abi", - "bitflags 2.9.1", - "bt-hci", - "bumpalo", - "cortex-m", - "cortex-m-rt", - "cyw43", - "cyw43-pio", - "defmt 0.3.100", - "defmt-rtt", - "embassy-embedded-hal", - "embassy-executor", - "embassy-futures", - "embassy-rp 0.4.0", - "embassy-sync 0.7.0", - "embassy-time", - "embassy-usb", - "embedded-graphics", - "embedded-hal 0.2.7", - "embedded-hal-async", - "embedded-hal-bus", - "embedded-layout", - "embedded-sdmmc", - "goblin", - "heapless", - "num_enum 0.7.4", - "panic-probe", - "portable-atomic", - "shared", - "spin", - "st7365p-lcd", - "static_cell", - "talc", - "trouble-host", -] - [[package]] name = "pin-project-lite" version = "0.2.16" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index b39cd25..f97455e 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,8 +3,16 @@ use core::ffi::c_void; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +// Instead of extern, declare a static pointer in a dedicated section #[unsafe(no_mangle)] -pub unsafe fn call_abi(_call: *const Syscall) {} +#[unsafe(link_section = ".user_reloc")] +pub static mut call_abi_ptr: usize = 0; + +// Helper to call it +pub unsafe fn call_abi(call: *const Syscall) { + let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; + f(call); +} #[repr(C)] pub enum Syscall { diff --git a/justfile b/justfile new file mode 100644 index 0000000..79061bc --- /dev/null +++ b/justfile @@ -0,0 +1,4 @@ +kernel: calculator + cargo run --bin kernel +calculator: + RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --release diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d6d0173..2891c66 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "picocalc-os-rs" +name = "kernel" version = "0.1.0" edition = "2024" [[bin]] -name = "picocalc-os-rs" +name = "kernel" path = "src/main.rs" test = false doctest = false @@ -78,11 +78,13 @@ static_cell = "2.1.1" bitflags = "2.9.1" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } -goblin = { version = "0.10.0", default-features = false, features = ["elf32", "elf64", "alloc", "endian_fd"] } -bumpalo = "3.19.0" +goblin = { version = "0.10.0", default-features = false, features = [ + "elf32", + "elf64", + "endian_fd", +] } talc = "4.4.3" spin = "0.10.0" shared = { path = "../shared" } abi = { path = "../abi" } - diff --git a/kernel/memory.x b/kernel/memory.x index 5bdbb38..fd22299 100644 --- a/kernel/memory.x +++ b/kernel/memory.x @@ -1,22 +1,10 @@ MEMORY { - /* - * The RP2350 has either external or internal flash. - * - * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. - */ FLASH : ORIGIN = 0x10000000, LENGTH = 4096K - /* - * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. - * This is usually good for performance, as it distributes load on - * those banks evenly. - */ RAM : ORIGIN = 0x20000000, LENGTH = 512K - /* - * RAM banks 8 and 9 use a direct mapping. They can be used to have - * memory areas dedicated for some specific job, improving predictability - * of access times. - * Example: Separate stacks for core0 and core1. - */ + + /* Reserve a block of RAM for the user app */ + USERAPP : ORIGIN = 0x20010000, LENGTH = 192K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } @@ -73,3 +61,11 @@ SECTIONS { PROVIDE(start_to_end = __end_block_addr - __start_block_addr); PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + +SECTIONS { + .userapp (NOLOAD) : + { + __userapp_start__ = ORIGIN(USERAPP); + __userapp_end__ = ORIGIN(USERAPP) + LENGTH(USERAPP); + } > USERAPP +} diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 5eba4df..1f8c3ba 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -11,7 +11,7 @@ use embedded_graphics::{ use crate::display::FRAMEBUFFER; #[allow(unused)] -pub fn call_abi(call: *const Syscall) { +pub extern "C" fn call_abi(call: *const Syscall) { info!("called abi"); let call = unsafe { &*call }; match call { diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 13943f2..a553aa4 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,85 +1,70 @@ #![allow(static_mut_refs)] -use abi::Syscall; -use bumpalo::Bump; use core::{alloc::Layout, ffi::c_void, ptr::NonNull, slice::from_raw_parts_mut}; -use goblin::{ - elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}, - elf32, -}; +use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; -use crate::abi::call_abi; +// userland ram region defined in memory.x +unsafe extern "C" { + static __userapp_start__: u8; + static __userapp_end__: u8; +} -pub fn load_elf(elf_bytes: &[u8], bump: &mut Bump) -> Result !, ()> { - let elf = Elf::parse(elf_bytes).map_err(|_| ())?; +type EntryFn = extern "C" fn(); - if elf.is_64 - || elf.is_lib - || elf.is_object_file() - || !elf.little_endian - || elf.header.e_type != ET_DYN - || elf.interpreter.is_some() - { - return Err(()); +pub unsafe fn load_binary(bytes: &[u8]) -> Result { + let elf = Elf::parse(&bytes).expect("Failed to parse ELF"); + + if elf.is_64 || elf.is_lib || !elf.little_endian { + return Err("Unsupported ELF type"); } - // Find base address (lowest virtual address of PT_LOAD segments) - let base_vaddr = elf - .program_headers - .iter() - .filter(|ph| ph.p_type == PT_LOAD) - .map(|ph| ph.p_vaddr) - .min() - .ok_or(())?; - - // Determine total memory needed for all PT_LOAD segments - let total_size = elf - .program_headers - .iter() - .filter(|ph| ph.p_type == PT_LOAD) - .map(|ph| { - let start = ph.p_vaddr; - let end = ph.p_vaddr + ph.p_memsz; - end - base_vaddr - }) - .max() - .unwrap_or(0) as usize; - - // Allocate one big block from the bump heap - let layout = Layout::from_size_align(total_size, 0x1000).map_err(|_| ())?; - let base_ptr = bump.alloc_layout(layout).as_ptr(); - for ph in &elf.program_headers { - if ph.p_type != PT_LOAD { - continue; - } + if ph.p_type == PT_LOAD { + let vaddr = ph.p_vaddr as usize; + let memsz = ph.p_memsz as usize; + let filesz = ph.p_filesz as usize; + let offset = ph.p_offset as usize; - let file_offset = ph.p_offset as usize; - let file_size = ph.p_filesz as usize; - let mem_size = ph.p_memsz as usize; - let virt_offset = (ph.p_vaddr - base_vaddr) as usize; + let seg_start = vaddr; + let seg_end = vaddr + memsz; - let src = &elf_bytes[file_offset..file_offset + file_size]; - let dst = unsafe { base_ptr.add(virt_offset) }; + // Bounds check: make sure segment fits inside payload region + let user_start = unsafe { &__userapp_start__ as *const u8 as usize }; + let user_end = unsafe { &__userapp_end__ as *const u8 as usize }; + if seg_start < user_start || seg_end > user_end { + panic!( + "Segment out of bounds: {:x}..{:x} not within {:x}..{:x}", + seg_start, seg_end, user_start, user_end + ); + } - unsafe { - core::ptr::copy_nonoverlapping(src.as_ptr(), dst, file_size); - if mem_size > file_size { - core::ptr::write_bytes(dst.add(file_size), 0, mem_size - file_size); + unsafe { + let dst = seg_start as *mut u8; + let src = bytes.as_ptr().add(offset); + + // Copy initialized part + core::ptr::copy_nonoverlapping(src, dst, filesz); + + // Zero BSS region (memsz - filesz) + if memsz > filesz { + core::ptr::write_bytes(dst.add(filesz), 0, memsz - filesz); + } } } } - // Patch `call_abi` symbol - for sym in elf.syms.iter() { - let name = elf.strtab.get_at(sym.st_name).ok_or(())?; - if name == "call_abi" && sym.st_bind() == sym::STB_GLOBAL { - let offset = (sym.st_value - base_vaddr) as usize; - let ptr = unsafe { base_ptr.add(offset) as *mut usize }; - unsafe { *ptr = call_abi as usize }; - } + let call_abi_sym = elf + .syms + .iter() + .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "call_abi_ptr") + .expect("call_abi_ptr not found"); + + // Virtual address inside user RAM + let addr = call_abi_sym.st_value as *mut usize; + + // Patch it + unsafe { + core::ptr::write(addr, crate::abi::call_abi as usize); } - // Compute relocated entry point - let relocated_entry = unsafe { base_ptr.add((elf.entry - base_vaddr) as usize) }; - Ok(unsafe { core::mem::transmute(relocated_entry) }) + Ok(unsafe { core::mem::transmute(elf.entry as u32) }) } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index d0b34f5..715b489 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -15,7 +15,7 @@ mod utils; use crate::{ display::{display_handler, init_display}, - elf::load_elf, + elf::load_binary, peripherals::{ conf_peripherals, keyboard::{KeyCode, KeyState, read_keyboard_fifo}, @@ -26,7 +26,6 @@ use crate::{ use {defmt_rtt as _, panic_probe as _}; -use bumpalo::Bump; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::join::join; @@ -56,7 +55,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 50_000] = [0; 50_000]; +static mut ARENA: [u8; 10000] = [0; 10000]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -107,14 +106,14 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - let mut bump = Bump::with_capacity(25_000); - + defmt::info!("Loading binary"); let binary_data: &[u8] = - include_bytes!("../../target/thumbv8m.main-none-eabihf/debug/calculator"); - let entry = load_elf(binary_data, &mut bump).unwrap(); + include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); + + defmt::info!("Running binary"); + let entry = unsafe { load_binary(binary_data).unwrap() }; entry(); - bump.reset(); // clear heap arena } struct Display { diff --git a/user-apps/calculator/build.rs b/user-apps/calculator/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/calculator/build.rs @@ -0,0 +1,28 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("../memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); +} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 3d219e5..3c26e28 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -10,17 +10,15 @@ fn panic(_info: &PanicInfo) -> ! { } #[unsafe(no_mangle)] -pub extern "C" fn _start() -> ! { - loop { - for i in 0..300 { - for o in 0..300 { - let call = Syscall::DrawPixel { - x: i, - y: o, - color: 0, - }; - unsafe { call_abi(&call) }; - } +pub extern "C" fn _start() { + for i in 0..300 { + for o in 0..300 { + let call = Syscall::DrawPixel { + x: i, + y: o, + color: 0, + }; + unsafe { call_abi(&call) }; } } } diff --git a/user-apps/link.x b/user-apps/link.x deleted file mode 100644 index cdfc9f7..0000000 --- a/user-apps/link.x +++ /dev/null @@ -1,21 +0,0 @@ -MEMORY -{ - RAM : ORIGIN = 0x00000000, LENGTH = 256K -} - -SECTIONS -{ - .text : { - *(.text .text.*); - *(.rodata .rodata.*); - } > RAM - - .data : { - *(.data .data.*); - } > RAM - - .bss : { - *(.bss .bss.*); - *(COMMON); - } > RAM -} diff --git a/user-apps/memory.x b/user-apps/memory.x new file mode 100644 index 0000000..19fa9a5 --- /dev/null +++ b/user-apps/memory.x @@ -0,0 +1,33 @@ +MEMORY +{ + /* Must match the USERAPP region in the kernel linker script */ + RAM : ORIGIN = 0x20010000, LENGTH = 192K +} + +SECTIONS +{ + /* Reserve first 1KB for patchable symbols */ + .user_reloc (NOLOAD) : ALIGN(4) + { + __user_reloc_start = .; + KEEP(*(.user_reloc*)); + __user_reloc_end = .; + } > RAM + + .text : ALIGN(4) + { + *(.text .text.*); + *(.rodata .rodata.*); + } > RAM + + .data : ALIGN(4) + { + *(.data .data.*); + } > RAM + + .bss : ALIGN(4) + { + *(.bss .bss.*); + *(COMMON); + } > RAM +} From 535a99800d26c968f9a7e3968699b38a6fe92b8a Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 26 Aug 2025 18:49:29 -0600 Subject: [PATCH 13/27] userland app drew to kernel framebuffer!!! --- kernel/src/main.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 715b489..e48c034 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -40,6 +40,7 @@ use embassy_rp::{ spi::{self, Spi}, usb as embassy_rp_usb, }; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -62,6 +63,8 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +static DRIVERS_READY: Signal = Signal::new(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -106,6 +109,8 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { + DRIVERS_READY.wait().await; + defmt::info!("Loading binary"); let binary_data: &[u8] = include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); @@ -153,21 +158,22 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { Timer::after_millis(250).await; - let display_fut = { - let mut config = spi::Config::default(); - config.frequency = 16_000_000; - let spi = Spi::new( - display.spi, - display.clk, - display.mosi, - display.miso, - display.dma1, - display.dma2, - config, - ); - let display = init_display(spi, display.cs, display.data, display.reset).await; - display_handler(display) - }; + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + let spi = Spi::new( + display.spi, + display.clk, + display.mosi, + display.miso, + display.dma1, + display.dma2, + config, + ); + let display = init_display(spi, display.cs, display.data, display.reset).await; + + DRIVERS_READY.signal(()); + + let display_fut = display_handler(display); { let mut config = spi::Config::default(); From 64254831e8c2c58bcb5455ef2444eb630934fcf2 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 26 Aug 2025 20:08:50 -0600 Subject: [PATCH 14/27] can drawiter --- Cargo.lock | 1 + abi/Cargo.toml | 1 + abi/src/lib.rs | 12 ++++++++++-- kernel/src/abi.rs | 25 ++++++++++++------------- user-apps/calculator/Cargo.toml | 2 +- user-apps/calculator/src/main.rs | 29 +++++++++++++++++++---------- 6 files changed, 44 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 130e238..da46856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ name = "abi" version = "0.1.0" dependencies = [ + "embedded-graphics", "shared", ] diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 80cc90d..e7aaf0e 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +embedded-graphics = "0.8.1" shared = { path = "../shared" } diff --git a/abi/src/lib.rs b/abi/src/lib.rs index f97455e..2ed0f97 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,11 +1,16 @@ #![no_std] -use core::ffi::c_void; +pub use embedded_graphics::{ + Pixel, + geometry::Point, + pixelcolor::{Rgb565, RgbColor}, +}; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; // Instead of extern, declare a static pointer in a dedicated section #[unsafe(no_mangle)] #[unsafe(link_section = ".user_reloc")] +#[allow(non_upper_case_globals)] pub static mut call_abi_ptr: usize = 0; // Helper to call it @@ -16,5 +21,8 @@ pub unsafe fn call_abi(call: *const Syscall) { #[repr(C)] pub enum Syscall { - DrawPixel { x: u32, y: u32, color: u16 }, + DrawIter { + pixels: *const Pixel, + len: usize, + }, } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 1f8c3ba..dcc1bed 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -3,6 +3,7 @@ use defmt::info; use embassy_futures::block_on; use embedded_graphics::{ Drawable, + draw_target::DrawTarget, pixelcolor::Rgb565, prelude::{Point, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, @@ -15,19 +16,17 @@ pub extern "C" fn call_abi(call: *const Syscall) { info!("called abi"); let call = unsafe { &*call }; match call { - Syscall::DrawPixel { x, y, color } => { - draw_pixel(*x, *y, *color); + Syscall::DrawIter { pixels, len } => { + // SAFETY: we're trusting the user program here + let slice = unsafe { core::slice::from_raw_parts(*pixels, *len) }; + + let framebuffer = block_on(FRAMEBUFFER.lock()); + framebuffer + .borrow_mut() + .as_mut() + .unwrap() + .draw_iter(slice.iter().copied()) + .unwrap(); } } } - -fn draw_pixel(x: u32, y: u32, color: u16) { - info!("draw pixel abi called"); - let framebuffer = block_on(FRAMEBUFFER.lock()); - Rectangle::new(Point::new(x as i32, y as i32), Size::new(1, 1)) - .draw_styled( - &PrimitiveStyle::with_fill(Rgb565::RED), - *framebuffer.borrow_mut().as_mut().unwrap(), - ) - .unwrap(); -} diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index 13d39be..8b3311f 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -abi = { path ="../../abi" } +abi = { path = "../../abi" } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 3c26e28..3a174be 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use abi::{Syscall, call_abi}; +use abi::{Pixel, Point, Rgb565, RgbColor, Syscall, call_abi}; use core::panic::PanicInfo; #[panic_handler] @@ -11,14 +11,23 @@ fn panic(_info: &PanicInfo) -> ! { #[unsafe(no_mangle)] pub extern "C" fn _start() { - for i in 0..300 { - for o in 0..300 { - let call = Syscall::DrawPixel { - x: i, - y: o, - color: 0, - }; - unsafe { call_abi(&call) }; - } + // Local pixel buffer + let mut pixels = [Pixel(Point { x: 50, y: 50 }, Rgb565::RED); 300]; + for (i, p) in pixels.iter_mut().enumerate() { + *p = Pixel( + Point { + x: i as i32, + y: i as i32, + }, + Rgb565::RED, + ) } + + // Construct syscall with raw pointer + length + let call = Syscall::DrawIter { + pixels: pixels.as_ptr(), // raw pointer + len: pixels.len(), // number of elements + }; + + unsafe { call_abi(&call) }; } From f3c67beb00eee1cf8dd0a3cd01b439b0e87f08bf Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 27 Aug 2025 17:12:51 -0600 Subject: [PATCH 15/27] userlib now implements embedded_graphics::DrawTarget --- Cargo.lock | 12 ++++- abi/Cargo.toml | 1 + abi/src/lib.rs | 90 ++++++++++++++++++++++++-------- abi_sys/Cargo.toml | 8 +++ abi_sys/src/lib.rs | 28 ++++++++++ kernel/Cargo.toml | 2 +- kernel/src/abi.rs | 2 +- user-apps/calculator/Cargo.toml | 1 + user-apps/calculator/src/main.rs | 39 +++++++------- 9 files changed, 141 insertions(+), 42 deletions(-) create mode 100644 abi_sys/Cargo.toml create mode 100644 abi_sys/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index da46856..0f917d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,15 @@ dependencies = [ [[package]] name = "abi" version = "0.1.0" +dependencies = [ + "abi_sys", + "embedded-graphics", + "shared", +] + +[[package]] +name = "abi_sys" +version = "0.1.0" dependencies = [ "embedded-graphics", "shared", @@ -208,6 +217,7 @@ name = "calculator" version = "0.1.0" dependencies = [ "abi", + "embedded-graphics", ] [[package]] @@ -1163,7 +1173,7 @@ dependencies = [ name = "kernel" version = "0.1.0" dependencies = [ - "abi", + "abi_sys", "bitflags 2.9.1", "bt-hci", "cortex-m", diff --git a/abi/Cargo.toml b/abi/Cargo.toml index e7aaf0e..d20ffeb 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] embedded-graphics = "0.8.1" shared = { path = "../shared" } +abi_sys = { path = "../abi_sys" } diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 2ed0f97..c387b4a 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,28 +1,76 @@ #![no_std] -pub use embedded_graphics::{ - Pixel, - geometry::Point, - pixelcolor::{Rgb565, RgbColor}, -}; +use abi_sys::{Syscall, call_abi}; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -// Instead of extern, declare a static pointer in a dedicated section -#[unsafe(no_mangle)] -#[unsafe(link_section = ".user_reloc")] -#[allow(non_upper_case_globals)] -pub static mut call_abi_ptr: usize = 0; +pub mod display { + use crate::{Syscall, call_abi}; + use embedded_graphics::{ + Pixel, + geometry::{Dimensions, Point}, + pixelcolor::{Rgb565, RgbColor}, + prelude::{DrawTarget, Size}, + primitives::Rectangle, + }; -// Helper to call it -pub unsafe fn call_abi(call: *const Syscall) { - let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; - f(call); -} + pub const SCREEN_WIDTH: usize = 320; + pub const SCREEN_HEIGHT: usize = 320; -#[repr(C)] -pub enum Syscall { - DrawIter { - pixels: *const Pixel, - len: usize, - }, + pub type Pixel565 = Pixel; + + pub struct Display; + + impl Display { + fn syscall_draw(&self, pixels: &[Pixel565]) { + let syscall = Syscall::DrawIter { + pixels: pixels.as_ptr(), + len: pixels.len(), + }; + unsafe { + call_abi(&syscall); + } + } + } + + impl Dimensions for Display { + fn bounding_box(&self) -> Rectangle { + Rectangle { + top_left: Point { x: 0, y: 0 }, + size: Size { + width: SCREEN_WIDTH as u32, + height: SCREEN_HEIGHT as u32, + }, + } + } + } + + impl DrawTarget for Display { + type Color = Rgb565; + type Error = (); + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + const BUF_SIZE: usize = 1024; // tune this for performance + let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; + + let mut count = 0; + for p in pixels { + buf[count] = p; + count += 1; + + if count == BUF_SIZE { + self.syscall_draw(&buf[..count]); + count = 0; + } + } + + if count > 0 { + self.syscall_draw(&buf[..count]); + } + + Ok(()) + } + } } diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml new file mode 100644 index 0000000..e460dea --- /dev/null +++ b/abi_sys/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "abi_sys" +version = "0.1.0" +edition = "2024" + +[dependencies] +embedded-graphics = "0.8.1" +shared = { path = "../shared" } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs new file mode 100644 index 0000000..2ed0f97 --- /dev/null +++ b/abi_sys/src/lib.rs @@ -0,0 +1,28 @@ +#![no_std] + +pub use embedded_graphics::{ + Pixel, + geometry::Point, + pixelcolor::{Rgb565, RgbColor}, +}; +use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; + +// Instead of extern, declare a static pointer in a dedicated section +#[unsafe(no_mangle)] +#[unsafe(link_section = ".user_reloc")] +#[allow(non_upper_case_globals)] +pub static mut call_abi_ptr: usize = 0; + +// Helper to call it +pub unsafe fn call_abi(call: *const Syscall) { + let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; + f(call); +} + +#[repr(C)] +pub enum Syscall { + DrawIter { + pixels: *const Pixel, + len: usize, + }, +} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 2891c66..417f175 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -87,4 +87,4 @@ talc = "4.4.3" spin = "0.10.0" shared = { path = "../shared" } -abi = { path = "../abi" } +abi_sys = { path = "../abi_sys" } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index dcc1bed..34c2fb5 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,4 +1,4 @@ -use abi::Syscall; +use abi_sys::Syscall; use defmt::info; use embassy_futures::block_on; use embedded_graphics::{ diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index 8b3311f..bcb1288 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -5,3 +5,4 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } +embedded-graphics = "0.8.1" diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 3a174be..aa5efc7 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,8 +1,16 @@ #![no_std] #![no_main] -use abi::{Pixel, Point, Rgb565, RgbColor, Syscall, call_abi}; +use abi::display::Display; use core::panic::PanicInfo; +use embedded_graphics::{ + Drawable, + geometry::{Dimensions, Point}, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::RgbColor, + text::{Alignment, Text}, +}; #[panic_handler] fn panic(_info: &PanicInfo) -> ! { @@ -11,23 +19,18 @@ fn panic(_info: &PanicInfo) -> ! { #[unsafe(no_mangle)] pub extern "C" fn _start() { - // Local pixel buffer - let mut pixels = [Pixel(Point { x: 50, y: 50 }, Rgb565::RED); 300]; - for (i, p) in pixels.iter_mut().enumerate() { - *p = Pixel( - Point { - x: i as i32, - y: i as i32, - }, - Rgb565::RED, - ) - } + let mut display = Display; - // Construct syscall with raw pointer + length - let call = Syscall::DrawIter { - pixels: pixels.as_ptr(), // raw pointer - len: pixels.len(), // number of elements - }; + let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); - unsafe { call_abi(&call) }; + // Draw centered text. + let text = "embedded-graphics"; + Text::with_alignment( + text, + display.bounding_box().center() + Point::new(0, 15), + character_style, + Alignment::Center, + ) + .draw(&mut display) + .unwrap(); } From 1bbd988ef74fcaab57a0a280c4974a70d911a006 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 29 Aug 2025 15:05:19 -0600 Subject: [PATCH 16/27] print syscall --- abi/src/lib.rs | 10 ++++++++++ abi_sys/src/lib.rs | 4 ++++ kernel/src/abi.rs | 10 ++++++++++ user-apps/calculator/src/main.rs | 3 ++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index c387b4a..86547b9 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,6 +3,16 @@ use abi_sys::{Syscall, call_abi}; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +pub fn print(msg: &str) { + let syscall = Syscall::Print { + msg: msg.as_ptr(), + len: msg.len(), + }; + unsafe { + call_abi(&syscall); + } +} + pub mod display { use crate::{Syscall, call_abi}; use embedded_graphics::{ diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2ed0f97..b0586ca 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -25,4 +25,8 @@ pub enum Syscall { pixels: *const Pixel, len: usize, }, + Print { + msg: *const u8, + len: usize, + }, } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 34c2fb5..8bf87f1 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -28,5 +28,15 @@ pub extern "C" fn call_abi(call: *const Syscall) { .draw_iter(slice.iter().copied()) .unwrap(); } + Syscall::Print { msg, len } => { + // SAFETY: we're trusting the user program here + let slice = unsafe { core::slice::from_raw_parts(*msg, *len) }; + + if let Ok(str) = str::from_utf8(slice) { + defmt::info!("{:?}", str); + } else { + defmt::error!("Failed to parse user print str") + } + } } } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index aa5efc7..9f2674e 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use abi::display::Display; +use abi::{display::Display, print}; use core::panic::PanicInfo; use embedded_graphics::{ Drawable, @@ -19,6 +19,7 @@ fn panic(_info: &PanicInfo) -> ! { #[unsafe(no_mangle)] pub extern "C" fn _start() { + print("Starting Calculator app"); let mut display = Display; let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); From 5d0a3608d17c28757b7b78534e43e7bd5cd21fe8 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 31 Aug 2025 22:41:09 -0600 Subject: [PATCH 17/27] WIP keyboard syscall --- Cargo.lock | 39 ++++++++++++++----- abi/Cargo.toml | 3 ++ abi/src/lib.rs | 40 +++++++------------- abi_sys/src/lib.rs | 64 +++++++++++++++++++++++--------- kernel/src/abi.rs | 56 ++++++++++++++-------------- kernel/src/elf.rs | 36 ++++++++++++------ kernel/src/main.rs | 34 +++++++++++++++-- user-apps/calculator/src/main.rs | 51 +++++++++++++++++-------- 8 files changed, 210 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f917d9..6551289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,8 +17,11 @@ name = "abi" version = "0.1.0" dependencies = [ "abi_sys", + "embassy-time 0.5.0", "embedded-graphics", "shared", + "spin", + "talc", ] [[package]] @@ -186,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f377753756ec12e76b52d2dd657437be0448cc9736402ffadd0b8b8b9602c8a1" dependencies = [ "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-io", "embedded-io-async", "futures-intrusive", @@ -320,7 +323,7 @@ dependencies = [ "embassy-futures", "embassy-net-driver-channel", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-hal 1.0.0", "embedded-io-async", "futures", @@ -494,7 +497,7 @@ dependencies = [ "embassy-futures", "embassy-hal-internal 0.3.0", "embassy-sync 0.7.0", - "embassy-time", + "embassy-time 0.4.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -588,7 +591,7 @@ dependencies = [ "embassy-futures", "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embassy-usb-driver", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -626,7 +629,7 @@ dependencies = [ "embassy-futures", "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embassy-time-driver", "embassy-time-queue-utils", "embassy-usb-driver", @@ -696,10 +699,26 @@ dependencies = [ ] [[package]] -name = "embassy-time-driver" -version = "0.2.0" +name = "embassy-time" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" dependencies = [ "document-features", ] @@ -1187,7 +1206,7 @@ dependencies = [ "embassy-futures", "embassy-rp 0.4.0", "embassy-sync 0.7.0", - "embassy-time", + "embassy-time 0.4.0", "embassy-usb", "embedded-graphics", "embedded-hal 0.2.7", @@ -2086,7 +2105,7 @@ dependencies = [ "bt-hci", "embassy-futures", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-io", "futures", "heapless", diff --git a/abi/Cargo.toml b/abi/Cargo.toml index d20ffeb..8a9af49 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -7,3 +7,6 @@ edition = "2024" embedded-graphics = "0.8.1" shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } +talc = "4.4.3" +spin = "0.10.0" +embassy-time = "0.5.0" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 86547b9..ec5b528 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,20 +1,20 @@ #![no_std] -use abi_sys::{Syscall, call_abi}; -use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +use abi_sys::draw_iter; +pub use abi_sys::{get_key, print}; +pub use embassy_time; +pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +use talc::*; -pub fn print(msg: &str) { - let syscall = Syscall::Print { - msg: msg.as_ptr(), - len: msg.len(), - }; - unsafe { - call_abi(&syscall); - } -} +static mut ARENA: [u8; 10000] = [0; 10000]; + +#[global_allocator] +static ALLOCATOR: Talck, ClaimOnOom> = + Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) + .lock(); pub mod display { - use crate::{Syscall, call_abi}; + use crate::draw_iter; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, @@ -30,18 +30,6 @@ pub mod display { pub struct Display; - impl Display { - fn syscall_draw(&self, pixels: &[Pixel565]) { - let syscall = Syscall::DrawIter { - pixels: pixels.as_ptr(), - len: pixels.len(), - }; - unsafe { - call_abi(&syscall); - } - } - } - impl Dimensions for Display { fn bounding_box(&self) -> Rectangle { Rectangle { @@ -71,13 +59,13 @@ pub mod display { count += 1; if count == BUF_SIZE { - self.syscall_draw(&buf[..count]); + draw_iter(&buf[..count]); count = 0; } } if count > 0 { - self.syscall_draw(&buf[..count]); + draw_iter(&buf[..count]); } Ok(()) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index b0586ca..10dea33 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,5 +1,9 @@ #![no_std] +extern crate alloc; +use alloc::boxed::Box; + +use core::pin::Pin; pub use embedded_graphics::{ Pixel, geometry::Point, @@ -7,26 +11,50 @@ pub use embedded_graphics::{ }; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -// Instead of extern, declare a static pointer in a dedicated section +pub type EntryFn = fn() -> Pin>>; + #[unsafe(no_mangle)] -#[unsafe(link_section = ".user_reloc")] -#[allow(non_upper_case_globals)] -pub static mut call_abi_ptr: usize = 0; +#[unsafe(link_section = ".userapp")] +pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; -// Helper to call it -pub unsafe fn call_abi(call: *const Syscall) { - let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; - f(call); +#[repr(usize)] +#[derive(Clone, Copy)] +pub enum CallAbiTable { + Print = 0, + DrawIter = 1, + GetKey = 2, } -#[repr(C)] -pub enum Syscall { - DrawIter { - pixels: *const Pixel, - len: usize, - }, - Print { - msg: *const u8, - len: usize, - }, +impl CallAbiTable { + pub const COUNT: usize = 3; +} + +pub type PrintAbi = extern "Rust" fn(msg: &str); + +pub fn print(msg: &str) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; + let f: PrintAbi = core::mem::transmute(ptr); + f(msg); + } +} + +pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); + +pub fn draw_iter(pixels: &[Pixel]) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]; + let f: DrawIterAbi = core::mem::transmute(ptr); + f(pixels); + } +} + +pub type GetKeyAbi = extern "Rust" fn() -> Option; + +pub fn get_key() -> Option { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::GetKey as usize]; + let f: GetKeyAbi = core::mem::transmute(ptr); + f() + } } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 8bf87f1..4463097 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,4 +1,7 @@ -use abi_sys::Syscall; +use core::pin::Pin; + +use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi}; +use alloc::boxed::Box; use defmt::info; use embassy_futures::block_on; use embedded_graphics::{ @@ -8,35 +11,30 @@ use embedded_graphics::{ prelude::{Point, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, }; +use shared::keyboard::KeyEvent; -use crate::display::FRAMEBUFFER; +use crate::{KEY_CACHE, display::FRAMEBUFFER}; -#[allow(unused)] -pub extern "C" fn call_abi(call: *const Syscall) { - info!("called abi"); - let call = unsafe { &*call }; - match call { - Syscall::DrawIter { pixels, len } => { - // SAFETY: we're trusting the user program here - let slice = unsafe { core::slice::from_raw_parts(*pixels, *len) }; +// ensure the abi and the kernel fn signatures are the same +const _: PrintAbi = print; +const _: DrawIterAbi = draw_iter; +const _: GetKeyAbi = get_key; - let framebuffer = block_on(FRAMEBUFFER.lock()); - framebuffer - .borrow_mut() - .as_mut() - .unwrap() - .draw_iter(slice.iter().copied()) - .unwrap(); - } - Syscall::Print { msg, len } => { - // SAFETY: we're trusting the user program here - let slice = unsafe { core::slice::from_raw_parts(*msg, *len) }; - - if let Ok(str) = str::from_utf8(slice) { - defmt::info!("{:?}", str); - } else { - defmt::error!("Failed to parse user print str") - } - } - } +pub extern "Rust" fn print(msg: &str) { + defmt::info!("{:?}", msg); +} + +pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { + let framebuffer = block_on(FRAMEBUFFER.lock()); + framebuffer + .borrow_mut() + .as_mut() + .unwrap() + .draw_iter(pixels.iter().copied()) + .unwrap(); +} + +pub extern "Rust" fn get_key() -> Option { + defmt::info!("get key called"); + unsafe { KEY_CACHE.dequeue() } } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index a553aa4..3a04ba1 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,5 +1,15 @@ #![allow(static_mut_refs)] -use core::{alloc::Layout, ffi::c_void, ptr::NonNull, slice::from_raw_parts_mut}; +use crate::abi; +use abi_sys::{CallAbiTable, EntryFn}; +use alloc::boxed::Box; +use core::{ + alloc::Layout, + ffi::c_void, + pin::Pin, + ptr::NonNull, + slice::from_raw_parts_mut, + task::{Context, Poll}, +}; use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; // userland ram region defined in memory.x @@ -8,8 +18,6 @@ unsafe extern "C" { static __userapp_end__: u8; } -type EntryFn = extern "C" fn(); - pub unsafe fn load_binary(bytes: &[u8]) -> Result { let elf = Elf::parse(&bytes).expect("Failed to parse ELF"); @@ -55,16 +63,22 @@ pub unsafe fn load_binary(bytes: &[u8]) -> Result { let call_abi_sym = elf .syms .iter() - .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "call_abi_ptr") - .expect("call_abi_ptr not found"); + .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "CALL_ABI_TABLE") + .expect("syscall table not found"); - // Virtual address inside user RAM - let addr = call_abi_sym.st_value as *mut usize; + let table_base = call_abi_sym.st_value as *mut usize; - // Patch it - unsafe { - core::ptr::write(addr, crate::abi::call_abi as usize); + let entries: &[(CallAbiTable, usize)] = &[ + (CallAbiTable::Print, abi::print as usize), + (CallAbiTable::DrawIter, abi::draw_iter as usize), + (CallAbiTable::GetKey, abi::get_key as usize), + ]; + assert!(entries.len() == CallAbiTable::COUNT); + + for &(abi_idx, func_ptr) in entries { + unsafe { + table_base.add(abi_idx as usize).write(func_ptr); + } } - Ok(unsafe { core::mem::transmute(elf.entry as u32) }) } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index e48c034..57b696e 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,7 +1,11 @@ #![feature(impl_trait_in_assoc_type)] +#![feature(type_alias_impl_trait)] #![feature(ascii_char)] #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] +#![allow(static_mut_refs)] + +extern crate alloc; mod abi; mod display; @@ -23,12 +27,13 @@ use crate::{ storage::{SDCARD, SdCard}, usb::{ENABLE_SCSI, usb_handler}, }; +use alloc::vec::Vec; use {defmt_rtt as _, panic_probe as _}; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; -use embassy_futures::join::join; +use embassy_futures::join::{join, join3}; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, @@ -40,10 +45,15 @@ use embassy_rp::{ spi::{self, Spi}, usb as embassy_rp_usb, }; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; +use embassy_sync::{ + blocking_mutex::{Mutex, raw::CriticalSectionRawMutex}, + signal::Signal, +}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; +use heapless::spsc::Queue; +use shared::keyboard::KeyEvent; use static_cell::StaticCell; use talc::*; @@ -118,7 +128,7 @@ async fn userland_task() { defmt::info!("Running binary"); let entry = unsafe { load_binary(binary_data).unwrap() }; - entry(); + entry().await; } struct Display { @@ -194,5 +204,21 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { let usb_fut = usb_handler(usb); ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); - join(usb_fut, display_fut).await; + join3(usb_fut, display_fut, async { + loop { + Timer::after_millis(100).await; + get_keys().await + } + }) + .await; +} + +static mut KEY_CACHE: Queue = Queue::new(); + +async fn get_keys() { + if let Some(event) = read_keyboard_fifo().await { + unsafe { + let _ = KEY_CACHE.enqueue(event); + } + } } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 9f2674e..337533c 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -1,8 +1,10 @@ #![no_std] #![no_main] -use abi::{display::Display, print}; -use core::panic::PanicInfo; +extern crate alloc; +use abi::{KeyCode, display::Display, embassy_time, get_key, print}; +use alloc::{boxed::Box, string::String, vec}; +use core::{panic::PanicInfo, pin::Pin}; use embedded_graphics::{ Drawable, geometry::{Dimensions, Point}, @@ -17,21 +19,40 @@ fn panic(_info: &PanicInfo) -> ! { loop {} } -#[unsafe(no_mangle)] -pub extern "C" fn _start() { - print("Starting Calculator app"); +pub async fn main() { + print("Starting Async Calculator app"); let mut display = Display; let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); - // Draw centered text. - let text = "embedded-graphics"; - Text::with_alignment( - text, - display.bounding_box().center() + Point::new(0, 15), - character_style, - Alignment::Center, - ) - .draw(&mut display) - .unwrap(); + let mut text = vec!['H', 'E', 'L', 'L', 'O']; + + loop { + Text::with_alignment( + &text.iter().cloned().collect::(), + display.bounding_box().center() + Point::new(0, 15), + character_style, + Alignment::Center, + ) + .draw(&mut display) + .unwrap(); + + if let Some(event) = get_key() { + print("User got event"); + match event.key { + KeyCode::Char(ch) => { + text.push(ch); + } + KeyCode::Backspace => { + text.pop(); + } + _ => (), + } + } + } +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() -> Pin>> { + Box::pin(async { main().await }) } From ec25ee86014d021d0838e8c8addf41bf1d604d46 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 4 Sep 2025 18:33:43 -0600 Subject: [PATCH 18/27] fix framebuffer crash --- kernel/src/abi.rs | 22 ++++++++++++++-------- kernel/src/display.rs | 30 +++++++++++++----------------- kernel/src/main.rs | 3 +-- kernel/src/ui.rs | 19 ++++++------------- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 4463097..889a0f3 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -24,17 +24,23 @@ pub extern "Rust" fn print(msg: &str) { defmt::info!("{:?}", msg); } +// TODO: maybe return result pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { - let framebuffer = block_on(FRAMEBUFFER.lock()); - framebuffer - .borrow_mut() - .as_mut() - .unwrap() - .draw_iter(pixels.iter().copied()) - .unwrap(); + for _ in 0..10 { + if let Some(mut framebuffer) = FRAMEBUFFER.try_lock().ok() { + for _ in 0..10 { + // kernel takes() framebuffer + if let Some(framebuffer) = framebuffer.as_mut() { + framebuffer.draw_iter(pixels.iter().copied()).unwrap(); + } + break; + } + break; + } + cortex_m::asm::nop(); + } } pub extern "Rust" fn get_key() -> Option { - defmt::info!("get key called"); unsafe { KEY_CACHE.dequeue() } } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 388f9bf..7f27811 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,10 +1,9 @@ -use core::cell::RefCell; use embassy_rp::{ gpio::{Level, Output}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex}; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::{FrameBuffer, ST7365P}; @@ -22,8 +21,7 @@ pub const SCREEN_HEIGHT: usize = 320; type FB = FrameBuffer; static FRAMEBUFFER_CELL: StaticCell = StaticCell::new(); -pub static FRAMEBUFFER: Mutex>> = - Mutex::new(RefCell::new(None)); +pub static FRAMEBUFFER: Mutex> = Mutex::new(None); pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -45,25 +43,23 @@ pub async fn init_display( display.set_custom_orientation(0x40).await.unwrap(); framebuffer.draw(&mut display).await.unwrap(); display.set_on().await.unwrap(); - FRAMEBUFFER - .lock() - .await - .swap(&RefCell::new(Some(framebuffer))); + FRAMEBUFFER.lock().await.replace(framebuffer); display } pub async fn display_handler(mut display: DISPLAY) { loop { - FRAMEBUFFER - .lock() - .await - .borrow_mut() - .as_mut() - .unwrap() - .partial_draw_batched(&mut display) - .await - .unwrap(); + let fb: &mut FB = { + let mut guard = FRAMEBUFFER.lock().await; + guard.take().unwrap() // take ownership + }; // guard dropped + + fb.partial_draw_batched(&mut display).await.unwrap(); + + // Put it back + FRAMEBUFFER.lock().await.replace(fb); + Timer::after_millis(32).await; // 30 fps } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 57b696e..27451df 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -181,8 +181,6 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { ); let display = init_display(spi, display.cs, display.data, display.reset).await; - DRIVERS_READY.signal(()); - let display_fut = display_handler(display); { @@ -204,6 +202,7 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { let usb_fut = usb_handler(usb); ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); + DRIVERS_READY.signal(()); join3(usb_fut, display_fut, async { loop { Timer::after_millis(100).await; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index acb80fd..18c8d1a 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -51,6 +51,9 @@ impl UI UI UI Date: Thu, 4 Sep 2025 19:32:47 -0600 Subject: [PATCH 19/27] WIP Binary Ui --- kernel/src/main.rs | 41 +++++++++++++---------- kernel/src/ui.rs | 83 +++++++++++++++++++++++++--------------------- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 27451df..1f19627 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -25,6 +25,7 @@ use crate::{ keyboard::{KeyCode, KeyState, read_keyboard_fifo}, }, storage::{SDCARD, SdCard}, + ui::ui_handler, usb::{ENABLE_SCSI, usb_handler}, }; use alloc::vec::Vec; @@ -33,7 +34,7 @@ use {defmt_rtt as _, panic_probe as _}; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; -use embassy_futures::join::{join, join3}; +use embassy_futures::join::{join, join3, join4}; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, @@ -45,10 +46,7 @@ use embassy_rp::{ spi::{self, Spi}, usb as embassy_rp_usb, }; -use embassy_sync::{ - blocking_mutex::{Mutex, raw::CriticalSectionRawMutex}, - signal::Signal, -}; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -73,7 +71,12 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); -static DRIVERS_READY: Signal = Signal::new(); +static TASK_STATE: Mutex = Mutex::new(TaskState::Ui); + +enum TaskState { + Ui, + Kernel, +} #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -119,16 +122,16 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - DRIVERS_READY.wait().await; + // DRIVERS_READY.wait().await; - defmt::info!("Loading binary"); - let binary_data: &[u8] = - include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); + // defmt::info!("Loading binary"); + // let binary_data: &[u8] = + // include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); - defmt::info!("Running binary"); - let entry = unsafe { load_binary(binary_data).unwrap() }; + // defmt::info!("Running binary"); + // let entry = unsafe { load_binary(binary_data).unwrap() }; - entry().await; + // entry().await; } struct Display { @@ -183,6 +186,8 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { let display_fut = display_handler(display); + let ui_fut = ui_handler(); + { let mut config = spi::Config::default(); config.frequency = 400_000; @@ -201,15 +206,15 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { let usb = embassy_rp_usb::Driver::new(usb, Irqs); let usb_fut = usb_handler(usb); - ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); - DRIVERS_READY.signal(()); - join3(usb_fut, display_fut, async { + let key_abi_fut = async { loop { Timer::after_millis(100).await; get_keys().await } - }) - .await; + }; + + ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); + join4(usb_fut, display_fut, key_abi_fut, ui_fut).await; } static mut KEY_CACHE: Queue = Queue::new(); diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 18c8d1a..d90f7a8 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,7 +1,10 @@ use crate::{ + TASK_STATE, TaskState, display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, format, + peripherals::keyboard, }; +use alloc::{string::String, vec::Vec}; use core::fmt::Debug; use defmt::info; use embassy_rp::{ @@ -9,6 +12,11 @@ use embassy_rp::{ peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; +use embassy_sync::{ + blocking_mutex::raw::{CriticalSectionRawMutex, ThreadModeRawMutex}, + mutex::Mutex, + signal::Signal, +}; use embassy_time::{Delay, Timer}; use embedded_graphics::{ Drawable, @@ -30,38 +38,38 @@ use embedded_layout::{ object_chain::Chain, prelude::*, }; -use heapless::{String, Vec}; +use shared::keyboard::KeyCode; -pub struct UI { - pub selections_list: SelectionList, -} +static SELECTIONS: Mutex = + Mutex::new(SelectionList::new(Vec::new())); -impl UI { - pub fn new() -> Self { - Self { - selections_list: SelectionList::new(Vec::new()), +pub async fn ui_handler() { + loop { + let state = TASK_STATE.lock().await; + if let TaskState::Ui = *state { + let mut selections = SELECTIONS.lock().await; + if let Some(event) = keyboard::read_keyboard_fifo().await { + match event.key { + KeyCode::JoyUp => selections.up(), + KeyCode::JoyDown => selections.down(), + KeyCode::Enter | KeyCode::JoyRight => (), + _ => (), + } + } + + draw_selection().await; } } +} - pub async fn draw>(&mut self) - where - ::Error: Debug, - { - self.draw_selection().await; - } - - async fn draw_selection(&mut self) { - let mut fb_lock = FRAMEBUFFER.lock().await; - let fb = fb_lock.as_mut().unwrap(); - +async fn draw_selection() { + let mut fb_lock = FRAMEBUFFER.lock().await; + if let Some(fb) = fb_lock.as_mut() { + info!("UIINg"); let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); - let selection = Rectangle::new( - Point::new(0, 0), - Size::new(SCREEN_WIDTH as u32 - 1, SCREEN_HEIGHT as u32 - 1), - ); - - let mut file_names = self.selections_list.selections.iter(); + let guard = SELECTIONS.lock().await; + let mut file_names = guard.selections.iter(); let Some(first) = file_names.next() else { Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory", @@ -72,24 +80,25 @@ impl UI { +pub struct SelectionList { current_selection: u16, - selections: Vec, MAX_SELECTION>, + selections: Vec, } -impl - SelectionList -{ - pub fn new(selections: Vec, MAX_SELECTION>) -> Self { +impl SelectionList { + pub const fn new(selections: Vec) -> Self { Self { selections, current_selection: 0, From e2ff3740f3c3151241a1e5eedeb794e01761d78e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 5 Sep 2025 14:04:35 -0600 Subject: [PATCH 20/27] working on binary selection --- Cargo.lock | 18 +++++++++ kernel/Cargo.toml | 1 + kernel/src/main.rs | 30 +++++++++++--- kernel/src/storage.rs | 44 +++++++++++++++++---- kernel/src/ui.rs | 92 ++++++++++++++++++++++++++++++------------- 5 files changed, 145 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6551289..737c75e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,6 +893,17 @@ dependencies = [ "embedded-storage", ] +[[package]] +name = "embedded-text" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005680edc0d075af5e02d5788ca291737bd9aba7fc404ae031cc9dfa715e5f7d" +dependencies = [ + "az", + "embedded-graphics", + "object-chain", +] + [[package]] name = "ena" version = "0.14.3" @@ -1214,6 +1225,7 @@ dependencies = [ "embedded-hal-bus", "embedded-layout", "embedded-sdmmc", + "embedded-text", "goblin", "heapless", "num_enum 0.7.4", @@ -1417,6 +1429,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "object-chain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41af26158b0f5530f7b79955006c2727cd23d0d8e7c3109dc316db0a919784dd" + [[package]] name = "once_cell" version = "1.21.3" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 417f175..4843e70 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -72,6 +72,7 @@ defmt-rtt = "0.4.2" embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch embedded-graphics = { version = "0.8.1" } +embedded-text = "0.7.2" embedded-layout = "0.4.2" static_cell = "2.1.1" diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 1f19627..9d1e6ba 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -17,6 +17,8 @@ mod ui; mod usb; mod utils; +use core::sync::atomic::Ordering; + use crate::{ display::{display_handler, init_display}, elf::load_binary, @@ -25,8 +27,8 @@ use crate::{ keyboard::{KeyCode, KeyState, read_keyboard_fifo}, }, storage::{SDCARD, SdCard}, - ui::ui_handler, - usb::{ENABLE_SCSI, usb_handler}, + ui::{SELECTIONS, ui_handler}, + usb::usb_handler, }; use alloc::vec::Vec; @@ -34,7 +36,7 @@ use {defmt_rtt as _, panic_probe as _}; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; -use embassy_futures::join::{join, join3, join4}; +use embassy_futures::join::{join, join3, join4, join5}; use embassy_rp::{ gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, @@ -188,6 +190,25 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { let ui_fut = ui_handler(); + let binary_search_fut = async { + loop { + { + let mut guard = SDCARD.get().lock().await; + + if let Some(sd) = guard.as_mut() { + let files = sd.list_files_by_extension(".bin").unwrap(); + let mut select = SELECTIONS.lock().await; + + if select.selections != files { + select.selections = files; + select.reset(); + } + } + } + Timer::after_secs(5).await; + } + }; + { let mut config = spi::Config::default(); config.frequency = 400_000; @@ -213,8 +234,7 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { } }; - ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); - join4(usb_fut, display_fut, key_abi_fut, ui_fut).await; + join4(display_fut, ui_fut, binary_search_fut, key_abi_fut).await; } static mut KEY_CACHE: Queue = Queue::new(); diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index ea698b0..42b1920 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -1,3 +1,5 @@ +use alloc::{string::String, vec::Vec}; +use core::str::FromStr; use embassy_rp::gpio::{Input, Output}; use embassy_rp::peripherals::SPI0; use embassy_rp::spi::{Blocking, Spi}; @@ -6,6 +8,7 @@ use embassy_sync::lazy_lock::LazyLock; use embassy_sync::mutex::Mutex; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::LfnBuffer; use embedded_sdmmc::{ Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, @@ -57,13 +60,6 @@ impl SdCard { self.det.is_low() } - pub fn open_volume(&mut self) -> Result, ()> { - if self.is_attached() { - return Ok(self.volume_mgr.open_volume(VolumeIdx(0)).map_err(|_| ())?); - } - Err(()) - } - pub fn size(&self) -> u64 { let mut result = 0; @@ -103,4 +99,38 @@ impl SdCard { }); res.map_err(|_| ()) } + + fn access_root_dir(&mut self, mut access: impl FnMut(Dir)) { + let volume0 = self.volume_mgr.open_volume(VolumeIdx(0)).unwrap(); + let root_dir = volume0.open_root_dir().unwrap(); + + access(root_dir); + } + + /// Returns a Vec of file names (long format) that match the given extension (e.g., "BIN") + pub fn list_files_by_extension(&mut self, ext: &str) -> Result, ()> { + let mut result = Vec::new(); + + // Only proceed if card is inserted + if !self.is_attached() { + return Ok(result); + } + + let mut lfn_storage = [0; 50]; + let mut lfn_buffer = LfnBuffer::new(&mut lfn_storage); + + self.access_root_dir(|dir| { + dir.iterate_dir_lfn(&mut lfn_buffer, |_entry, name| { + if let Some(name) = name { + let name = String::from_str(name).unwrap(); + if name.contains(ext) { + result.push(name); + } + } + }) + .unwrap() + }); + + Ok(result) + } } diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index d90f7a8..ab8bb6a 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -3,9 +3,10 @@ use crate::{ display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, format, peripherals::keyboard, + usb::RESTART_USB, }; use alloc::{string::String, vec::Vec}; -use core::fmt::Debug; +use core::{fmt::Debug, str::FromStr, sync::atomic::Ordering}; use defmt::info; use embassy_rp::{ gpio::{Level, Output}, @@ -23,10 +24,10 @@ use embedded_graphics::{ draw_target::DrawTarget, mono_font::{ MonoTextStyle, - ascii::{FONT_6X10, FONT_9X15, FONT_10X20}, + ascii::{FONT_6X9, FONT_6X10, FONT_9X15, FONT_10X20}, }, pixelcolor::Rgb565, - prelude::{Dimensions, Point, RgbColor, Size}, + prelude::{Dimensions, Point, Primitive, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, text::Text, }; @@ -38,22 +39,29 @@ use embedded_layout::{ object_chain::Chain, prelude::*, }; -use shared::keyboard::KeyCode; +use embedded_text::TextBox; +use shared::keyboard::{KeyCode, KeyState}; -static SELECTIONS: Mutex = +pub static SELECTIONS: Mutex = Mutex::new(SelectionList::new(Vec::new())); pub async fn ui_handler() { loop { - let state = TASK_STATE.lock().await; - if let TaskState::Ui = *state { - let mut selections = SELECTIONS.lock().await; + if let TaskState::Ui = *TASK_STATE.lock().await { if let Some(event) = keyboard::read_keyboard_fifo().await { - match event.key { - KeyCode::JoyUp => selections.up(), - KeyCode::JoyDown => selections.down(), - KeyCode::Enter | KeyCode::JoyRight => (), - _ => (), + if let KeyState::Pressed = event.state { + match event.key { + KeyCode::JoyUp => { + let mut selections = SELECTIONS.lock().await; + selections.up(); + } + KeyCode::JoyDown => { + let mut selections = SELECTIONS.lock().await; + selections.down(); + } + KeyCode::Enter | KeyCode::JoyRight => (), + _ => (), + } } } @@ -63,38 +71,62 @@ pub async fn ui_handler() { } async fn draw_selection() { + let file_names: Vec = { + let guard = SELECTIONS.lock().await; + guard.selections.clone() + }; + let mut fb_lock = FRAMEBUFFER.lock().await; if let Some(fb) = fb_lock.as_mut() { - info!("UIINg"); let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + let display_area = fb.bounding_box(); - let guard = SELECTIONS.lock().await; - let mut file_names = guard.selections.iter(); + const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; + let no_bins = String::from_str(NO_BINS).unwrap(); - let Some(first) = file_names.next() else { - Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory", + if file_names.is_empty() { + TextBox::new( + &no_bins, + Rectangle::new( + Point::new(25, 25), + Size::new(display_area.size.width - 50, display_area.size.width - 50), + ), + text_style, + ) + .draw(*fb) + .unwrap(); + } else { + let mut file_names = file_names.iter(); + let Some(first) = file_names.next() else { + Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory", Point::zero(), text_style).draw(*fb).unwrap(); - return; - }; + return; + }; - let chain = Chain::new(Text::new(first, Point::zero(), text_style)); + let chain = Chain::new(Text::new(first, Point::zero(), text_style)); + + // for _ in 0..file_names.len() { + // let chain = chain.append(Text::new( + // file_names.next().unwrap(), + // Point::zero(), + // text_style, + // )); + // } - for _ in 0..10 { LinearLayout::vertical(chain) .with_alignment(horizontal::Center) .arrange() - .align_to(&fb.bounding_box(), horizontal::Center, vertical::Center) + .align_to(&display_area, horizontal::Center, vertical::Center) .draw(*fb) .unwrap(); - break; - } + }; } } pub struct SelectionList { current_selection: u16, - selections: Vec, + pub selections: Vec, } impl SelectionList { @@ -105,13 +137,17 @@ impl SelectionList { } } - pub fn down(&mut self) { + pub fn reset(&mut self) { + self.current_selection = 1 + } + + fn down(&mut self) { if self.current_selection + 1 < self.selections.len() as u16 { self.current_selection += 1 } } - pub fn up(&mut self) { + fn up(&mut self) { if self.current_selection > self.selections.len() as u16 { self.current_selection -= 1 } From 8dad3ce6bb21c4e19548e3a21e85c5a6d30c1141 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 5 Sep 2025 17:27:15 -0600 Subject: [PATCH 21/27] wip dynamic loading --- Cargo.toml | 11 ++++++++++- justfile | 2 +- kernel/src/elf.rs | 39 +++++++++++++++++++++++++++++++++++++-- kernel/src/main.rs | 34 +++++++++++++++++++++++----------- kernel/src/storage.rs | 19 ++++++++++++++----- kernel/src/ui.rs | 33 +++++++++++++++++++++++++-------- 6 files changed, 110 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e58fa93..067cbbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,16 @@ resolver = "3" members = ["kernel", "abi", "shared", "user-apps/calculator"] [profile.release] -debug = 2 +debug = true +opt-level = "z" +lto = true +codegen-units = 1 + +[profile.release-binary] +inherits = "release" +lto = true +debug = false +opt-level = "s" [profile.dev] lto = true diff --git a/justfile b/justfile index 79061bc..6b97834 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ kernel: calculator cargo run --bin kernel calculator: - RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --release + RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 3a04ba1..64cb28b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,7 +1,7 @@ #![allow(static_mut_refs)] -use crate::abi; +use crate::{abi, storage::SDCARD}; use abi_sys::{CallAbiTable, EntryFn}; -use alloc::boxed::Box; +use alloc::{boxed::Box, vec::Vec}; use core::{ alloc::Layout, ffi::c_void, @@ -10,8 +10,43 @@ use core::{ slice::from_raw_parts_mut, task::{Context, Poll}, }; +use embedded_sdmmc::ShortFileName; use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; +pub async fn read_binary(name: &ShortFileName) -> Option> { + let mut guard = SDCARD.get().lock().await; + let sd = guard.as_mut()?; + + let mut buf = Vec::new(); + + defmt::info!("sd closure"); + sd.access_root_dir(|root_dir| { + // Try to open the file directly by name + defmt::info!("trying to open file: {:?}", name); + if let Ok(file) = root_dir.open_file_in_dir(name, embedded_sdmmc::Mode::ReadOnly) { + defmt::info!("opened"); + let mut temp = [0u8; 512]; + + defmt::info!("caching binary"); + loop { + match file.read(&mut temp) { + Ok(n) if n > 0 => buf.extend_from_slice(&temp[..n]), + _ => break, + } + } + + defmt::info!("done"); + let _ = file.close(); + } + }); + + if buf.is_empty() { + return None; + } + + Some(buf) +} + // userland ram region defined in memory.x unsafe extern "C" { static __userapp_start__: u8; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 9d1e6ba..ca2bbce 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -30,6 +30,7 @@ use crate::{ ui::{SELECTIONS, ui_handler}, usb::usb_handler, }; +use abi_sys::EntryFn; use alloc::vec::Vec; use {defmt_rtt as _, panic_probe as _}; @@ -48,7 +49,7 @@ use embassy_rp::{ spi::{self, Spi}, usb as embassy_rp_usb, }; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -66,7 +67,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 10000] = [0; 10000]; +static mut ARENA: [u8; 10 * 1024] = [0; 10 * 1024]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -121,19 +122,30 @@ async fn main(_spawner: Spawner) { executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(display, sd, mcu, p.USB)))); } +// One-slot channel to pass EntryFn from core1 +static BINARY_CH: Channel = Channel::new(); + // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - // DRIVERS_READY.wait().await; + let recv = BINARY_CH.receiver(); + loop { + let entry = recv.receive().await; - // defmt::info!("Loading binary"); - // let binary_data: &[u8] = - // include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); + // disable kernel ui + { + let mut state = TASK_STATE.lock().await; + *state = TaskState::Kernel; + } - // defmt::info!("Running binary"); - // let entry = unsafe { load_binary(binary_data).unwrap() }; + entry().await; - // entry().await; + // enable kernel ui + { + let mut state = TASK_STATE.lock().await; + *state = TaskState::Ui; + } + } } struct Display { @@ -224,8 +236,8 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); }; - let usb = embassy_rp_usb::Driver::new(usb, Irqs); - let usb_fut = usb_handler(usb); + // let usb = embassy_rp_usb::Driver::new(usb, Irqs); + // let usb_fut = usb_handler(usb); let key_abi_fut = async { loop { diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 42b1920..c16a52b 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -8,11 +8,11 @@ use embassy_sync::lazy_lock::LazyLock; use embassy_sync::mutex::Mutex; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; -use embedded_sdmmc::LfnBuffer; use embedded_sdmmc::{ Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, }; +use embedded_sdmmc::{LfnBuffer, ShortFileName}; pub const MAX_DIRS: usize = 4; pub const MAX_FILES: usize = 5; @@ -34,6 +34,12 @@ impl TimeSource for DummyTimeSource { } } +#[derive(Clone, PartialEq)] +pub struct FileName { + pub long_name: String, + pub short_name: ShortFileName, +} + pub struct SdCard { det: Input<'static>, volume_mgr: VolMgr, @@ -100,7 +106,7 @@ impl SdCard { res.map_err(|_| ()) } - fn access_root_dir(&mut self, mut access: impl FnMut(Dir)) { + pub fn access_root_dir(&mut self, mut access: impl FnMut(Dir)) { let volume0 = self.volume_mgr.open_volume(VolumeIdx(0)).unwrap(); let root_dir = volume0.open_root_dir().unwrap(); @@ -108,7 +114,7 @@ impl SdCard { } /// Returns a Vec of file names (long format) that match the given extension (e.g., "BIN") - pub fn list_files_by_extension(&mut self, ext: &str) -> Result, ()> { + pub fn list_files_by_extension(&mut self, ext: &str) -> Result, ()> { let mut result = Vec::new(); // Only proceed if card is inserted @@ -120,11 +126,14 @@ impl SdCard { let mut lfn_buffer = LfnBuffer::new(&mut lfn_storage); self.access_root_dir(|dir| { - dir.iterate_dir_lfn(&mut lfn_buffer, |_entry, name| { + dir.iterate_dir_lfn(&mut lfn_buffer, |entry, name| { if let Some(name) = name { let name = String::from_str(name).unwrap(); if name.contains(ext) { - result.push(name); + result.push(FileName { + long_name: name, + short_name: entry.name.clone(), + }); } } }) diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index ab8bb6a..208fa3f 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,8 +1,9 @@ use crate::{ - TASK_STATE, TaskState, + BINARY_CH, TASK_STATE, TaskState, display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, format, peripherals::keyboard, + storage::FileName, usb::RESTART_USB, }; use alloc::{string::String, vec::Vec}; @@ -43,7 +44,7 @@ use embedded_text::TextBox; use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = - Mutex::new(SelectionList::new(Vec::new())); + Mutex::new(SelectionList::new()); pub async fn ui_handler() { loop { @@ -59,7 +60,22 @@ pub async fn ui_handler() { let mut selections = SELECTIONS.lock().await; selections.down(); } - KeyCode::Enter | KeyCode::JoyRight => (), + KeyCode::Enter | KeyCode::JoyRight => { + let selections = SELECTIONS.lock().await; + let selection = selections.selections + [selections.current_selection as usize - 1] + .clone(); + + defmt::info!( + "loading selected binary: {:?}", + &selection.long_name.as_str() + ); + let bytes = crate::elf::read_binary(&selection.short_name) + .await + .unwrap(); + let entry = unsafe { crate::elf::load_binary(&bytes).unwrap() }; + BINARY_CH.send(entry).await; + } _ => (), } } @@ -71,7 +87,7 @@ pub async fn ui_handler() { } async fn draw_selection() { - let file_names: Vec = { + let file_names: Vec = { let guard = SELECTIONS.lock().await; guard.selections.clone() }; @@ -104,7 +120,7 @@ async fn draw_selection() { return; }; - let chain = Chain::new(Text::new(first, Point::zero(), text_style)); + let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style)); // for _ in 0..file_names.len() { // let chain = chain.append(Text::new( @@ -124,15 +140,16 @@ async fn draw_selection() { } } +#[derive(Clone)] pub struct SelectionList { current_selection: u16, - pub selections: Vec, + pub selections: Vec, } impl SelectionList { - pub const fn new(selections: Vec) -> Self { + pub const fn new() -> Self { Self { - selections, + selections: Vec::new(), current_selection: 0, } } From 70ecbcafc3606369f0860c54a04bbb9f33f55964 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 7 Sep 2025 18:13:55 -0600 Subject: [PATCH 22/27] can dynamically load applications --- Cargo.lock | 15 --- abi_sys/src/lib.rs | 2 +- kernel/Cargo.toml | 6 +- kernel/src/elf.rs | 261 ++++++++++++++++++++++++++++-------------- kernel/src/main.rs | 1 + kernel/src/storage.rs | 19 ++- kernel/src/ui.rs | 11 +- kernel/src/usb.rs | 2 +- 8 files changed, 198 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 737c75e..25dcb88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,7 +1080,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e961b33649994dcf69303af6b3a332c1228549e604d455d61ec5d2ab5e68d3a" dependencies = [ - "log", "plain", "scroll", ] @@ -1830,20 +1829,6 @@ name = "scroll" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] [[package]] name = "semver" diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 10dea33..45df9da 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -14,7 +14,7 @@ use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; pub type EntryFn = fn() -> Pin>>; #[unsafe(no_mangle)] -#[unsafe(link_section = ".userapp")] +#[unsafe(link_section = ".user_reloc")] pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; #[repr(usize)] diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 4843e70..2424867 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -79,11 +79,7 @@ static_cell = "2.1.1" bitflags = "2.9.1" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } -goblin = { version = "0.10.0", default-features = false, features = [ - "elf32", - "elf64", - "endian_fd", -] } +goblin = { version = "0.10.0", default-features = false, features = ["elf32"] } talc = "4.4.3" spin = "0.10.0" diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 64cb28b..ba63483 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,51 +1,22 @@ #![allow(static_mut_refs)] -use crate::{abi, storage::SDCARD}; -use abi_sys::{CallAbiTable, EntryFn}; -use alloc::{boxed::Box, vec::Vec}; -use core::{ - alloc::Layout, - ffi::c_void, - pin::Pin, - ptr::NonNull, - slice::from_raw_parts_mut, - task::{Context, Poll}, + +use crate::{ + abi, + storage::{File, SDCARD}, }; +use abi_sys::{CallAbiTable, EntryFn}; +use alloc::{vec, vec::Vec}; use embedded_sdmmc::ShortFileName; -use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; +use goblin::{ + elf::{ + header::header32::Header, + program_header::program_header32::{PT_LOAD, ProgramHeader}, + section_header::SHT_SYMTAB, + }, + elf32::{section_header::SectionHeader, sym::Sym}, +}; -pub async fn read_binary(name: &ShortFileName) -> Option> { - let mut guard = SDCARD.get().lock().await; - let sd = guard.as_mut()?; - - let mut buf = Vec::new(); - - defmt::info!("sd closure"); - sd.access_root_dir(|root_dir| { - // Try to open the file directly by name - defmt::info!("trying to open file: {:?}", name); - if let Ok(file) = root_dir.open_file_in_dir(name, embedded_sdmmc::Mode::ReadOnly) { - defmt::info!("opened"); - let mut temp = [0u8; 512]; - - defmt::info!("caching binary"); - loop { - match file.read(&mut temp) { - Ok(n) if n > 0 => buf.extend_from_slice(&temp[..n]), - _ => break, - } - } - - defmt::info!("done"); - let _ = file.close(); - } - }); - - if buf.is_empty() { - return None; - } - - Some(buf) -} +const ELF32_HDR_SIZE: usize = 52; // userland ram region defined in memory.x unsafe extern "C" { @@ -53,67 +24,181 @@ unsafe extern "C" { static __userapp_end__: u8; } -pub unsafe fn load_binary(bytes: &[u8]) -> Result { - let elf = Elf::parse(&bytes).expect("Failed to parse ELF"); +pub async unsafe fn load_binary(name: &ShortFileName) -> Result { + let mut sd_lock = SDCARD.get().lock().await; + let sd = sd_lock.as_mut().unwrap(); - if elf.is_64 || elf.is_lib || !elf.little_endian { - return Err("Unsupported ELF type"); - } + let mut error = ""; + let mut entry = 0; - for ph in &elf.program_headers { - if ph.p_type == PT_LOAD { - let vaddr = ph.p_vaddr as usize; - let memsz = ph.p_memsz as usize; - let filesz = ph.p_filesz as usize; - let offset = ph.p_offset as usize; + let mut header_buf = [0; ELF32_HDR_SIZE]; - let seg_start = vaddr; - let seg_end = vaddr + memsz; + sd.read_file(name, |mut file| { + file.read(&mut header_buf).unwrap(); + let elf_header = Header::from_bytes(&header_buf); - // Bounds check: make sure segment fits inside payload region - let user_start = unsafe { &__userapp_start__ as *const u8 as usize }; - let user_end = unsafe { &__userapp_end__ as *const u8 as usize }; - if seg_start < user_start || seg_end > user_end { - panic!( - "Segment out of bounds: {:x}..{:x} not within {:x}..{:x}", - seg_start, seg_end, user_start, user_end - ); + let mut program_headers_buf = vec![0_u8; elf_header.e_phentsize as usize]; + for i in 1..=elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(&mut program_headers_buf).unwrap(); + + let ph = cast_phdr(&program_headers_buf); + + if ph.p_type == PT_LOAD { + load_segment(&mut file, &ph).unwrap() } + } - unsafe { - let dst = seg_start as *mut u8; - let src = bytes.as_ptr().add(offset); + // MUST MATCH ABI EXACTLY + let entries: &[(CallAbiTable, usize)] = &[ + (CallAbiTable::Print, abi::print as usize), + (CallAbiTable::DrawIter, abi::draw_iter as usize), + (CallAbiTable::GetKey, abi::get_key as usize), + ]; + assert!(entries.len() == CallAbiTable::COUNT); - // Copy initialized part - core::ptr::copy_nonoverlapping(src, dst, filesz); + patch_abi(entries, &elf_header, &mut file).unwrap(); - // Zero BSS region (memsz - filesz) - if memsz > filesz { - core::ptr::write_bytes(dst.add(filesz), 0, memsz - filesz); + // TODO: dynamically search for abi table + + entry = elf_header.e_entry as u32; + }) + .await + .unwrap(); + + if entry != 0 { + Ok(unsafe { core::mem::transmute(entry) }) + } else { + Err(error) + } +} + +fn patch_abi( + entries: &[(CallAbiTable, usize)], + elf_header: &Header, + file: &mut File, +) -> Result<(), ()> { + for i in 1..=elf_header.e_shnum { + let sh = read_section(file, &elf_header, i.into()); + + // find the symbol table + if sh.sh_type == SHT_SYMTAB { + let mut symtab_buf = vec![0u8; sh.sh_size as usize]; + file.seek_from_start(sh.sh_offset).unwrap(); + file.read(&mut symtab_buf).unwrap(); + + // Cast buffer into symbols + let sym_count = sh.sh_size as usize / sh.sh_entsize as usize; + for i in 0..sym_count { + let sym_bytes = + &symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize]; + let sym = cast_sym(sym_bytes); + + let str_sh = read_section(file, &elf_header, sh.sh_link); + + let mut name = Vec::new(); + file.seek_from_start(str_sh.sh_offset + sym.st_name) + .unwrap(); + + loop { + let mut byte = [0u8; 1]; + file.read(&mut byte).unwrap(); + if byte[0] == 0 { + break; + } + name.push(byte[0]); + } + + let symbol_name = core::str::from_utf8(&name).unwrap(); + if symbol_name == "CALL_ABI_TABLE" { + let table_base = sym.st_value as *mut usize; + + for &(abi_idx, func_ptr) in entries { + unsafe { + table_base.add(abi_idx as usize).write(func_ptr); + } + } + return Ok(()); } } } } + Err(()) +} - let call_abi_sym = elf - .syms - .iter() - .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "CALL_ABI_TABLE") - .expect("syscall table not found"); +fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHeader { + let mut section_header_buf = vec![0_u8; elf_header.e_shentsize as usize]; - let table_base = call_abi_sym.st_value as *mut usize; + file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize as u32 * section)) + .unwrap(); + file.read(&mut section_header_buf).unwrap(); - let entries: &[(CallAbiTable, usize)] = &[ - (CallAbiTable::Print, abi::print as usize), - (CallAbiTable::DrawIter, abi::draw_iter as usize), - (CallAbiTable::GetKey, abi::get_key as usize), - ]; - assert!(entries.len() == CallAbiTable::COUNT); + cast_shdr(§ion_header_buf) +} + +fn load_segment(file: &mut File, ph: &ProgramHeader) -> Result<(), ()> { + let dst_start = ph.p_vaddr as *mut u8; + let filesz = ph.p_filesz as usize; + let memsz = ph.p_memsz as usize; + let vaddr = ph.p_vaddr as usize; + let mut remaining = filesz; + let mut dst_ptr = dst_start; + let mut file_offset = ph.p_offset; + + let seg_start = vaddr; + let seg_end = vaddr + memsz; + + // Bounds check: make sure segment fits inside payload region + let user_start = unsafe { &__userapp_start__ as *const u8 as usize }; + let user_end = unsafe { &__userapp_end__ as *const u8 as usize }; + if seg_start < user_start || seg_end > user_end { + panic!( + "Segment out of bounds: {:x}..{:x} not within {:x}..{:x}", + seg_start, seg_end, user_start, user_end + ); + } + + // Buffer for chunked reads (512 bytes is typical SD sector size) + let mut buf = [0u8; 512]; + + while remaining > 0 { + let to_read = core::cmp::min(remaining, buf.len()); + // Read chunk from file + file.seek_from_start(file_offset).unwrap(); + file.read(&mut buf[..to_read]).unwrap(); - for &(abi_idx, func_ptr) in entries { unsafe { - table_base.add(abi_idx as usize).write(func_ptr); + // Copy chunk directly into destination memory + core::ptr::copy_nonoverlapping(buf.as_ptr(), dst_ptr, to_read); + dst_ptr = dst_ptr.add(to_read); + } + + remaining -= to_read; + file_offset += to_read as u32; + } + + // Zero BSS (memsz - filesz) + if memsz > filesz { + unsafe { + core::ptr::write_bytes(dst_ptr, 0, memsz - filesz); } } - Ok(unsafe { core::mem::transmute(elf.entry as u32) }) + + Ok(()) +} + +fn cast_phdr(buf: &[u8]) -> ProgramHeader { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const ProgramHeader) } +} + +fn cast_shdr(buf: &[u8]) -> SectionHeader { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const SectionHeader) } +} + +fn cast_sym(buf: &[u8]) -> Sym { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const Sym) } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index ca2bbce..e6083dc 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -138,6 +138,7 @@ async fn userland_task() { *state = TaskState::Kernel; } + defmt::info!("Executing Binary"); entry().await; // enable kernel ui diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index c16a52b..25b82e2 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -12,7 +12,7 @@ use embedded_sdmmc::{ Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, }; -use embedded_sdmmc::{LfnBuffer, ShortFileName}; +use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName}; pub const MAX_DIRS: usize = 4; pub const MAX_FILES: usize = 5; @@ -23,6 +23,7 @@ type SD = SdmmcSdCard; type VolMgr = VolumeManager; type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +pub type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; pub static SDCARD: LazyLock>> = LazyLock::new(|| Mutex::new(None)); @@ -113,6 +114,22 @@ impl SdCard { access(root_dir); } + pub async fn read_file( + &mut self, + name: &ShortFileName, + mut access: impl FnMut(File), + ) -> Result<(), ()> { + let mut res = Err(()); + self.access_root_dir(|root_dir| { + if let Ok(file) = root_dir.open_file_in_dir(name, Mode::ReadOnly) { + res = Ok(()); + access(file); + } + }); + + res + } + /// Returns a Vec of file names (long format) that match the given extension (e.g., "BIN") pub fn list_files_by_extension(&mut self, ext: &str) -> Result, ()> { let mut result = Vec::new(); diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 208fa3f..3485bb0 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,6 +1,7 @@ use crate::{ BINARY_CH, TASK_STATE, TaskState, display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, + elf::load_binary, format, peripherals::keyboard, storage::FileName, @@ -66,14 +67,8 @@ pub async fn ui_handler() { [selections.current_selection as usize - 1] .clone(); - defmt::info!( - "loading selected binary: {:?}", - &selection.long_name.as_str() - ); - let bytes = crate::elf::read_binary(&selection.short_name) - .await - .unwrap(); - let entry = unsafe { crate::elf::load_binary(&bytes).unwrap() }; + let entry = + unsafe { load_binary(&selection.short_name).await.unwrap() }; BINARY_CH.send(entry).await; } _ => (), diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index b657211..8349652 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -17,7 +17,7 @@ pub static RESTART_USB: Signal = Signal::new(); pub static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); pub async fn usb_handler(driver: Driver<'static, USB>) { - let mut config = Config::new(0xc0de, 0xcafe); + let mut config = Config::new(0xc0de, 0xbabe); config.manufacturer = Some("LegitCamper"); config.product = Some("PicoCalc"); config.serial_number = Some("01001100"); From 1994d74a17a0435dddc4f676dc958c81e2fbb999 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 13 Sep 2025 13:57:41 -0600 Subject: [PATCH 23/27] WIP --- Cargo.lock | 2 +- abi/src/lib.rs | 2 +- abi_sys/src/lib.rs | 17 ++++- kernel/Cargo.toml | 2 +- kernel/src/abi.rs | 31 +++++---- kernel/src/display.rs | 37 ++++++----- kernel/src/elf.rs | 1 + kernel/src/main.rs | 17 +++-- kernel/src/scsi/mod.rs | 44 ++++++++----- kernel/src/ui.rs | 104 +++++++++++++------------------ kernel/src/usb.rs | 34 +++------- user-apps/calculator/src/main.rs | 2 + 12 files changed, 154 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25dcb88..14d49a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1941,7 +1941,7 @@ dependencies = [ [[package]] name = "st7365p-lcd" version = "0.11.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=87abf450404865dcb535292e9e1a6a2457fd4599#87abf450404865dcb535292e9e1a6a2457fd4599" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=2a484aaab5f6b9824cc813fe4ae087250c9e39c1#2a484aaab5f6b9824cc813fe4ae087250c9e39c1" dependencies = [ "bitvec", "embedded-graphics-core", diff --git a/abi/src/lib.rs b/abi/src/lib.rs index ec5b528..559c81f 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use abi_sys::draw_iter; -pub use abi_sys::{get_key, print}; +pub use abi_sys::{get_key, print, sleep}; pub use embassy_time; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 45df9da..d821afc 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -21,12 +21,13 @@ pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable:: #[derive(Clone, Copy)] pub enum CallAbiTable { Print = 0, - DrawIter = 1, - GetKey = 2, + Sleep = 1, + DrawIter = 2, + GetKey = 3, } impl CallAbiTable { - pub const COUNT: usize = 3; + pub const COUNT: usize = 4; } pub type PrintAbi = extern "Rust" fn(msg: &str); @@ -39,6 +40,16 @@ pub fn print(msg: &str) { } } +pub type SleepAbi = extern "Rust" fn(ticks: u64); + +pub fn sleep(ticks: u64) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; + let f: SleepAbi = core::mem::transmute(ptr); + f(ticks); + } +} + pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); pub fn draw_iter(pixels: &[Pixel]) { diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 2424867..8f1507a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -70,7 +70,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" embedded-sdmmc = { version = "0.9", default-features = false } -st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "2a484aaab5f6b9824cc813fe4ae087250c9e39c1" } # async branch embedded-graphics = { version = "0.8.1" } embedded-text = "0.7.2" embedded-layout = "0.4.2" diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 889a0f3..8be0c82 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,9 +1,10 @@ -use core::pin::Pin; +use core::{pin::Pin, time::Duration}; -use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi}; +use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi, SleepAbi}; use alloc::boxed::Box; use defmt::info; use embassy_futures::block_on; +use embassy_time::Timer; use embedded_graphics::{ Drawable, draw_target::DrawTarget, @@ -17,6 +18,7 @@ use crate::{KEY_CACHE, display::FRAMEBUFFER}; // ensure the abi and the kernel fn signatures are the same const _: PrintAbi = print; +const _: SleepAbi = sleep; const _: DrawIterAbi = draw_iter; const _: GetKeyAbi = get_key; @@ -24,20 +26,23 @@ pub extern "Rust" fn print(msg: &str) { defmt::info!("{:?}", msg); } +pub extern "Rust" fn sleep(ticks: u64) { + for _ in 0..ticks { + for _ in 0..100 { + cortex_m::asm::nop(); + } + } +} + // TODO: maybe return result pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { - for _ in 0..10 { - if let Some(mut framebuffer) = FRAMEBUFFER.try_lock().ok() { - for _ in 0..10 { - // kernel takes() framebuffer - if let Some(framebuffer) = framebuffer.as_mut() { - framebuffer.draw_iter(pixels.iter().copied()).unwrap(); - } - break; - } - break; + loop { + let fb = FRAMEBUFFER.get().try_lock(); + if let Ok(mut fb) = fb { + fb.draw_iter(pixels.iter().copied()).unwrap(); + return; } - cortex_m::asm::nop(); + sleep(1) } } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 7f27811..456c06a 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -3,11 +3,16 @@ use embassy_rp::{ peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; +use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, lazy_lock::LazyLock, mutex::Mutex, +}; use embassy_time::{Delay, Timer}; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::{Rgb565, RgbColor}, +}; use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::{FrameBuffer, ST7365P}; -use static_cell::StaticCell; type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, @@ -20,8 +25,8 @@ pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; type FB = FrameBuffer; -static FRAMEBUFFER_CELL: StaticCell = StaticCell::new(); -pub static FRAMEBUFFER: Mutex> = Mutex::new(None); +pub static FRAMEBUFFER: LazyLock> = + LazyLock::new(|| Mutex::new(FrameBuffer::new())); pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -38,27 +43,27 @@ pub async fn init_display( true, Delay, ); - let framebuffer = FRAMEBUFFER_CELL.init(FrameBuffer::new()); + let mut fb = FRAMEBUFFER.get().lock().await; display.init().await.unwrap(); display.set_custom_orientation(0x40).await.unwrap(); - framebuffer.draw(&mut display).await.unwrap(); + display.draw(&mut fb).await.unwrap(); display.set_on().await.unwrap(); - FRAMEBUFFER.lock().await.replace(framebuffer); display } +pub async fn clear_fb() { + let mut fb = FRAMEBUFFER.get().lock().await; + let fb = &mut *fb; + fb.clear(Rgb565::BLACK).unwrap(); +} + pub async fn display_handler(mut display: DISPLAY) { loop { - let fb: &mut FB = { - let mut guard = FRAMEBUFFER.lock().await; - guard.take().unwrap() // take ownership - }; // guard dropped - - fb.partial_draw_batched(&mut display).await.unwrap(); - - // Put it back - FRAMEBUFFER.lock().await.replace(fb); + { + let mut fb = FRAMEBUFFER.get().lock().await; + display.partial_draw_batched(&mut fb).await.unwrap(); + } Timer::after_millis(32).await; // 30 fps } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index ba63483..e7e6c2b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -53,6 +53,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result { // MUST MATCH ABI EXACTLY let entries: &[(CallAbiTable, usize)] = &[ (CallAbiTable::Print, abi::print as usize), + (CallAbiTable::Sleep, abi::sleep as usize), (CallAbiTable::DrawIter, abi::draw_iter as usize), (CallAbiTable::GetKey, abi::get_key as usize), ]; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index e6083dc..bdea0a1 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -20,7 +20,7 @@ mod utils; use core::sync::atomic::Ordering; use crate::{ - display::{display_handler, init_display}, + display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, elf::load_binary, peripherals::{ conf_peripherals, @@ -49,7 +49,9 @@ use embassy_rp::{ spi::{self, Spi}, usb as embassy_rp_usb, }; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex}; +use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex, signal::Signal, +}; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; @@ -75,7 +77,9 @@ static ALLOCATOR: Talck, ClaimOnOom> = .lock(); static TASK_STATE: Mutex = Mutex::new(TaskState::Ui); +static TASK_STATE_CHANGED: Signal = Signal::new(); +#[derive(Copy, Clone, PartialEq)] enum TaskState { Ui, Kernel, @@ -131,6 +135,7 @@ async fn userland_task() { let recv = BINARY_CH.receiver(); loop { let entry = recv.receive().await; + defmt::info!("Got Entry"); // disable kernel ui { @@ -138,6 +143,8 @@ async fn userland_task() { *state = TaskState::Kernel; } + // clear_fb().await; // blocks future exec? + defmt::info!("Executing Binary"); entry().await; @@ -237,8 +244,8 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); }; - // let usb = embassy_rp_usb::Driver::new(usb, Irqs); - // let usb_fut = usb_handler(usb); + let usb = embassy_rp_usb::Driver::new(usb, Irqs); + let usb_fut = usb_handler(usb); let key_abi_fut = async { loop { @@ -247,7 +254,7 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { } }; - join4(display_fut, ui_fut, binary_search_fut, key_abi_fut).await; + join5(display_fut, ui_fut, usb_fut, binary_search_fut, key_abi_fut).await; } static mut KEY_CACHE: Queue = Queue::new(); diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index 7859fa5..8ce1172 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -8,18 +8,17 @@ use heapless::Vec; mod scsi_types; use scsi_types::*; -use crate::storage::SdCard; +use crate::storage::{SDCARD, SdCard}; const BULK_ENDPOINT_PACKET_SIZE: usize = 64; -pub struct MassStorageClass<'d, 's, D: Driver<'d>> { - sdcard: &'s SdCard, +pub struct MassStorageClass<'d, D: Driver<'d>> { bulk_out: D::EndpointOut, bulk_in: D::EndpointIn, } -impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { - pub fn new(builder: &mut Builder<'d, D>, sdcard: &'s SdCard) -> Self { +impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { + pub fn new(builder: &mut Builder<'d, D>) -> Self { let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class let mut interface = function.interface(); let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); @@ -27,11 +26,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { let bulk_out = alt.endpoint_bulk_out(BULK_ENDPOINT_PACKET_SIZE as u16); let bulk_in = alt.endpoint_bulk_in(BULK_ENDPOINT_PACKET_SIZE as u16); - Self { - bulk_out, - bulk_in, - sdcard, - } + Self { bulk_out, bulk_in } } pub async fn poll(&mut self) { @@ -139,7 +134,9 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { self.bulk_in.write(&response[..len]).await.map_err(|_| ()) } ScsiCommand::TestUnitReady => { - if self.sdcard.is_attached() { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + if sdcard.is_attached() { Ok(()) } else { Err(()) @@ -185,8 +182,11 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { self.bulk_in.write(&response[..len]).await.map_err(|_| ()) } ScsiCommand::ReadCapacity10 => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + let block_size = SdCard::BLOCK_SIZE as u64; - let total_blocks = self.sdcard.size() / block_size; + let total_blocks = sdcard.size() / block_size; let last_lba = total_blocks.checked_sub(1).unwrap_or(0); @@ -196,8 +196,11 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { self.bulk_in.write(&response).await.map_err(|_| ()) } ScsiCommand::ReadCapacity16 { alloc_len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + let block_size = SdCard::BLOCK_SIZE as u64; - let total_blocks = self.sdcard.size() / block_size; + let total_blocks = sdcard.size() / block_size; let last_lba = total_blocks.checked_sub(1).unwrap_or(0); @@ -209,9 +212,12 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { self.bulk_in.write(&response[..len]).await.map_err(|_| ()) } ScsiCommand::Read { lba, len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + for i in 0..len { let block_idx = BlockIdx(lba as u32 + i as u32); - self.sdcard.read_blocks(&mut block, block_idx)?; + sdcard.read_blocks(&mut block, block_idx)?; for chunk in block[0].contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { self.bulk_in.write(chunk).await.map_err(|_| ())?; } @@ -219,6 +225,9 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { Ok(()) } ScsiCommand::Write { lba, len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + for i in 0..len { let block_idx = BlockIdx(lba as u32 + i as u32); for chunk in block[0] @@ -227,13 +236,16 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 's, D> { { self.bulk_out.read(chunk).await.map_err(|_| ())?; } - self.sdcard.write_blocks(&mut block, block_idx)?; + sdcard.write_blocks(&mut block, block_idx)?; } Ok(()) } ScsiCommand::ReadFormatCapacities { alloc_len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + let block_size = SdCard::BLOCK_SIZE as u32; - let num_blocks = (self.sdcard.size() / block_size as u64) as u32; + let num_blocks = (sdcard.size() / block_size as u64) as u32; let mut response = [0u8; 12]; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 3485bb0..481de65 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -5,36 +5,21 @@ use crate::{ format, peripherals::keyboard, storage::FileName, - usb::RESTART_USB, }; use alloc::{string::String, vec::Vec}; use core::{fmt::Debug, str::FromStr, sync::atomic::Ordering}; -use defmt::info; -use embassy_rp::{ - gpio::{Level, Output}, - peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, - spi::{Async, Spi}, -}; use embassy_sync::{ blocking_mutex::raw::{CriticalSectionRawMutex, ThreadModeRawMutex}, mutex::Mutex, - signal::Signal, }; -use embassy_time::{Delay, Timer}; use embedded_graphics::{ Drawable, - draw_target::DrawTarget, - mono_font::{ - MonoTextStyle, - ascii::{FONT_6X9, FONT_6X10, FONT_9X15, FONT_10X20}, - }, + mono_font::{MonoTextStyle, ascii::FONT_9X15}, pixelcolor::Rgb565, - prelude::{Dimensions, Point, Primitive, RgbColor, Size}, - primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, + prelude::{Dimensions, Point, RgbColor, Size}, + primitives::Rectangle, text::Text, }; -use embedded_hal_async::spi::SpiDevice; -use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}; use embedded_layout::{ align::{horizontal, vertical}, layout::linear::LinearLayout, @@ -77,6 +62,8 @@ pub async fn ui_handler() { } draw_selection().await; + } else { + embassy_time::Timer::after_millis(50).await; } } } @@ -87,51 +74,50 @@ async fn draw_selection() { guard.selections.clone() }; - let mut fb_lock = FRAMEBUFFER.lock().await; - if let Some(fb) = fb_lock.as_mut() { - let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); - let display_area = fb.bounding_box(); + let mut fb = FRAMEBUFFER.get().lock().await; + let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + let display_area = fb.bounding_box(); - const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; - let no_bins = String::from_str(NO_BINS).unwrap(); + const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; + let no_bins = String::from_str(NO_BINS).unwrap(); - if file_names.is_empty() { - TextBox::new( - &no_bins, - Rectangle::new( - Point::new(25, 25), - Size::new(display_area.size.width - 50, display_area.size.width - 50), - ), - text_style, - ) - .draw(*fb) - .unwrap(); - } else { - let mut file_names = file_names.iter(); - let Some(first) = file_names.next() else { - Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory", - Point::zero(), text_style).draw(*fb).unwrap(); - - return; - }; - - let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style)); - - // for _ in 0..file_names.len() { - // let chain = chain.append(Text::new( - // file_names.next().unwrap(), - // Point::zero(), - // text_style, - // )); - // } - - LinearLayout::vertical(chain) - .with_alignment(horizontal::Center) - .arrange() - .align_to(&display_area, horizontal::Center, vertical::Center) - .draw(*fb) + if file_names.is_empty() { + TextBox::new( + &no_bins, + Rectangle::new( + Point::new(25, 25), + Size::new(display_area.size.width - 50, display_area.size.width - 50), + ), + text_style, + ) + .draw(&mut *fb) + .unwrap(); + } else { + let mut file_names = file_names.iter(); + let Some(first) = file_names.next() else { + Text::new(NO_BINS, Point::zero(), text_style) + .draw(&mut *fb) .unwrap(); + + return; }; + + let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style)); + + // for _ in 0..file_names.len() { + // let chain = chain.append(Text::new( + // file_names.next().unwrap(), + // Point::zero(), + // text_style, + // )); + // } + + LinearLayout::vertical(chain) + .with_alignment(horizontal::Center) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center) + .draw(&mut *fb) + .unwrap(); } } diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 8349652..0035a90 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,6 +1,5 @@ -use core::sync::atomic::Ordering; - use crate::{ + TASK_STATE, TASK_STATE_CHANGED, TaskState, scsi::MassStorageClass, storage::{SDCARD, SdCard}, }; @@ -13,9 +12,6 @@ use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_usb::{Builder, Config}; use portable_atomic::AtomicBool; -pub static RESTART_USB: Signal = Signal::new(); -pub static ENABLE_SCSI: AtomicBool = AtomicBool::new(false); - pub async fn usb_handler(driver: Driver<'static, USB>) { let mut config = Config::new(0xc0de, 0xbabe); config.manufacturer = Some("LegitCamper"); @@ -37,27 +33,17 @@ pub async fn usb_handler(driver: Driver<'static, USB>) { &mut control_buf, ); - let lock = SDCARD.get().lock().await; - let sdcard = lock.as_ref().unwrap(); - - let mut scsi = MassStorageClass::new(&mut builder, &sdcard); + let mut scsi = MassStorageClass::new(&mut builder); let mut usb = builder.build(); loop { - select3( - async { - loop { - RESTART_USB.wait().await; - return; - } - }, - usb.run(), - async { - if ENABLE_SCSI.load(Ordering::Acquire) { - scsi.poll().await - } - }, - ) - .await; + defmt::info!("in: {}", *TASK_STATE.lock().await as u32); + if *TASK_STATE.lock().await == TaskState::Ui { + defmt::info!("running scsi and usb"); + select(join(usb.run(), scsi.poll()), TASK_STATE_CHANGED.wait()).await; + } else { + defmt::info!("not in ui state"); + TASK_STATE_CHANGED.wait().await; + } } } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 337533c..39810e8 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -37,6 +37,8 @@ pub async fn main() { .draw(&mut display) .unwrap(); + embassy_time::Timer::after_millis(1000).await; + if let Some(event) = get_key() { print("User got event"); match event.key { From 59f6bf8ef762f9726ff8b382a2a3f69220ec85e1 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 13 Sep 2025 14:43:54 -0600 Subject: [PATCH 24/27] update deps --- Cargo.lock | 136 +++++++++++++++++++++++----------- kernel/Cargo.toml | 11 +-- kernel/src/display.rs | 7 +- kernel/src/main.rs | 45 ++++++----- kernel/src/peripherals/mod.rs | 6 +- kernel/src/scsi/mod.rs | 4 +- kernel/src/storage.rs | 4 +- kernel/src/usb.rs | 8 +- 8 files changed, 133 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14d49a4..4db15b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ dependencies = [ "term 1.1.0", ] +[[package]] +name = "assign-resources" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840ad5d907de7448d88a3f22b4a7b5d326c6eb3deeb9f94cfaaec7354a80b305" + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -496,7 +502,7 @@ checksum = "8578db196d74db92efdd5ebc546736dac1685499ee245b22eff92fa5e4b57945" dependencies = [ "embassy-futures", "embassy-hal-internal 0.3.0", - "embassy-sync 0.7.0", + "embassy-sync 0.7.2", "embassy-time 0.4.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -507,23 +513,41 @@ dependencies = [ ] [[package]] -name = "embassy-executor" -version = "0.7.0" +name = "embassy-embedded-hal" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6" +checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +dependencies = [ + "embassy-futures", + "embassy-hal-internal 0.3.0", + "embassy-sync 0.7.2", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" dependencies = [ "cortex-m", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "document-features", "embassy-executor-macros", + "embassy-executor-timer-queue", ] [[package]] name = "embassy-executor-macros" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" dependencies = [ "darling", "proc-macro2", @@ -532,10 +556,16 @@ dependencies = [ ] [[package]] -name = "embassy-futures" -version = "0.1.1" +name = "embassy-executor-timer-queue" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" [[package]] name = "embassy-hal-internal" @@ -545,7 +575,6 @@ checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59" dependencies = [ "cortex-m", "critical-section", - "defmt 0.3.100", "num-traits", ] @@ -555,6 +584,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ + "cortex-m", + "critical-section", + "defmt 1.0.1", "num-traits", ] @@ -566,13 +598,13 @@ checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" [[package]] name = "embassy-net-driver-channel" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a567ab50319d866ad5e6c583ed665ba9b07865389644d3d82e45bf1497c934" +checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f" dependencies = [ "embassy-futures", "embassy-net-driver", - "embassy-sync 0.7.0", + "embassy-sync 0.7.2", ] [[package]] @@ -587,12 +619,12 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", - "embassy-embedded-hal", + "embassy-embedded-hal 0.3.1", "embassy-futures", "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", "embassy-time 0.4.0", - "embassy-usb-driver", + "embassy-usb-driver 0.1.1", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -605,7 +637,7 @@ dependencies = [ "nb 1.1.0", "pio 0.2.1", "pio-proc 0.2.2", - "rand_core", + "rand_core 0.6.4", "rp-pac", "rp2040-boot2", "sha2-const-stable", @@ -614,25 +646,25 @@ dependencies = [ [[package]] name = "embassy-rp" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a063d8baccdc5c7752840f4c7931f17bcd7de1ffe1efa2109e68113fe42612" +checksum = "1a284935af0a869de3fa14af74b5f932389dd66d7048012f1083b06f38d05399" dependencies = [ "atomic-polyfill", "cfg-if", "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "document-features", - "embassy-embedded-hal", + "embassy-embedded-hal 0.5.0", "embassy-futures", - "embassy-hal-internal 0.2.0", - "embassy-sync 0.6.2", - "embassy-time 0.4.0", + "embassy-hal-internal 0.3.0", + "embassy-sync 0.7.2", + "embassy-time 0.5.0", "embassy-time-driver", "embassy-time-queue-utils", - "embassy-usb-driver", + "embassy-usb-driver 0.2.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -644,7 +676,8 @@ dependencies = [ "fixed", "nb 1.1.0", "pio 0.3.0", - "rand_core", + "rand_core 0.6.4", + "rand_core 0.9.3", "rp-pac", "rp2040-boot2", "sha2-const-stable", @@ -667,16 +700,16 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef1a8a1ea892f9b656de0295532ac5d8067e9830d49ec75076291fd6066b136" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" dependencies = [ "cfg-if", "critical-section", "defmt 1.0.1", "embedded-io-async", + "futures-core", "futures-sink", - "futures-util", "heapless", ] @@ -688,10 +721,8 @@ checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" dependencies = [ "cfg-if", "critical-section", - "defmt 0.3.100", "document-features", "embassy-time-driver", - "embassy-time-queue-utils", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -706,8 +737,10 @@ checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" dependencies = [ "cfg-if", "critical-section", + "defmt 1.0.1", "document-features", "embassy-time-driver", + "embassy-time-queue-utils", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -725,24 +758,25 @@ dependencies = [ [[package]] name = "embassy-time-queue-utils" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83" +checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ - "embassy-executor", + "embassy-executor-timer-queue", "heapless", ] [[package]] name = "embassy-usb" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +checksum = "dc4462e48b19a4f401a11901bdd981aab80c6a826608016a0bdc73cbbab31954" dependencies = [ "embassy-futures", "embassy-net-driver-channel", - "embassy-sync 0.6.2", - "embassy-usb-driver", + "embassy-sync 0.7.2", + "embassy-usb-driver 0.2.0", + "embedded-io-async", "heapless", "ssmarshal", "usbd-hid", @@ -753,6 +787,15 @@ name = "embassy-usb-driver" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" +dependencies = [ + "embedded-io-async", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a" dependencies = [ "defmt 1.0.1", "embedded-io-async", @@ -1203,6 +1246,7 @@ name = "kernel" version = "0.1.0" dependencies = [ "abi_sys", + "assign-resources", "bitflags 2.9.1", "bt-hci", "cortex-m", @@ -1211,12 +1255,12 @@ dependencies = [ "cyw43-pio", "defmt 0.3.100", "defmt-rtt", - "embassy-embedded-hal", + "embassy-embedded-hal 0.3.1", "embassy-executor", "embassy-futures", - "embassy-rp 0.4.0", - "embassy-sync 0.7.0", - "embassy-time 0.4.0", + "embassy-rp 0.8.0", + "embassy-sync 0.7.2", + "embassy-time 0.5.0", "embassy-usb", "embedded-graphics", "embedded-hal 0.2.7", @@ -1711,6 +1755,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + [[package]] name = "redox_syscall" version = "0.5.13" @@ -2112,7 +2162,7 @@ dependencies = [ "embedded-io", "futures", "heapless", - "rand_core", + "rand_core 0.6.4", "static_cell", "trouble-host-macros", "zerocopy", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 8f1507a..327cc02 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -31,20 +31,20 @@ defmt = [ ] [dependencies] -embassy-executor = { version = "0.7", features = [ +embassy-executor = { version = "0.9", features = [ "arch-cortex-m", "executor-interrupt", "executor-thread", "nightly", ] } -embassy-rp = { version = "0.4.0", features = [ +embassy-rp = { version = "0.8.0", features = [ "critical-section-impl", "unstable-pac", "time-driver", ] } -embassy-usb = "0.4.0" -embassy-futures = "0.1.1" -embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } +embassy-usb = "0.5.1" +embassy-futures = "0.1.2" +embassy-time = { version = "0.5.0", features = ["generic-queue-8"] } embassy-embedded-hal = "0.3.1" embassy-sync = { version = "0.7" } trouble-host = { version = "0.1", features = [ @@ -65,6 +65,7 @@ cortex-m = { version = "0.7.7" } cortex-m-rt = "0.7.5" panic-probe = "0.3" portable-atomic = { version = "1.11", features = ["critical-section"] } +assign-resources = "0.5.0" defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 456c06a..c362bd7 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,4 +1,5 @@ use embassy_rp::{ + Peri, gpio::{Level, Output}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, @@ -30,9 +31,9 @@ pub static FRAMEBUFFER: LazyLock> = pub async fn init_display( spi: Spi<'static, SPI1, Async>, - cs: PIN_13, - data: PIN_14, - reset: PIN_15, + cs: Peri<'static, PIN_13>, + data: Peri<'static, PIN_14>, + reset: Peri<'static, PIN_15>, ) -> DISPLAY { let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); let mut display = ST7365P::new( diff --git a/kernel/src/main.rs b/kernel/src/main.rs index bdea0a1..84c2ae9 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -17,8 +17,6 @@ mod ui; mod usb; mod utils; -use core::sync::atomic::Ordering; - use crate::{ display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, elf::load_binary, @@ -31,14 +29,15 @@ use crate::{ usb::usb_handler, }; use abi_sys::EntryFn; -use alloc::vec::Vec; use {defmt_rtt as _, panic_probe as _}; +use assign_resources::assign_resources; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::join::{join, join3, join4, join5}; use embassy_rp::{ + Peri, gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, multicore::{Stack, spawn_core1}, @@ -157,34 +156,32 @@ async fn userland_task() { } struct Display { - spi: SPI1, - clk: PIN_10, - mosi: PIN_11, - miso: PIN_12, - dma1: DMA_CH0, - dma2: DMA_CH1, - cs: PIN_13, - data: PIN_14, - reset: PIN_15, + spi: Peri<'static, SPI1>, + clk: Peri<'static, PIN_10>, + mosi: Peri<'static, PIN_11>, + miso: Peri<'static, PIN_12>, + dma1: Peri<'static, DMA_CH0>, + dma2: Peri<'static, DMA_CH1>, + cs: Peri<'static, PIN_13>, + data: Peri<'static, PIN_14>, + reset: Peri<'static, PIN_15>, } - struct Sd { - spi: SPI0, - clk: PIN_18, - mosi: PIN_19, - miso: PIN_16, - cs: PIN_17, - det: PIN_22, + spi: Peri<'static, SPI0>, + clk: Peri<'static, PIN_18>, + mosi: Peri<'static, PIN_19>, + miso: Peri<'static, PIN_16>, + cs: Peri<'static, PIN_17>, + det: Peri<'static, PIN_22>, } - struct Mcu { - i2c: I2C1, - clk: PIN_7, - data: PIN_6, + i2c: Peri<'static, I2C1>, + clk: Peri<'static, PIN_7>, + data: Peri<'static, PIN_6>, } #[embassy_executor::task] -async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) { +async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB>) { // MCU i2c bus for peripherals let mut config = i2c::Config::default(); config.frequency = 400_000; diff --git a/kernel/src/peripherals/mod.rs b/kernel/src/peripherals/mod.rs index d00c7ec..25a436e 100644 --- a/kernel/src/peripherals/mod.rs +++ b/kernel/src/peripherals/mod.rs @@ -5,7 +5,9 @@ use embassy_rp::{ i2c::{Async, I2c}, peripherals::I2C1, }; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, lazy_lock::LazyLock, mutex::Mutex}; +use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, lazy_lock::LazyLock, mutex::Mutex, +}; use embassy_time::Timer; pub mod keyboard; @@ -15,7 +17,7 @@ use crate::peripherals::keyboard::{configure_keyboard, read_keyboard_fifo}; const MCU_ADDR: u8 = 0x1F; type I2CBUS = I2c<'static, I2C1, Async>; -pub static PERIPHERAL_BUS: LazyLock>> = +pub static PERIPHERAL_BUS: LazyLock>> = LazyLock::new(|| Mutex::new(None)); const REG_ID_VER: u8 = 0x01; diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index 8ce1172..e92fdeb 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -23,8 +23,8 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { let mut interface = function.interface(); let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); - let bulk_out = alt.endpoint_bulk_out(BULK_ENDPOINT_PACKET_SIZE as u16); - let bulk_in = alt.endpoint_bulk_in(BULK_ENDPOINT_PACKET_SIZE as u16); + let bulk_out = alt.endpoint_bulk_out(None, BULK_ENDPOINT_PACKET_SIZE as u16); + let bulk_in = alt.endpoint_bulk_in(None, BULK_ENDPOINT_PACKET_SIZE as u16); Self { bulk_out, bulk_in } } diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 25b82e2..ee1fa1e 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -3,7 +3,7 @@ use core::str::FromStr; use embassy_rp::gpio::{Input, Output}; use embassy_rp::peripherals::SPI0; use embassy_rp::spi::{Blocking, Spi}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::lazy_lock::LazyLock; use embassy_sync::mutex::Mutex; use embassy_time::Delay; @@ -25,7 +25,7 @@ type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES> type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; pub type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; -pub static SDCARD: LazyLock>> = +pub static SDCARD: LazyLock>> = LazyLock::new(|| Mutex::new(None)); pub struct DummyTimeSource {} diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 0035a90..49c0665 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,16 +1,10 @@ -use crate::{ - TASK_STATE, TASK_STATE_CHANGED, TaskState, - scsi::MassStorageClass, - storage::{SDCARD, SdCard}, -}; +use crate::{TASK_STATE, TASK_STATE_CHANGED, TaskState, scsi::MassStorageClass}; use embassy_futures::{ join::join, select::{select, select3}, }; use embassy_rp::{peripherals::USB, usb::Driver}; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; use embassy_usb::{Builder, Config}; -use portable_atomic::AtomicBool; pub async fn usb_handler(driver: Driver<'static, USB>) { let mut config = Config::new(0xc0de, 0xbabe); From 919744dbec7ac0a501c3a13ad0f52e95269bd81f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 14 Sep 2025 18:40:48 -0600 Subject: [PATCH 25/27] atomic fb --- Cargo.lock | 3 +- kernel/Cargo.toml | 3 +- kernel/src/abi.rs | 8 +- kernel/src/display.rs | 25 +-- kernel/src/framebuffer.rs | 432 ++++++++++++++++++++++++++++++++++++++ kernel/src/main.rs | 1 + kernel/src/ui.rs | 9 +- 7 files changed, 453 insertions(+), 28 deletions(-) create mode 100644 kernel/src/framebuffer.rs diff --git a/Cargo.lock b/Cargo.lock index 4db15b7..a4e1b7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1264,6 +1264,7 @@ dependencies = [ "embassy-usb", "embedded-graphics", "embedded-hal 0.2.7", + "embedded-hal 1.0.0", "embedded-hal-async", "embedded-hal-bus", "embedded-layout", @@ -1991,7 +1992,7 @@ dependencies = [ [[package]] name = "st7365p-lcd" version = "0.11.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=2a484aaab5f6b9824cc813fe4ae087250c9e39c1#2a484aaab5f6b9824cc813fe4ae087250c9e39c1" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=1d15123929fa7ef73d5d6aead7faf1bba50ce915#1d15123929fa7ef73d5d6aead7faf1bba50ce915" dependencies = [ "bitvec", "embedded-graphics-core", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 327cc02..a7fe247 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -60,6 +60,7 @@ cyw43-pio = { version = "0.3.0", optional = true } embedded-hal-bus = { version = "0.3.0", features = ["async"] } embedded-hal = "0.2.7" +embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" } embedded-hal-async = "1.0.0" cortex-m = { version = "0.7.7" } cortex-m-rt = "0.7.5" @@ -71,7 +72,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" embedded-sdmmc = { version = "0.9", default-features = false } -st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "2a484aaab5f6b9824cc813fe4ae087250c9e39c1" } # async branch +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "1d15123929fa7ef73d5d6aead7faf1bba50ce915" } # async branch embedded-graphics = { version = "0.8.1" } embedded-text = "0.7.2" embedded-layout = "0.4.2" diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 8be0c82..453d2e9 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -37,12 +37,8 @@ pub extern "Rust" fn sleep(ticks: u64) { // TODO: maybe return result pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { loop { - let fb = FRAMEBUFFER.get().try_lock(); - if let Ok(mut fb) = fb { - fb.draw_iter(pixels.iter().copied()).unwrap(); - return; - } - sleep(1) + unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } + return; } } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index c362bd7..ff18142 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,19 +1,17 @@ +use crate::framebuffer::AtomicFrameBuffer; use embassy_rp::{ Peri, gpio::{Level, Output}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, spi::{Async, Spi}, }; -use embassy_sync::{ - blocking_mutex::raw::CriticalSectionRawMutex, lazy_lock::LazyLock, mutex::Mutex, -}; use embassy_time::{Delay, Timer}; use embedded_graphics::{ draw_target::DrawTarget, pixelcolor::{Rgb565, RgbColor}, }; use embedded_hal_bus::spi::ExclusiveDevice; -use st7365p_lcd::{FrameBuffer, ST7365P}; +use st7365p_lcd::ST7365P; type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, @@ -25,9 +23,7 @@ type DISPLAY = ST7365P< pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; -type FB = FrameBuffer; -pub static FRAMEBUFFER: LazyLock> = - LazyLock::new(|| Mutex::new(FrameBuffer::new())); +pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new(); pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -44,26 +40,25 @@ pub async fn init_display( true, Delay, ); - let mut fb = FRAMEBUFFER.get().lock().await; display.init().await.unwrap(); display.set_custom_orientation(0x40).await.unwrap(); - display.draw(&mut fb).await.unwrap(); + unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } display.set_on().await.unwrap(); display } pub async fn clear_fb() { - let mut fb = FRAMEBUFFER.get().lock().await; - let fb = &mut *fb; - fb.clear(Rgb565::BLACK).unwrap(); + unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() } } pub async fn display_handler(mut display: DISPLAY) { loop { - { - let mut fb = FRAMEBUFFER.get().lock().await; - display.partial_draw_batched(&mut fb).await.unwrap(); + unsafe { + FRAMEBUFFER + .partial_draw_batched(&mut display) + .await + .unwrap() } Timer::after_millis(32).await; // 30 fps diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs new file mode 100644 index 0000000..11a832e --- /dev/null +++ b/kernel/src/framebuffer.rs @@ -0,0 +1,432 @@ +use crate::display::{SCREEN_HEIGHT, SCREEN_WIDTH}; +use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_sync::lazy_lock::LazyLock; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::{ + Rgb565, + raw::{RawData, RawU16}, + }, + prelude::*, + primitives::Rectangle, +}; +use embedded_hal_2::digital::OutputPin; +use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; +use heapless::Vec; +use st7365p_lcd::{FrameBuffer, ST7365P}; + +pub const TILE_SIZE: usize = 16; // 16x16 tile +pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles + +// Group of tiles for batching +pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer +type MetaTileVec = heapless::Vec; + +const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; + +static mut BUFFER: [u16; SIZE] = [0; SIZE]; + +static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { + let mut tiles = Vec::new(); + for _ in 0..TILE_COUNT { + tiles.push(AtomicBool::new(true)); + } + tiles +}); + +#[allow(dead_code)] +pub struct AtomicFrameBuffer; + +impl AtomicFrameBuffer { + pub const fn new() -> Self { + Self + } + + fn mark_tiles_dirty(&mut self, rect: Rectangle) { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let start_tx = (rect.top_left.x as usize) / TILE_SIZE; + let end_tx = ((rect.top_left.x + rect.size.width as i32 - 1) as usize) / TILE_SIZE; + let start_ty = (rect.top_left.y as usize) / TILE_SIZE; + let end_ty = ((rect.top_left.y + rect.size.height as i32 - 1) as usize) / TILE_SIZE; + + for ty in start_ty..=end_ty { + for tx in start_tx..=end_tx { + let tile_idx = ty * tiles_x + tx; + unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Relaxed) }; + } + } + } + + fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> { + unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + + Ok(()) + } + + fn set_pixels_buffered>( + &mut self, + sx: u16, + sy: u16, + ex: u16, + ey: u16, + colors: P, + ) -> Result<(), ()> { + if sx >= self.size().width as u16 - 1 + || ex >= self.size().width as u16 - 1 + || sy >= self.size().height as u16 - 1 + || ey >= self.size().height as u16 - 1 + { + return Err(()); // Bounds check + } + + let mut color_iter = colors.into_iter(); + + for y in sy..=ey { + for x in sx..=ex { + if let Some(color) = color_iter.next() { + unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + } else { + return Err(()); // Not enough data + } + } + } + + // Optional: check that we consumed *exactly* the right amount + if color_iter.next().is_some() { + return Err(()); // Too much data + } + + Ok(()) + } + + // walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates + fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec { + let mut meta_tiles: MetaTileVec = Vec::new(); + + for ty in 0..tiles_y { + let mut tx = 0; + while tx < tiles_x { + let idx = ty * tiles_x + tx; + if !unsafe { DIRTY_TILES.get()[idx].load(Ordering::Acquire) } { + tx += 1; + continue; + } + + // Start meta-tile at this tile + let mut width_tiles = 1; + let height_tiles = 1; + + // Grow horizontally, but keep under MAX_TILES_PER_METATILE + while tx + width_tiles < tiles_x + && unsafe { + DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Relaxed) + } + && (width_tiles + height_tiles) <= MAX_META_TILES + { + width_tiles += 1; + } + + // TODO: for simplicity, skipped vertical growth + + for x_off in 0..width_tiles { + unsafe { + DIRTY_TILES.get()[ty * tiles_x + tx + x_off] + .store(false, Ordering::Release); + }; + } + + // new meta-tile pos + let rect = Rectangle::new( + Point::new((tx * TILE_SIZE) as i32, (ty * TILE_SIZE) as i32), + Size::new( + (width_tiles * TILE_SIZE) as u32, + (height_tiles * TILE_SIZE) as u32, + ), + ); + + if meta_tiles.push(rect).is_err() { + return meta_tiles; + }; + + tx += width_tiles; + } + } + + meta_tiles + } + + /// Sends the entire framebuffer to the display + pub async fn draw( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + { + display + .set_pixels_buffered( + 0, + 0, + self.size().width as u16 - 1, + self.size().height as u16 - 1, + unsafe { &BUFFER }, + ) + .await?; + + unsafe { + for tile in DIRTY_TILES.get_mut().iter() { + tile.store(false, Ordering::Release); + } + }; + + Ok(()) + } + + /// Sends only dirty tiles (16x16px) individually to the display without batching + pub async fn partial_draw( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + { + if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + + let mut tile_buffer = [0u16; TILE_SIZE * TILE_SIZE]; + + for ty in 0..tiles_y { + for tx in 0..tiles_x { + if unsafe { !DIRTY_TILES.get()[ty * tiles_x + tx].load(Ordering::Acquire) } { + continue; + } + + let x = tx * TILE_SIZE; + let y = ty * TILE_SIZE; + + // Copy pixels for the tile into tile_buffer + for row in 0..TILE_SIZE { + for col in 0..TILE_SIZE { + let actual_x = x + col; + let actual_y = y + row; + + if actual_x < SCREEN_WIDTH && actual_y < SCREEN_HEIGHT { + let idx = actual_y * SCREEN_WIDTH + actual_x; + tile_buffer[row * TILE_SIZE + col] = unsafe { BUFFER[idx] }; + } else { + // Out of bounds, fill with zero (or background) + tile_buffer[row * TILE_SIZE + col] = 0; + } + } + } + + // Send the tile's pixel data to the display + display + .set_pixels_buffered( + x as u16, + y as u16, + (x + TILE_SIZE - 1).min(SCREEN_WIDTH - 1) as u16, + (y + TILE_SIZE - 1).min(SCREEN_HEIGHT - 1) as u16, + &tile_buffer, + ) + .await?; + + // Mark tile as clean + unsafe { + DIRTY_TILES.get_mut()[ty * tiles_x + tx].store(false, Ordering::Release) + }; + } + } + } + + Ok(()) + } + + /// Sends only dirty tiles (16x16px) in batches to the display + pub async fn partial_draw_batched( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, + { + if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + + let meta_tiles = self.find_meta_tiles(tiles_x, tiles_y); + + // buffer for copying meta tiles before sending to display + let mut pixel_buffer: heapless::Vec = + Vec::new(); + + for rect in meta_tiles { + let rect_width = rect.size.width as usize; + let rect_height = rect.size.height as usize; + let rect_x = rect.top_left.x as usize; + let rect_y = rect.top_left.y as usize; + + pixel_buffer.clear(); + + for row in 0..rect_height { + let y = rect_y + row; + let start = y * SCREEN_WIDTH + rect_x; + let end = start + rect_width; + + // Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS + pixel_buffer + .extend_from_slice(unsafe { &BUFFER[start..end] }) + .unwrap(); + } + + display + .set_pixels_buffered( + rect_x as u16, + rect_y as u16, + (rect_x + rect_width - 1) as u16, + (rect_y + rect_height - 1) as u16, + &pixel_buffer, + ) + .await?; + + // walk the meta-tile and set as clean + let start_tx = rect_x / TILE_SIZE; + let start_ty = rect_y / TILE_SIZE; + let end_tx = (rect_x + rect_width - 1) / TILE_SIZE; + let end_ty = (rect_y + rect_height - 1) / TILE_SIZE; + + for ty in start_ty..=end_ty { + for tx in start_tx..=end_tx { + let tile_idx = ty * tiles_x + tx; + unsafe { DIRTY_TILES.get_mut()[tile_idx].store(false, Ordering::Release) }; + } + } + } + } + + Ok(()) + } +} + +impl DrawTarget for AtomicFrameBuffer { + type Error = (); + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let mut dirty_rect: Option = None; + + for Pixel(coord, color) in pixels { + if coord.x >= 0 && coord.y >= 0 { + let x = coord.x as i32; + let y = coord.y as i32; + + if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { + unsafe { + BUFFER[(y as usize) * SCREEN_WIDTH + (x as usize)] = + RawU16::from(color).into_inner() + }; + + if let Some(ref mut rect) = dirty_rect { + rect.top_left.x = rect.top_left.x.min(x); + rect.top_left.y = rect.top_left.y.min(y); + let max_x = (rect.top_left.x + rect.size.width as i32 - 1).max(x); + let max_y = (rect.top_left.y + rect.size.height as i32 - 1).max(y); + rect.size.width = (max_x - rect.top_left.x + 1) as u32; + rect.size.height = (max_y - rect.top_left.y + 1) as u32; + } else { + dirty_rect = Some(Rectangle::new(Point::new(x, y), Size::new(1, 1))); + } + } + } + } + + if let Some(rect) = dirty_rect { + self.mark_tiles_dirty(rect); + } + + Ok(()) + } + + fn fill_contiguous(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> + where + I: IntoIterator, + { + let drawable_area = area.intersection(&Rectangle::new(Point::zero(), self.size())); + + if drawable_area.size != Size::zero() { + // We assume that `colors` iterator is in row-major order for the original `area` + // So we must skip rows/pixels that are clipped + let area_width = area.size.width; + let area_height = area.size.height; + let mut colors = colors.into_iter(); + + for y in 0..area_height { + for x in 0..area_width { + let p = area.top_left + Point::new(x as i32, y as i32); + + if drawable_area.contains(p) { + if let Some(color) = colors.next() { + self.set_pixel( + p.x as u16, + p.y as u16, + RawU16::from(color).into_inner(), + )?; + } else { + break; + } + } else { + // Still need to consume the color even if not used! + let _ = colors.next(); + } + } + } + + self.mark_tiles_dirty(*area); + } + + Ok(()) + } + + fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { + self.fill_contiguous( + area, + core::iter::repeat(color).take((self.size().width * self.size().height) as usize), + ) + } + + fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { + self.set_pixels_buffered( + 0, + 0, + self.size().width as u16 - 1, + self.size().height as u16 - 1, + core::iter::repeat(RawU16::from(color).into_inner()) + .take((self.size().width * self.size().height) as usize), + )?; + + unsafe { + for tile in DIRTY_TILES.get_mut().iter() { + tile.store(true, Ordering::Relaxed); + } + } + + Ok(()) + } +} + +impl OriginDimensions for AtomicFrameBuffer { + fn size(&self) -> Size { + Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) + } +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 84c2ae9..73150bb 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -10,6 +10,7 @@ extern crate alloc; mod abi; mod display; mod elf; +mod framebuffer; mod peripherals; mod scsi; mod storage; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 481de65..03122c1 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -74,9 +74,8 @@ async fn draw_selection() { guard.selections.clone() }; - let mut fb = FRAMEBUFFER.get().lock().await; let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); - let display_area = fb.bounding_box(); + let display_area = unsafe { FRAMEBUFFER.bounding_box() }; const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; let no_bins = String::from_str(NO_BINS).unwrap(); @@ -90,13 +89,13 @@ async fn draw_selection() { ), text_style, ) - .draw(&mut *fb) + .draw(unsafe { &mut FRAMEBUFFER }) .unwrap(); } else { let mut file_names = file_names.iter(); let Some(first) = file_names.next() else { Text::new(NO_BINS, Point::zero(), text_style) - .draw(&mut *fb) + .draw(unsafe { &mut FRAMEBUFFER }) .unwrap(); return; @@ -116,7 +115,7 @@ async fn draw_selection() { .with_alignment(horizontal::Center) .arrange() .align_to(&display_area, horizontal::Center, vertical::Center) - .draw(&mut *fb) + .draw(unsafe { &mut FRAMEBUFFER }) .unwrap(); } } From d9340369653094ff255ee13d4706bc8cc4b34bda Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 14 Sep 2025 19:12:17 -0600 Subject: [PATCH 26/27] screen tearing kinda poop --- abi_sys/src/lib.rs | 6 +++--- kernel/src/abi.rs | 13 ++++++------- kernel/src/main.rs | 6 ++++-- user-apps/calculator/src/main.rs | 18 ++++++++++++++---- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index d821afc..35f8a57 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -40,13 +40,13 @@ pub fn print(msg: &str) { } } -pub type SleepAbi = extern "Rust" fn(ticks: u64); +pub type SleepAbi = extern "Rust" fn(ms: u64); -pub fn sleep(ticks: u64) { +pub fn sleep(ms: u64) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; let f: SleepAbi = core::mem::transmute(ptr); - f(ticks); + f(ms); } } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 453d2e9..8451ffd 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -4,6 +4,7 @@ use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi, SleepAbi}; use alloc::boxed::Box; use defmt::info; use embassy_futures::block_on; +use embassy_rp::clocks::clk_sys_freq; use embassy_time::Timer; use embedded_graphics::{ Drawable, @@ -26,9 +27,10 @@ pub extern "Rust" fn print(msg: &str) { defmt::info!("{:?}", msg); } -pub extern "Rust" fn sleep(ticks: u64) { - for _ in 0..ticks { - for _ in 0..100 { +pub extern "Rust" fn sleep(ms: u64) { + let cycles_per_ms = clk_sys_freq() / 1000; + for _ in 0..ms { + for _ in 0..cycles_per_ms { cortex_m::asm::nop(); } } @@ -36,10 +38,7 @@ pub extern "Rust" fn sleep(ticks: u64) { // TODO: maybe return result pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { - loop { - unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } - return; - } + unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } } pub extern "Rust" fn get_key() -> Option { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 73150bb..a773373 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -259,8 +259,10 @@ static mut KEY_CACHE: Queue = Queue::new(); async fn get_keys() { if let Some(event) = read_keyboard_fifo().await { - unsafe { - let _ = KEY_CACHE.enqueue(event); + if let KeyState::Pressed = event.state { + unsafe { + let _ = KEY_CACHE.enqueue(event); + } } } } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 39810e8..a50f9f7 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,7 +2,7 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, display::Display, embassy_time, get_key, print}; +use abi::{KeyCode, display::Display, embassy_time, get_key, print, sleep}; use alloc::{boxed::Box, string::String, vec}; use core::{panic::PanicInfo, pin::Pin}; use embedded_graphics::{ @@ -10,7 +10,8 @@ use embedded_graphics::{ geometry::{Dimensions, Point}, mono_font::{MonoTextStyle, ascii::FONT_6X10}, pixelcolor::Rgb565, - prelude::RgbColor, + prelude::{Primitive, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle}, text::{Alignment, Text}, }; @@ -28,6 +29,16 @@ pub async fn main() { let mut text = vec!['H', 'E', 'L', 'L', 'O']; loop { + // First, clear the text area + let text_area = Rectangle::new( + display.bounding_box().center() + Point::new(0, 0), + Size::new(320, 320), + ); + Rectangle::new(text_area.top_left, text_area.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(&mut display) + .unwrap(); + Text::with_alignment( &text.iter().cloned().collect::(), display.bounding_box().center() + Point::new(0, 15), @@ -37,8 +48,6 @@ pub async fn main() { .draw(&mut display) .unwrap(); - embassy_time::Timer::after_millis(1000).await; - if let Some(event) = get_key() { print("User got event"); match event.key { @@ -51,6 +60,7 @@ pub async fn main() { _ => (), } } + sleep(1000) } } From 505450c8b8a9d28ea958c947e492f29758f37a82 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 14 Sep 2025 20:46:43 -0600 Subject: [PATCH 27/27] working dynamically loaded app --- Cargo.lock | 78 ++++++++++++++++++++++++++++---- abi_sys/src/lib.rs | 2 +- kernel/src/display.rs | 8 +++- kernel/src/framebuffer.rs | 2 +- kernel/src/main.rs | 2 +- user-apps/calculator/src/main.rs | 47 +++++++++++-------- 6 files changed, 104 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4e1b7f..22bdab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.1.0" dependencies = [ "abi_sys", "embassy-time 0.5.0", - "embedded-graphics", + "embedded-graphics 0.8.1", "shared", "spin", "talc", @@ -28,7 +28,7 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ - "embedded-graphics", + "embedded-graphics 0.8.1", "shared", ] @@ -226,7 +226,7 @@ name = "calculator" version = "0.1.0" dependencies = [ "abi", - "embedded-graphics", + "embedded-graphics 0.8.1", ] [[package]] @@ -801,6 +801,19 @@ dependencies = [ "embedded-io-async", ] +[[package]] +name = "embedded-graphics" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "750082c65094fbcc4baf9ba31583ce9a8bb7f52cadfb96f6164b1bc7f922f32b" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core 0.3.3", + "float-cmp 0.8.0", + "micromath 1.1.1", +] + [[package]] name = "embedded-graphics" version = "0.8.1" @@ -810,9 +823,19 @@ dependencies = [ "az", "byteorder", "defmt 0.3.100", - "embedded-graphics-core", - "float-cmp", - "micromath", + "embedded-graphics-core 0.4.0", + "float-cmp 0.9.0", + "micromath 2.1.0", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa" +dependencies = [ + "az", + "byteorder", ] [[package]] @@ -893,7 +916,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a90553247f2b05c59ac7894ea13d830636c2b1203fa03bff400eddbd1fa9f52" dependencies = [ - "embedded-graphics", + "embedded-graphics 0.8.1", "embedded-layout-macros", ] @@ -921,6 +944,16 @@ dependencies = [ "heapless", ] +[[package]] +name = "embedded-snake" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af598ad20b839c26b95be615a94619a069303ec776a58aef6dc0e86cf7eabbb8" +dependencies = [ + "embedded-graphics 0.7.1", + "rand_core 0.6.4", +] + [[package]] name = "embedded-storage" version = "0.3.1" @@ -943,7 +976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005680edc0d075af5e02d5788ca291737bd9aba7fc404ae031cc9dfa715e5f7d" dependencies = [ "az", - "embedded-graphics", + "embedded-graphics 0.8.1", "object-chain", ] @@ -992,6 +1025,15 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -1262,7 +1304,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-time 0.5.0", "embassy-usb", - "embedded-graphics", + "embedded-graphics 0.8.1", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -1396,6 +1438,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "micromath" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94" + [[package]] name = "micromath" version = "2.1.0" @@ -1970,6 +2018,16 @@ dependencies = [ "rgb", ] +[[package]] +name = "snake" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics 0.8.1", + "embedded-snake", + "rand_core 0.6.4", +] + [[package]] name = "spin" version = "0.10.0" @@ -1995,7 +2053,7 @@ version = "0.11.0" source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=1d15123929fa7ef73d5d6aead7faf1bba50ce915#1d15123929fa7ef73d5d6aead7faf1bba50ce915" dependencies = [ "bitvec", - "embedded-graphics-core", + "embedded-graphics-core 0.4.0", "embedded-hal 1.0.0", "embedded-hal-async", "heapless", diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 35f8a57..60e7131 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -44,7 +44,7 @@ pub type SleepAbi = extern "Rust" fn(ms: u64); pub fn sleep(ms: u64) { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; + let ptr = CALL_ABI_TABLE[CallAbiTable::Sleep as usize]; let f: SleepAbi = core::mem::transmute(ptr); f(ms); } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index ff18142..14698b2 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -9,6 +9,7 @@ use embassy_time::{Delay, Timer}; use embedded_graphics::{ draw_target::DrawTarget, pixelcolor::{Rgb565, RgbColor}, + prelude::Dimensions, }; use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; @@ -48,8 +49,11 @@ pub async fn init_display( display } -pub async fn clear_fb() { - unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() } +pub fn clear_fb() { + let bounds = unsafe { FRAMEBUFFER.bounding_box() }; + unsafe { + let _ = FRAMEBUFFER.fill_solid(&bounds, Rgb565::BLACK); + } } pub async fn display_handler(mut display: DISPLAY) { diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 11a832e..402551c 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -29,7 +29,7 @@ static mut BUFFER: [u16; SIZE] = [0; SIZE]; static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { let mut tiles = Vec::new(); for _ in 0..TILE_COUNT { - tiles.push(AtomicBool::new(true)); + tiles.push(AtomicBool::new(true)).unwrap(); } tiles }); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a773373..7f208a8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -143,7 +143,7 @@ async fn userland_task() { *state = TaskState::Kernel; } - // clear_fb().await; // blocks future exec? + // clear_fb(); defmt::info!("Executing Binary"); entry().await; diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index a50f9f7..6808c7e 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -26,41 +26,48 @@ pub async fn main() { let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); - let mut text = vec!['H', 'E', 'L', 'L', 'O']; + let mut text = vec!['T', 'y', 'p', 'e']; + let mut dirty = true; + let mut last_bounds: Option = None; loop { - // First, clear the text area - let text_area = Rectangle::new( - display.bounding_box().center() + Point::new(0, 0), - Size::new(320, 320), - ); - Rectangle::new(text_area.top_left, text_area.size) - .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) - .draw(&mut display) - .unwrap(); + if dirty { + if let Some(bounds) = last_bounds { + Rectangle::new(bounds.top_left, bounds.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(&mut display) + .unwrap(); + } - Text::with_alignment( - &text.iter().cloned().collect::(), - display.bounding_box().center() + Point::new(0, 15), - character_style, - Alignment::Center, - ) - .draw(&mut display) - .unwrap(); + let text = text.iter().cloned().collect::(); + let aligned_text = Text::with_alignment( + &text, + display.bounding_box().center(), + character_style, + Alignment::Center, + ); + last_bounds = Some(aligned_text.bounding_box()); + + aligned_text.draw(&mut display).unwrap(); + dirty = false; + } if let Some(event) = get_key() { - print("User got event"); + dirty = true; match event.key { KeyCode::Char(ch) => { text.push(ch); } + KeyCode::Del => { + text.clear(); + } KeyCode::Backspace => { text.pop(); } + KeyCode::Esc => return, _ => (), } } - sleep(1000) } }