From 2d31e62d8254075c492e6499b2bff8646195415e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 17 Jul 2025 23:34:20 -0600 Subject: [PATCH 01/64] Squash merge keyboard-writer into main --- Cargo.lock | 225 +++++++++++++++++++++++++++++------- Cargo.toml | 5 +- src/display.rs | 140 +++++++++------------- src/main.rs | 87 +++++++++----- src/peripherals/keyboard.rs | 24 ++-- src/peripherals/mod.rs | 24 ++-- 6 files changed, 313 insertions(+), 192 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe98260..13db5ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ - "term 1.0.2", + "term 1.1.0", ] [[package]] @@ -138,6 +138,18 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -447,12 +459,13 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fea5ef5bed4d3468dfd44f5c9fa4cda8f54c86d4fb4ae683eacf9d39e2ea12" +checksum = "8578db196d74db92efdd5ebc546736dac1685499ee245b22eff92fa5e4b57945" dependencies = [ "embassy-futures", - "embassy-sync 0.6.2", + "embassy-hal-internal 0.3.0", + "embassy-sync 0.7.0", "embassy-time", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -505,6 +518,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "embassy-hal-internal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" +dependencies = [ + "num-traits", +] + [[package]] name = "embassy-net-driver" version = "0.2.0" @@ -513,13 +535,13 @@ checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" [[package]] name = "embassy-net-driver-channel" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +checksum = "25a567ab50319d866ad5e6c583ed665ba9b07865389644d3d82e45bf1497c934" dependencies = [ "embassy-futures", "embassy-net-driver", - "embassy-sync 0.6.2", + "embassy-sync 0.7.0", ] [[package]] @@ -536,7 +558,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", "embassy-time", "embassy-usb-driver", @@ -574,7 +596,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", "embassy-time", "embassy-time-driver", @@ -666,11 +688,12 @@ dependencies = [ [[package]] name = "embassy-usb-driver" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" +checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" dependencies = [ - "defmt 0.3.100", + "defmt 1.0.1", + "embedded-io-async", ] [[package]] @@ -844,6 +867,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -989,15 +1018,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1006,9 +1026,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1022,7 +1042,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1101,7 +1121,7 @@ dependencies = [ "regex-syntax 0.8.5", "sha3", "string_cache", - "term 1.0.2", + "term 1.1.0", "unicode-xid", "walkdir", ] @@ -1288,7 +1308,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1355,10 +1375,13 @@ dependencies = [ "embedded-hal-async", "embedded-hal-bus", "embedded-sdmmc", + "heapless", "panic-probe", "portable-atomic", + "spin", "st7365p-lcd", "static_cell", + "talc", "trouble-host", ] @@ -1541,6 +1564,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand_core" version = "0.6.4" @@ -1604,9 +1633,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] @@ -1728,12 +1757,24 @@ dependencies = [ ] [[package]] -name = "st7365p-lcd" +name = "spin" version = "0.10.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs#d751e8d30f1a3f964ffe05e4bb16f82112fbefce" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" dependencies = [ + "lock_api", +] + +[[package]] +name = "st7365p-lcd" +version = "0.11.0" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?branch=async#87abf450404865dcb535292e9e1a6a2457fd4599" +dependencies = [ + "bitvec", "embedded-graphics-core", "embedded-hal 1.0.0", + "embedded-hal-async", + "heapless", "nb 1.1.0", ] @@ -1792,6 +1833,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "talc" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ae828aa394de34c7de08f522d1b86bd1c182c668d27da69caadda00590f26d" +dependencies = [ + "lock_api", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "term" version = "0.7.0" @@ -1805,12 +1861,11 @@ dependencies = [ [[package]] name = "term" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a984c8d058c627faaf5e8e2ed493fa3c51771889196de1016cf9c1c6e90d750" +checksum = "a43bddab41f8626c7bdaab872bbba75f8df5847b516d77c569c746e2ae5eb746" dependencies = [ - "home", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] @@ -2061,7 +2116,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -2076,7 +2131,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] @@ -2085,14 +2149,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -2101,48 +2181,105 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index 5a9425d..6477884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,10 @@ 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" } +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/src/display.rs b/src/display.rs index fe3f2b0..7cf9ab4 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,21 +1,35 @@ +use core::sync::atomic::Ordering; + +use defmt::info; use embassy_rp::{ gpio::{Level, Output}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, - spi::{Blocking, Spi}, + spi::{Async, Spi}, }; -use embassy_time::{Delay, Timer}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; +use embassy_time::{Delay, Instant, Timer}; use embedded_graphics::{ - Drawable, Pixel, - pixelcolor::{Rgb565, raw::RawU16}, - prelude::{DrawTarget, OriginDimensions, Point, Primitive, RawData, RgbColor, Size}, - primitives::{PrimitiveStyle, Rectangle}, + Drawable, + draw_target::DrawTarget, + mono_font::{MonoTextStyle, ascii::FONT_10X20}, + pixelcolor::Rgb565, + prelude::{Dimensions, Point, RgbColor, Size}, + primitives::Rectangle, + text::{Alignment, Text}, }; use embedded_hal_bus::spi::ExclusiveDevice; -use st7365p_lcd::{Orientation, ST7365P}; +use portable_atomic::AtomicBool; +use st7365p_lcd::{FrameBuffer, ST7365P}; -#[embassy_executor::task] -pub async fn display_task( - spi: Spi<'static, SPI1, Blocking>, +use crate::LAST_TEXT_RECT; + +const SCREEN_WIDTH: usize = 320; +const SCREEN_HEIGHT: usize = 320; + +pub static DISPLAY_SIGNAL: Signal = Signal::new(); + +pub async fn display_handler( + spi: Spi<'static, SPI1, Async>, cs: PIN_13, data: PIN_14, reset: PIN_15, @@ -25,89 +39,49 @@ pub async fn display_task( spi_device, Output::new(data, Level::Low), Some(Output::new(reset, Level::High)), - true, false, - 320, - 320, + true, + Delay, ); - display.init(&mut Delay).unwrap(); - display.set_orientation(&Orientation::Landscape).unwrap(); - let mut virtual_display = VirtualDisplay::new(display, 320 / 2, 320 / 2); + let mut framebuffer: FrameBuffer< + SCREEN_WIDTH, + SCREEN_HEIGHT, + { SCREEN_WIDTH * SCREEN_HEIGHT }, + > = FrameBuffer::new(); + display.init().await.unwrap(); + display.set_custom_orientation(0x40).await.unwrap(); + framebuffer.draw(&mut display).await.unwrap(); + display.set_on().await.unwrap(); - let thin_stroke = PrimitiveStyle::with_stroke(Rgb565::RED, 20); - - Rectangle::new(Point::new(10, 10), Size::new(100, 100)) - .into_styled(thin_stroke) - .draw(&mut virtual_display) - .unwrap(); + DISPLAY_SIGNAL.signal(()); loop { - Timer::after_millis(500).await; - } -} + DISPLAY_SIGNAL.wait().await; -/// simple abstraction over real display & resolution to reduce frame buffer size -/// by cutting the resolution by 1/4 -struct VirtualDisplay { - display: ST7365P< - ExclusiveDevice, Output<'static>, Delay>, - Output<'static>, - Output<'static>, - >, - width: u32, - height: u32, -} + let text_string = crate::STRING.lock().await.clone(); -impl VirtualDisplay { - pub fn new( - display: ST7365P< - ExclusiveDevice, Output<'static>, Delay>, - Output<'static>, - Output<'static>, - >, - new_width: u32, - new_height: u32, - ) -> Self { - Self { - display, - width: new_width, - height: new_height, - } - } -} + let text = Text::with_alignment( + &text_string, + Point::new(160, 160), + MonoTextStyle::new(&FONT_10X20, Rgb565::RED), + Alignment::Center, + ); -impl DrawTarget for VirtualDisplay { - type Color = Rgb565; - type Error = (); - - fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> - where - I: IntoIterator>, - { - for Pixel(coord, color) in pixels.into_iter() { - // Check bounds on the *virtual* (already reduced) resolution - if coord.x >= 0 - && coord.y >= 0 - && coord.x < self.width as i32 - && coord.y < self.height as i32 - { - let px = coord.x as u16 * 2; - let py = coord.y as u16 * 2; - let raw_color = RawU16::from(color).into_inner(); - - // Draw the 2x2 block on the underlying hardware - self.display.set_pixel(px, py, raw_color)?; - self.display.set_pixel(px + 1, py, raw_color)?; - self.display.set_pixel(px, py + 1, raw_color)?; - self.display.set_pixel(px + 1, py + 1, raw_color)?; + { + 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()); } - Ok(()) - } -} -impl OriginDimensions for VirtualDisplay { - fn size(&self) -> Size { - Size::new(self.width, self.height) + text.draw(&mut framebuffer).unwrap(); + + let start = Instant::now(); + framebuffer + .partial_draw_batched(&mut display) + .await + .unwrap(); + info!("Elapsed {}ms", start.elapsed().as_millis()); } } diff --git a/src/main.rs b/src/main.rs index 56b63f8..e754b93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,57 +1,82 @@ #![feature(impl_trait_in_assoc_type)] +#![feature(ascii_char)] #![no_std] #![no_main] -#[cfg(feature = "defmt")] -use defmt::*; +use crate::{ + display::DISPLAY_SIGNAL, + peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, +}; + 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::spi::Spi; -use embassy_rp::{ - bind_interrupts, - gpio::{Level, Output}, - i2c, - i2c::I2c, - spi, -}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::channel::Channel; +use embassy_rp::{i2c, i2c::I2c, spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; use embassy_time::Timer; -use embedded_hal_bus::spi::ExclusiveDevice; -use embedded_sdmmc::asynchronous::{File, SdCard, ShortFileName, VolumeIdx, VolumeManager}; -use static_cell::StaticCell; +use embedded_graphics::primitives::Rectangle; +use heapless::String; mod peripherals; -use peripherals::{keyboard::KeyEvent, peripherals_task}; +use peripherals::conf_peripherals; mod display; -use display::display_task; +use display::display_handler; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::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) { +async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - static KEYBOARD_EVENTS: StaticCell> = StaticCell::new(); - let keyboard_events = KEYBOARD_EVENTS.init(Channel::new()); + STRING.lock().await.push_str("Press Del").unwrap(); // configure keyboard event handler let mut config = i2c::Config::default(); - config.frequency = 100_000; + config.frequency = 400_000; let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); - spawner - .spawn(peripherals_task(i2c1, keyboard_events.sender())) - .unwrap(); + conf_peripherals(i2c1).await; - // // configure display handler - // let mut config = spi::Config::default(); - // config.frequency = 16_000_000; - // let spi1 = spi::Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); - // spawner - // .spawn(display_task(spi1, p.PIN_13, p.PIN_14, p.PIN_15)) - // .unwrap(); + 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; } diff --git a/src/peripherals/keyboard.rs b/src/peripherals/keyboard.rs index eaa07c7..208a335 100644 --- a/src/peripherals/keyboard.rs +++ b/src/peripherals/keyboard.rs @@ -1,12 +1,3 @@ -use defmt::{info, warn}; -use embassy_rp::{ - i2c::{Async, I2c}, - peripherals::I2C1, -}; -use embassy_sync::{ - blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}, - channel::Sender, -}; use crate::peripherals::PERIPHERAL_BUS; @@ -17,7 +8,7 @@ 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(channel: &mut Sender<'static, NoopRawMutex, KeyEvent, 10>) { +pub async fn read_keyboard_fifo() -> Option { let mut i2c = PERIPHERAL_BUS.get().lock().await; let i2c = i2c.as_mut().unwrap(); @@ -40,16 +31,15 @@ pub async fn read_keyboard_fifo(channel: &mut Sender<'static, NoopRawMutex, KeyE .await .is_ok() { - channel - .try_send(KeyEvent { - state: KeyState::from(event[0]), - key: KeyCode::from(event[1]), - mods: Modifiers::NONE, - }) - .expect("Failed to push key"); + return Some(KeyEvent { + state: KeyState::from(event[0]), + key: KeyCode::from(event[1]), + mods: Modifiers::NONE, + }); } } } + None } const REG_ID_DEB: u8 = 0x06; diff --git a/src/peripherals/mod.rs b/src/peripherals/mod.rs index b3e7d1a..d00c7ec 100644 --- a/src/peripherals/mod.rs +++ b/src/peripherals/mod.rs @@ -5,13 +5,10 @@ use embassy_rp::{ i2c::{Async, I2c}, peripherals::I2C1, }; -use embassy_sync::{ - blocking_mutex::raw::NoopRawMutex, channel::Sender, lazy_lock::LazyLock, mutex::Mutex, -}; -use embassy_time::{Duration, Timer}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, lazy_lock::LazyLock, mutex::Mutex}; +use embassy_time::Timer; pub mod keyboard; -use keyboard::{KeyCode, KeyEvent, KeyState}; use crate::peripherals::keyboard::{configure_keyboard, read_keyboard_fifo}; @@ -25,23 +22,18 @@ const REG_ID_VER: u8 = 0x01; const REG_ID_RST: u8 = 0x08; const REG_ID_INT: u8 = 0x03; -#[embassy_executor::task] -pub async fn peripherals_task( - i2c: I2CBUS, - mut keyboard_channel: Sender<'static, NoopRawMutex, KeyEvent, 10>, -) { +pub async fn conf_peripherals(i2c: I2CBUS) { Timer::after(embassy_time::Duration::from_millis(100)).await; PERIPHERAL_BUS.get().lock().await.replace(i2c); configure_keyboard(200, 100).await; - set_lcd_backlight(255).await; - set_key_backlight(0).await; - loop { - Timer::after(Duration::from_millis(200)).await; - read_keyboard_fifo(&mut keyboard_channel).await; - } + // empty keys + while read_keyboard_fifo().await.is_some() {} + + // set_lcd_backlight(255).await; + set_key_backlight(0).await; } /// return major & minor mcu version From 0f4b4f8ffc4db90298ffcf616ebed3f8d4063dbd Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 19 Jul 2025 23:15:27 -0600 Subject: [PATCH 02/64] 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 03/64] 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 04/64] 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 05/64] 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 06/64] 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 07/64] 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 08/64] 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 09/64] 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 10/64] 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 11/64] 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 12/64] 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 13/64] 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 14/64] 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 15/64] 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 16/64] 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 17/64] 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 18/64] 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 19/64] 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 20/64] 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 21/64] 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 22/64] 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 23/64] 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 24/64] 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 25/64] 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 26/64] 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 27/64] 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 28/64] 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) } } From 4c6e16270eb52e5c5b9b1de63575718b178e1caf Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 15 Sep 2025 19:54:40 -0600 Subject: [PATCH 29/64] WIP scsi --- Cargo.lock | 126 ++++++++++++--------------- abi/Cargo.toml | 1 - abi/src/lib.rs | 1 - kernel/Cargo.toml | 3 +- kernel/src/display.rs | 4 +- kernel/src/framebuffer.rs | 18 ++-- kernel/src/main.rs | 143 ++++++++++++++++++------------- kernel/src/scsi/mod.rs | 36 +++++--- kernel/src/ui.rs | 101 +++++++--------------- kernel/src/usb.rs | 19 +--- user-apps/calculator/Cargo.toml | 1 + user-apps/calculator/src/main.rs | 31 +++---- 12 files changed, 222 insertions(+), 262 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22bdab6..7659019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,8 +17,7 @@ name = "abi" version = "0.1.0" dependencies = [ "abi_sys", - "embassy-time 0.5.0", - "embedded-graphics 0.8.1", + "embedded-graphics", "shared", "spin", "talc", @@ -28,7 +27,7 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ - "embedded-graphics 0.8.1", + "embedded-graphics", "shared", ] @@ -143,6 +142,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitfield" version = "0.13.2" @@ -226,7 +231,8 @@ name = "calculator" version = "0.1.0" dependencies = [ "abi", - "embedded-graphics 0.8.1", + "embedded-graphics", + "kolibri-embedded-gui", ] [[package]] @@ -801,19 +807,6 @@ 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" @@ -823,19 +816,9 @@ dependencies = [ "az", "byteorder", "defmt 0.3.100", - "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", + "embedded-graphics-core", + "float-cmp", + "micromath", ] [[package]] @@ -895,6 +878,18 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "embedded-iconoir" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52b9899b636b56d4e66834f7a90766d0bc6600c0f067d91ed0711b11fa3f5c8" +dependencies = [ + "bit_field", + "embedded-graphics", + "paste", + "static_assertions", +] + [[package]] name = "embedded-io" version = "0.6.1" @@ -916,7 +911,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a90553247f2b05c59ac7894ea13d830636c2b1203fa03bff400eddbd1fa9f52" dependencies = [ - "embedded-graphics 0.8.1", + "embedded-graphics", "embedded-layout-macros", ] @@ -944,16 +939,6 @@ 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" @@ -976,7 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005680edc0d075af5e02d5788ca291737bd9aba7fc404ae031cc9dfa715e5f7d" dependencies = [ "az", - "embedded-graphics 0.8.1", + "embedded-graphics", "object-chain", ] @@ -1025,15 +1010,6 @@ 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" @@ -1049,6 +1025,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -1210,6 +1192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", + "serde", "stable_deref_trait", ] @@ -1304,7 +1287,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-time 0.5.0", "embassy-usb", - "embedded-graphics 0.8.1", + "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -1314,6 +1297,7 @@ dependencies = [ "embedded-text", "goblin", "heapless", + "kolibri-embedded-gui", "num_enum 0.7.4", "panic-probe", "portable-atomic", @@ -1325,6 +1309,18 @@ dependencies = [ "trouble-host", ] +[[package]] +name = "kolibri-embedded-gui" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011f8f415e8c2f03e4ad752afcf1bb156a18926250401b1fe29d8feda644c140" +dependencies = [ + "embedded-graphics", + "embedded-iconoir", + "foldhash", + "heapless", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -1438,12 +1434,6 @@ 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" @@ -2018,16 +2008,6 @@ 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" @@ -2050,10 +2030,10 @@ dependencies = [ [[package]] name = "st7365p-lcd" version = "0.11.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=1d15123929fa7ef73d5d6aead7faf1bba50ce915#1d15123929fa7ef73d5d6aead7faf1bba50ce915" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=a784b9e6df0769371dfc522528e770cf8fc6403a#a784b9e6df0769371dfc522528e770cf8fc6403a" dependencies = [ "bitvec", - "embedded-graphics-core 0.4.0", + "embedded-graphics-core", "embedded-hal 1.0.0", "embedded-hal-async", "heapless", @@ -2066,6 +2046,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "static_cell" version = "2.1.1" diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 8a9af49..3273c92 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -9,4 +9,3 @@ 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 559c81f..db012a0 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -2,7 +2,6 @@ use abi_sys::draw_iter; 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/kernel/Cargo.toml b/kernel/Cargo.toml index a7fe247..248ce91 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -72,10 +72,11 @@ 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 = "1d15123929fa7ef73d5d6aead7faf1bba50ce915" } # async branch +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch embedded-graphics = { version = "0.8.1" } embedded-text = "0.7.2" embedded-layout = "0.4.2" +kolibri-embedded-gui = "0.1.0" static_cell = "2.1.1" bitflags = "2.9.1" diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 14698b2..d00a859 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -50,12 +50,12 @@ pub async fn init_display( } pub fn clear_fb() { - let bounds = unsafe { FRAMEBUFFER.bounding_box() }; unsafe { - let _ = FRAMEBUFFER.fill_solid(&bounds, Rgb565::BLACK); + FRAMEBUFFER.clear(Rgb565::WHITE).unwrap(); } } +#[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { loop { unsafe { diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 402551c..8b5fa4c 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -26,7 +26,7 @@ const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; static mut BUFFER: [u16; SIZE] = [0; SIZE]; -static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { +static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { let mut tiles = Vec::new(); for _ in 0..TILE_COUNT { tiles.push(AtomicBool::new(true)).unwrap(); @@ -71,10 +71,10 @@ impl AtomicFrameBuffer { 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 + if sx >= self.size().width as u16 + || ex >= self.size().width as u16 + || sy >= self.size().height as u16 + || ey >= self.size().height as u16 { return Err(()); // Bounds check } @@ -101,7 +101,7 @@ impl AtomicFrameBuffer { // 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(); + let mut meta_tiles: MetaTileVec = heapless::Vec::new(); for ty in 0..tiles_y { let mut tx = 0; @@ -415,10 +415,8 @@ impl DrawTarget for AtomicFrameBuffer { .take((self.size().width * self.size().height) as usize), )?; - unsafe { - for tile in DIRTY_TILES.get_mut().iter() { - tile.store(true, Ordering::Relaxed); - } + for tile in unsafe { DIRTY_TILES.get_mut() }.iter() { + tile.store(true, Ordering::Release); } Ok(()) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 7f208a8..6dd3154 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -19,12 +19,12 @@ mod usb; mod utils; use crate::{ - display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, - elf::load_binary, + display::{clear_fb, display_handler, init_display}, peripherals::{ conf_peripherals, - keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + keyboard::{KeyState, read_keyboard_fifo}, }, + scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, ui::{SELECTIONS, ui_handler}, usb::usb_handler, @@ -33,10 +33,12 @@ use abi_sys::EntryFn; 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_futures::{ + join::{join, join3, join5}, + yield_now, +}; use embassy_rp::{ Peri, gpio::{Input, Level, Output, Pull}, @@ -86,7 +88,7 @@ enum TaskState { } #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); spawn_core1( @@ -123,7 +125,7 @@ async fn main(_spawner: Spawner) { data: p.PIN_6, }; let executor0 = EXECUTOR0.init(Executor::new()); - executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(display, sd, mcu, p.USB)))); + executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(spawner, display, sd, mcu, p.USB)))); } // One-slot channel to pass EntryFn from core1 @@ -141,10 +143,11 @@ async fn userland_task() { { let mut state = TASK_STATE.lock().await; *state = TaskState::Kernel; + TASK_STATE_CHANGED.signal(()); + // clear_fb(); + MSC_SHUTDOWN.signal(()); } - // clear_fb(); - defmt::info!("Executing Binary"); entry().await; @@ -152,6 +155,8 @@ async fn userland_task() { { let mut state = TASK_STATE.lock().await; *state = TaskState::Ui; + TASK_STATE_CHANGED.signal(()); + // clear_fb(); } } } @@ -181,16 +186,15 @@ struct Mcu { data: Peri<'static, PIN_6>, } -#[embassy_executor::task] -async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB>) { - // MCU i2c bus for peripherals +async fn setup_mcu(mcu: Mcu) { + // MCU i2c bus for peripherals( keyboard) let mut config = i2c::Config::default(); config.frequency = 400_000; let i2c1 = I2c::new_async(mcu.i2c, mcu.clk, mcu.data, Irqs, config); conf_peripherals(i2c1).await; +} - Timer::after_millis(250).await; - +async fn setup_display(display: Display, spawner: Spawner) { let mut config = spi::Config::default(); config.frequency = 16_000_000; let spi = Spi::new( @@ -203,66 +207,85 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB> config, ); let display = init_display(spi, display.cs, display.data, display.reset).await; + spawner.spawn(display_handler(display)).unwrap(); +} - let display_fut = display_handler(display); +async fn setup_sd(sd: Sd) { + let mut config = spi::Config::default(); + config.frequency = 400_000; + 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 ui_fut = ui_handler(); + let device = ExclusiveDevice::new(spi, cs, Delay).unwrap(); + let sdcard = SdmmcSdCard::new(device, Delay); - let binary_search_fut = async { - loop { - { - let mut guard = SDCARD.get().lock().await; + config.frequency = 32_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); + SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); +} - 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; - 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); - - config.frequency = 32_000_000; - sdcard.spi(|dev| dev.bus_mut().set_config(&config)); - SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); - }; +#[embassy_executor::task] +async fn kernel_task( + spawner: Spawner, + display: Display, + sd: Sd, + mcu: Mcu, + usb: Peri<'static, USB>, +) { + setup_mcu(mcu).await; + Timer::after_millis(250).await; + setup_display(display, spawner).await; + setup_sd(sd).await; let usb = embassy_rp_usb::Driver::new(usb, Irqs); - let usb_fut = usb_handler(usb); + spawner.spawn(usb_handler(usb)).unwrap(); - let key_abi_fut = async { - loop { - Timer::after_millis(100).await; - get_keys().await + spawner.spawn(key_handler()).unwrap(); + + loop { + if let TaskState::Ui = *TASK_STATE.lock().await { + let ui_fut = ui_handler(); + let binary_search_fut = prog_search_handler(); + + join(ui_fut, binary_search_fut).await; } - }; - join5(display_fut, ui_fut, usb_fut, binary_search_fut, key_abi_fut).await; + yield_now().await; + } +} + +async fn prog_search_handler() { + 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; + } } static mut KEY_CACHE: Queue = Queue::new(); -async fn get_keys() { - if let Some(event) = read_keyboard_fifo().await { - if let KeyState::Pressed = event.state { - unsafe { - let _ = KEY_CACHE.enqueue(event); +#[embassy_executor::task] +async fn key_handler() { + loop { + if let Some(event) = read_keyboard_fifo().await { + if let KeyState::Pressed = event.state { + unsafe { + let _ = KEY_CACHE.enqueue(event); + } } } + Timer::after_millis(50).await; } } diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index e92fdeb..c4df16c 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -1,7 +1,7 @@ -use crate::format; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::signal::Signal; +use embassy_usb::Builder; 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; @@ -12,6 +12,8 @@ use crate::storage::{SDCARD, SdCard}; const BULK_ENDPOINT_PACKET_SIZE: usize = 64; +pub static MSC_SHUTDOWN: Signal = Signal::new(); + pub struct MassStorageClass<'d, D: Driver<'d>> { bulk_out: D::EndpointOut, bulk_in: D::EndpointIn, @@ -31,16 +33,24 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { 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 - } + embassy_futures::select::select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await; + + if MSC_SHUTDOWN.signaled() { + defmt::info!("MSC shutting down"); + return; // or break + } + } + } + + async fn handle_cbw(&mut self) { + 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]) { + if self.handle_command(&cbw.CBWCB).await.is_ok() { + self.send_csw_success(cbw.dCBWTag).await + } else { + self.send_csw_fail(cbw.dCBWTag).await } } } diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 03122c1..22ac827 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -14,7 +14,10 @@ use embassy_sync::{ }; use embedded_graphics::{ Drawable, - mono_font::{MonoTextStyle, ascii::FONT_9X15}, + mono_font::{ + MonoTextStyle, + ascii::{self, FONT_9X15}, + }, pixelcolor::Rgb565, prelude::{Dimensions, Point, RgbColor, Size}, primitives::Rectangle, @@ -27,6 +30,7 @@ use embedded_layout::{ prelude::*, }; use embedded_text::TextBox; +use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui}; use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = @@ -34,89 +38,50 @@ pub static SELECTIONS: Mutex = pub async fn ui_handler() { loop { - if let TaskState::Ui = *TASK_STATE.lock().await { - if let Some(event) = keyboard::read_keyboard_fifo().await { - 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 => { - let selections = SELECTIONS.lock().await; - let selection = selections.selections - [selections.current_selection as usize - 1] - .clone(); - - let entry = - unsafe { load_binary(&selection.short_name).await.unwrap() }; - BINARY_CH.send(entry).await; - } - _ => (), + if let Some(event) = keyboard::read_keyboard_fifo().await { + 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 => { + let selections = SELECTIONS.lock().await; + let selection = selections.selections + [selections.current_selection as usize - 1] + .clone(); + + let entry = unsafe { load_binary(&selection.short_name).await.unwrap() }; + BINARY_CH.send(entry).await; + } + _ => (), } } - - draw_selection().await; - } else { - embassy_time::Timer::after_millis(50).await; } + + draw_selection().await; } } async fn draw_selection() { + const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; let file_names: Vec = { let guard = SELECTIONS.lock().await; guard.selections.clone() }; - let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); - 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(); + let mut ui = Ui::new_fullscreen(unsafe { &mut FRAMEBUFFER }, medsize_rgb565_style()); 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(unsafe { &mut FRAMEBUFFER }) - .unwrap(); + ui.add(Label::new(NO_BINS).with_font(ascii::FONT_10X20)); } else { - let mut file_names = file_names.iter(); - let Some(first) = file_names.next() else { - Text::new(NO_BINS, Point::zero(), text_style) - .draw(unsafe { &mut FRAMEBUFFER }) - .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(unsafe { &mut FRAMEBUFFER }) - .unwrap(); + for file in file_names { + ui.add(Label::new(&file.long_name).with_font(ascii::FONT_10X20)); + } } } diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 49c0665..b61766a 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,11 +1,9 @@ -use crate::{TASK_STATE, TASK_STATE_CHANGED, TaskState, scsi::MassStorageClass}; -use embassy_futures::{ - join::join, - select::{select, select3}, -}; +use crate::scsi::MassStorageClass; +use embassy_futures::join::join; use embassy_rp::{peripherals::USB, usb::Driver}; use embassy_usb::{Builder, Config}; +#[embassy_executor::task] pub async fn usb_handler(driver: Driver<'static, USB>) { let mut config = Config::new(0xc0de, 0xbabe); config.manufacturer = Some("LegitCamper"); @@ -30,14 +28,5 @@ pub async fn usb_handler(driver: Driver<'static, USB>) { let mut scsi = MassStorageClass::new(&mut builder); let mut usb = builder.build(); - loop { - 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; - } - } + join(usb.run(), scsi.poll()).await; } diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index bcb1288..b43abb7 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" +kolibri-embedded-gui = "0.1.0" diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 6808c7e..7c47976 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,18 +2,22 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, display::Display, embassy_time, get_key, print, sleep}; +use abi::{KeyCode, display::Display, get_key, print, sleep}; use alloc::{boxed::Box, string::String, vec}; use core::{panic::PanicInfo, pin::Pin}; use embedded_graphics::{ Drawable, geometry::{Dimensions, Point}, - mono_font::{MonoTextStyle, ascii::FONT_6X10}, + mono_font::{ + MonoTextStyle, + ascii::{self, FONT_6X10}, + }, pixelcolor::Rgb565, prelude::{Primitive, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle}, text::{Alignment, Text}, }; +use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui}; #[panic_handler] fn panic(_info: &PanicInfo) -> ! { @@ -24,36 +28,20 @@ pub async fn main() { print("Starting Async Calculator app"); let mut display = Display; - let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); - let mut text = vec!['T', 'y', 'p', 'e']; let mut dirty = true; - let mut last_bounds: Option = None; loop { 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(); - } - + let mut ui = Ui::new_fullscreen(&mut display, medsize_rgb565_style()); 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(); + // ui.clear_background(); + ui.add(Label::new(&text).with_font(ascii::FONT_10X20)); dirty = false; } if let Some(event) = get_key() { - dirty = true; match event.key { KeyCode::Char(ch) => { text.push(ch); @@ -67,6 +55,7 @@ pub async fn main() { KeyCode::Esc => return, _ => (), } + dirty = true; } } } From ef66933a258dd4d6572da422836ea3f18715be9f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 16 Sep 2025 16:46:24 -0600 Subject: [PATCH 30/64] WIP scsi :( --- Cargo.lock | 1 + kernel/Cargo.toml | 1 + kernel/src/display.rs | 1 - kernel/src/main.rs | 12 +-- kernel/src/scsi/mod.rs | 177 +++++++++++++++++++++++++++++------------ kernel/src/ui.rs | 60 +++++++------- kernel/src/usb.rs | 25 ++++-- 7 files changed, 182 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7659019..2a7a7c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1299,6 +1299,7 @@ dependencies = [ "heapless", "kolibri-embedded-gui", "num_enum 0.7.4", + "once_cell", "panic-probe", "portable-atomic", "shared", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 248ce91..260d754 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -78,6 +78,7 @@ embedded-text = "0.7.2" embedded-layout = "0.4.2" kolibri-embedded-gui = "0.1.0" +once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" bitflags = "2.9.1" heapless = "0.8.0" diff --git a/kernel/src/display.rs b/kernel/src/display.rs index d00a859..1c7e369 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -9,7 +9,6 @@ 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; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 6dd3154..a919984 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -78,12 +78,12 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); -static TASK_STATE: Mutex = Mutex::new(TaskState::Ui); +static TASK_STATE: Mutex = Mutex::new(TaskState::Selection); static TASK_STATE_CHANGED: Signal = Signal::new(); #[derive(Copy, Clone, PartialEq)] enum TaskState { - Ui, + Selection, Kernel, } @@ -154,7 +154,7 @@ async fn userland_task() { // enable kernel ui { let mut state = TASK_STATE.lock().await; - *state = TaskState::Ui; + *state = TaskState::Selection; TASK_STATE_CHANGED.signal(()); // clear_fb(); } @@ -244,7 +244,7 @@ async fn kernel_task( spawner.spawn(key_handler()).unwrap(); loop { - if let TaskState::Ui = *TASK_STATE.lock().await { + if let TaskState::Selection = *TASK_STATE.lock().await { let ui_fut = ui_handler(); let binary_search_fut = prog_search_handler(); @@ -264,8 +264,8 @@ async fn prog_search_handler() { let files = sd.list_files_by_extension(".bin").unwrap(); let mut select = SELECTIONS.lock().await; - if select.selections != files { - select.selections = files; + if *select.selections() != files { + select.update_selections(files); select.reset(); } } diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index c4df16c..c10a97f 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -1,4 +1,8 @@ +use core::sync::atomic::AtomicBool; + +use embassy_futures::select::select; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::lazy_lock::LazyLock; use embassy_sync::signal::Signal; use embassy_usb::Builder; use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; @@ -14,13 +18,22 @@ const BULK_ENDPOINT_PACKET_SIZE: usize = 64; pub static MSC_SHUTDOWN: Signal = Signal::new(); +// number of blocks to read from sd at once +// higher is better, but is larger. Size is BLOCKS * 512 bytes +const BLOCKS: usize = 32; +static mut BLOCK_BUF: LazyLock<[Block; BLOCKS]> = + LazyLock::new(|| core::array::from_fn(|_| Block::new())); + pub struct MassStorageClass<'d, D: Driver<'d>> { + temp_sd: Option, // temporarly owns sdcard when scsi is running + ejected: bool, + pending_eject: bool, bulk_out: D::EndpointOut, bulk_in: D::EndpointIn, } impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { - pub fn new(builder: &mut Builder<'d, D>) -> Self { + pub fn new(builder: &mut Builder<'d, D>, temp_sd: Option) -> 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); @@ -28,16 +41,32 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { 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 } + Self { + temp_sd, + pending_eject: false, + ejected: false, + bulk_out, + bulk_in, + } } pub async fn poll(&mut self) { loop { - embassy_futures::select::select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await; + if !self.ejected { + select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await; - if MSC_SHUTDOWN.signaled() { - defmt::info!("MSC shutting down"); - return; // or break + if MSC_SHUTDOWN.signaled() { + defmt::info!("MSC shutting down"); + + if self.temp_sd.is_some() { + let mut guard = SDCARD.get().lock().await; + guard.replace(self.temp_sd.take().unwrap()).unwrap(); + } + + self.ejected = true; + + return; + } } } } @@ -45,28 +74,41 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { async fn handle_cbw(&mut self) { let mut cbw_buf = [0u8; 31]; if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { + // Take sdcard to increase speed + if self.temp_sd.is_none() { + let mut guard = SDCARD.get().lock().await; + if let Some(sd) = guard.take() { + self.temp_sd = Some(sd); + } else { + defmt::warn!("Tried to take SDCARD but it was already taken"); + return; + } + } + if n == 31 { if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { - if self.handle_command(&cbw.CBWCB).await.is_ok() { + let command = parse_cb(&cbw.CBWCB); + if self.handle_command(command).await.is_ok() { self.send_csw_success(cbw.dCBWTag).await } else { self.send_csw_fail(cbw.dCBWTag).await } + + if self.pending_eject { + if let ScsiCommand::Write { lba: _, len: _ } = command { + MSC_SHUTDOWN.signal(()); + } + } } } } } - async fn handle_command(&mut self, cbw: &[u8]) -> Result<(), ()> { + async fn handle_command(&mut self, command: ScsiCommand) -> 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(()) - } + match command { + ScsiCommand::Unknown => Err(()), ScsiCommand::Inquiry { evpd, page_code, @@ -144,9 +186,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { self.bulk_in.write(&response[..len]).await.map_err(|_| ()) } ScsiCommand::TestUnitReady => { - let guard = SDCARD.get().lock().await; - let sdcard = guard.as_ref().unwrap(); - if sdcard.is_attached() { + if self.temp_sd.as_ref().unwrap().is_attached() { Ok(()) } else { Err(()) @@ -192,11 +232,8 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 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 = sdcard.size() / block_size; + let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let last_lba = total_blocks.checked_sub(1).unwrap_or(0); @@ -206,11 +243,8 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 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 = sdcard.size() / block_size; + let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let last_lba = total_blocks.checked_sub(1).unwrap_or(0); @@ -222,40 +256,80 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, 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(); + let sdcard = self.temp_sd.as_ref().unwrap(); + let block_buf = unsafe { &mut *BLOCK_BUF.get_mut() }; + let mut blocks = len; + let mut idx = lba; - for i in 0..len { - let block_idx = BlockIdx(lba as u32 + i as u32); - 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(|_| ())?; + while blocks > 0 { + if blocks >= block_buf.len() as u64 { + sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?; + + for block in &mut *block_buf { + for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { + self.bulk_in.write(chunk).await.map_err(|_| ())?; + } + } + + blocks -= block_buf.len() as u64; + idx += block_buf.len() as u64; + } else { + sdcard + .read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?; + + for block in &block_buf[..blocks as usize] { + for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { + self.bulk_in.write(chunk).await.map_err(|_| ())?; + } + } + + idx += blocks; + blocks = 0; } } Ok(()) } ScsiCommand::Write { lba, len } => { - let guard = SDCARD.get().lock().await; - let sdcard = guard.as_ref().unwrap(); + let sdcard = self.temp_sd.as_ref().unwrap(); + let block_buf = unsafe { &mut *BLOCK_BUF.get_mut() }; + let mut blocks = len; + let mut idx = lba; - 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(|_| ())?; + while blocks > 0 { + if blocks >= block_buf.len() as u64 { + for block in block_buf.as_mut() { + for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into()) + { + self.bulk_out.read(chunk).await.map_err(|_| ())?; + } + } + + sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?; + + blocks -= block_buf.len() as u64; + idx += block_buf.len() as u64; + } else { + for block in block_buf[..blocks as usize].as_mut() { + for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into()) + { + self.bulk_out.read(chunk).await.map_err(|_| ())?; + } + } + + sdcard.write_blocks( + &mut block_buf[..blocks as usize], + BlockIdx(idx as u32), + )?; + + idx += blocks; + blocks = 0; } - 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 = (sdcard.size() / block_size as u64) as u32; + let num_blocks = (self.temp_sd.as_ref().unwrap().size() / block_size as u64) as u32; let mut response = [0u8; 12]; @@ -274,7 +348,12 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { .map_err(|_| ()) } ScsiCommand::PreventAllowMediumRemoval { prevent: _prevent } => Ok(()), - ScsiCommand::StartStopUnit { start, load_eject } => Ok(()), + ScsiCommand::StartStopUnit { start, load_eject } => { + if !start && load_eject { + self.pending_eject = true; + } + Ok(()) + } } } @@ -283,7 +362,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { } pub async fn send_csw_fail(&mut self, tag: u32) { - defmt::error!("Command Failed"); + defmt::error!("Command Failed: {}", tag); self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed } diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 22ac827..51ec2a1 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,35 +1,12 @@ +use core::sync::atomic::Ordering; + use crate::{ - BINARY_CH, TASK_STATE, TaskState, - display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, - elf::load_binary, - format, - peripherals::keyboard, - storage::FileName, + BINARY_CH, display::FRAMEBUFFER, elf::load_binary, peripherals::keyboard, storage::FileName, + usb::USB_ACTIVE, }; -use alloc::{string::String, vec::Vec}; -use core::{fmt::Debug, str::FromStr, sync::atomic::Ordering}; -use embassy_sync::{ - blocking_mutex::raw::{CriticalSectionRawMutex, ThreadModeRawMutex}, - mutex::Mutex, -}; -use embedded_graphics::{ - Drawable, - mono_font::{ - MonoTextStyle, - ascii::{self, FONT_9X15}, - }, - pixelcolor::Rgb565, - prelude::{Dimensions, Point, RgbColor, Size}, - primitives::Rectangle, - text::Text, -}; -use embedded_layout::{ - align::{horizontal, vertical}, - layout::linear::LinearLayout, - object_chain::Chain, - prelude::*, -}; -use embedded_text::TextBox; +use alloc::vec::Vec; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; +use embedded_graphics::mono_font::ascii; use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui}; use shared::keyboard::{KeyCode, KeyState}; @@ -63,7 +40,9 @@ pub async fn ui_handler() { } } - draw_selection().await; + if SELECTIONS.lock().await.changed { + draw_selection().await; + } } } @@ -83,12 +62,16 @@ async fn draw_selection() { ui.add(Label::new(&file.long_name).with_font(ascii::FONT_10X20)); } } + + let mut sel = SELECTIONS.lock().await; + sel.changed = false; } #[derive(Clone)] pub struct SelectionList { current_selection: u16, - pub selections: Vec, + selections: Vec, + changed: bool, } impl SelectionList { @@ -96,11 +79,22 @@ impl SelectionList { Self { selections: Vec::new(), current_selection: 0, + changed: false, } } + pub fn update_selections(&mut self, selections: Vec) { + self.selections = selections; + self.changed = true; + } + + pub fn selections(&self) -> &Vec { + &self.selections + } + pub fn reset(&mut self) { - self.current_selection = 1 + self.current_selection = 1; + self.changed = true; } fn down(&mut self) { diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index b61766a..56e31b8 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,7 +1,10 @@ -use crate::scsi::MassStorageClass; -use embassy_futures::join::join; +use crate::{scsi::MassStorageClass, storage::SdCard}; +use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_futures::{join::join, select::select}; use embassy_rp::{peripherals::USB, usb::Driver}; -use embassy_usb::{Builder, Config}; +use embassy_usb::{Builder, Config, UsbDevice}; + +pub static USB_ACTIVE: AtomicBool = AtomicBool::new(false); #[embassy_executor::task] pub async fn usb_handler(driver: Driver<'static, USB>) { @@ -25,8 +28,18 @@ pub async fn usb_handler(driver: Driver<'static, USB>) { &mut control_buf, ); - let mut scsi = MassStorageClass::new(&mut builder); - let mut usb = builder.build(); + let temp_sd: Option = None; + let mut scsi = MassStorageClass::new(&mut builder, temp_sd); + let usb = builder.build(); - join(usb.run(), scsi.poll()).await; + select(run(usb), scsi.poll()).await; +} + +async fn run<'d>(mut usb: UsbDevice<'d, Driver<'d, USB>>) -> ! { + loop { + usb.wait_resume().await; + USB_ACTIVE.store(true, Ordering::Release); + usb.run_until_suspend().await; + USB_ACTIVE.store(false, Ordering::Release); + } } From dadde9048df64be93f67420523a54b9478e8ead8 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 16 Sep 2025 16:52:45 -0600 Subject: [PATCH 31/64] fix loader --- kernel/src/main.rs | 21 ++++++--------------- kernel/src/scsi/mod.rs | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a919984..d23f61c 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -18,6 +18,8 @@ mod ui; mod usb; mod utils; +use core::sync::atomic::{AtomicBool, Ordering}; + use crate::{ display::{clear_fb, display_handler, init_display}, peripherals::{ @@ -78,14 +80,7 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); -static TASK_STATE: Mutex = Mutex::new(TaskState::Selection); -static TASK_STATE_CHANGED: Signal = Signal::new(); - -#[derive(Copy, Clone, PartialEq)] -enum TaskState { - Selection, - Kernel, -} +static ENABLE_UI: AtomicBool = AtomicBool::new(true); #[embassy_executor::main] async fn main(spawner: Spawner) { @@ -141,9 +136,7 @@ async fn userland_task() { // disable kernel ui { - let mut state = TASK_STATE.lock().await; - *state = TaskState::Kernel; - TASK_STATE_CHANGED.signal(()); + ENABLE_UI.store(false, Ordering::Release); // clear_fb(); MSC_SHUTDOWN.signal(()); } @@ -153,9 +146,7 @@ async fn userland_task() { // enable kernel ui { - let mut state = TASK_STATE.lock().await; - *state = TaskState::Selection; - TASK_STATE_CHANGED.signal(()); + ENABLE_UI.store(true, Ordering::Release); // clear_fb(); } } @@ -244,7 +235,7 @@ async fn kernel_task( spawner.spawn(key_handler()).unwrap(); loop { - if let TaskState::Selection = *TASK_STATE.lock().await { + if ENABLE_UI.load(Ordering::Relaxed) { let ui_fut = ui_handler(); let binary_search_fut = prog_search_handler(); diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index c10a97f..f8c72fb 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -74,19 +74,19 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { async fn handle_cbw(&mut self) { let mut cbw_buf = [0u8; 31]; if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { - // Take sdcard to increase speed - if self.temp_sd.is_none() { - let mut guard = SDCARD.get().lock().await; - if let Some(sd) = guard.take() { - self.temp_sd = Some(sd); - } else { - defmt::warn!("Tried to take SDCARD but it was already taken"); - return; - } - } - if n == 31 { if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { + // Take sdcard to increase speed + if self.temp_sd.is_none() { + let mut guard = SDCARD.get().lock().await; + if let Some(sd) = guard.take() { + self.temp_sd = Some(sd); + } else { + defmt::warn!("Tried to take SDCARD but it was already taken"); + return; + } + } + let command = parse_cb(&cbw.CBWCB); if self.handle_command(command).await.is_ok() { self.send_csw_success(cbw.dCBWTag).await From 177c15e9cc1a94e087bd8544e8a50b442c121567 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 10:57:08 -0600 Subject: [PATCH 32/64] fixes --- Cargo.lock | 1 - abi_sys/src/lib.rs | 2 +- kernel/src/main.rs | 20 +++++----- kernel/src/ui.rs | 68 ++++++++++++++++++++++++++------ user-apps/calculator/Cargo.toml | 1 - user-apps/calculator/src/main.rs | 41 ++++++++++++++----- 6 files changed, 99 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a7a7c1..8a98415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,7 +232,6 @@ version = "0.1.0" dependencies = [ "abi", "embedded-graphics", - "kolibri-embedded-gui", ] [[package]] diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 60e7131..54a8abb 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -11,7 +11,7 @@ pub use embedded_graphics::{ }; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -pub type EntryFn = fn() -> Pin>>; +pub type EntryFn = fn(); #[unsafe(no_mangle)] #[unsafe(link_section = ".user_reloc")] diff --git a/kernel/src/main.rs b/kernel/src/main.rs index d23f61c..bf15531 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -39,6 +39,7 @@ use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::{ join::{join, join3, join5}, + select::select, yield_now, }; use embassy_rp::{ @@ -81,6 +82,7 @@ static ALLOCATOR: Talck, ClaimOnOom> = .lock(); static ENABLE_UI: AtomicBool = AtomicBool::new(true); +static UI_CHANGE: Signal = Signal::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { @@ -137,16 +139,18 @@ async fn userland_task() { // disable kernel ui { ENABLE_UI.store(false, Ordering::Release); + UI_CHANGE.signal(()); // clear_fb(); MSC_SHUTDOWN.signal(()); } defmt::info!("Executing Binary"); - entry().await; + entry(); // enable kernel ui { ENABLE_UI.store(true, Ordering::Release); + UI_CHANGE.signal(()); // clear_fb(); } } @@ -230,19 +234,18 @@ async fn kernel_task( setup_sd(sd).await; let usb = embassy_rp_usb::Driver::new(usb, Irqs); - spawner.spawn(usb_handler(usb)).unwrap(); - - spawner.spawn(key_handler()).unwrap(); + // spawner.spawn(usb_handler(usb)).unwrap(); loop { - if ENABLE_UI.load(Ordering::Relaxed) { + let ui_enabled = ENABLE_UI.load(Ordering::Relaxed); + if ui_enabled { let ui_fut = ui_handler(); let binary_search_fut = prog_search_handler(); - join(ui_fut, binary_search_fut).await; + select(join(ui_fut, binary_search_fut), UI_CHANGE.wait()).await; + } else { + select(key_handler(), UI_CHANGE.wait()).await; } - - yield_now().await; } } @@ -267,7 +270,6 @@ async fn prog_search_handler() { static mut KEY_CACHE: Queue = Queue::new(); -#[embassy_executor::task] async fn key_handler() { loop { if let Some(event) = read_keyboard_fifo().await { diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 51ec2a1..6ff4b1a 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,13 +1,23 @@ -use core::sync::atomic::Ordering; - use crate::{ BINARY_CH, display::FRAMEBUFFER, elf::load_binary, peripherals::keyboard, storage::FileName, - usb::USB_ACTIVE, }; -use alloc::vec::Vec; +use alloc::{str::FromStr, string::String, vec::Vec}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; -use embedded_graphics::mono_font::ascii; -use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui}; +use embedded_graphics::{ + Drawable, + mono_font::{MonoTextStyle, ascii::FONT_9X15}, + pixelcolor::Rgb565, + prelude::{Dimensions, Point, RgbColor, Size}, + primitives::Rectangle, + text::Text, +}; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::LinearLayout, + object_chain::Chain, + prelude::*, +}; +use embedded_text::TextBox; use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = @@ -47,20 +57,54 @@ pub async fn ui_handler() { } async fn draw_selection() { - const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; let file_names: Vec = { let guard = SELECTIONS.lock().await; guard.selections.clone() }; - let mut ui = Ui::new_fullscreen(unsafe { &mut FRAMEBUFFER }, medsize_rgb565_style()); + let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + 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(); if file_names.is_empty() { - ui.add(Label::new(NO_BINS).with_font(ascii::FONT_10X20)); + 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(unsafe { &mut FRAMEBUFFER }) + .unwrap(); } else { - for file in file_names { - ui.add(Label::new(&file.long_name).with_font(ascii::FONT_10X20)); - } + let mut file_names = file_names.iter(); + let Some(first) = file_names.next() else { + Text::new(NO_BINS, Point::zero(), text_style) + .draw(unsafe { &mut FRAMEBUFFER }) + .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(unsafe { &mut FRAMEBUFFER }) + .unwrap(); } let mut sel = SELECTIONS.lock().await; diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index b43abb7..bcb1288 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -6,4 +6,3 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" -kolibri-embedded-gui = "0.1.0" diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 7c47976..13f9499 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; use abi::{KeyCode, display::Display, get_key, print, sleep}; -use alloc::{boxed::Box, string::String, vec}; +use alloc::{boxed::Box, format, string::String, vec}; use core::{panic::PanicInfo, pin::Pin}; use embedded_graphics::{ Drawable, @@ -17,27 +17,48 @@ use embedded_graphics::{ primitives::{PrimitiveStyle, Rectangle}, text::{Alignment, Text}, }; -use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui}; #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(info: &PanicInfo) -> ! { + print(&format!( + "user panic: {} @ {:?}", + info.message(), + info.location(), + )); loop {} } -pub async fn main() { +pub fn main() { print("Starting Async Calculator app"); let mut display = Display; + let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); + let mut text = vec!['T', 'y', 'p', 'e']; let mut dirty = true; + let mut last_area: Option = None; loop { if dirty { - let mut ui = Ui::new_fullscreen(&mut display, medsize_rgb565_style()); - let text = text.iter().cloned().collect::(); + if let Some(area) = last_area { + Rectangle::new(area.top_left, area.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(&mut display) + .unwrap(); + } + + let text = text.iter().cloned().collect::(); + let aligned_text = Text::with_alignment( + &text, + display.bounding_box().center() + Point::new(0, 15), + character_style, + Alignment::Center, + ); + + last_area = Some(aligned_text.bounding_box()); + + aligned_text.draw(&mut display).unwrap(); - // ui.clear_background(); - ui.add(Label::new(&text).with_font(ascii::FONT_10X20)); dirty = false; } @@ -61,6 +82,6 @@ pub async fn main() { } #[unsafe(no_mangle)] -pub extern "Rust" fn _start() -> Pin>> { - Box::pin(async { main().await }) +pub extern "Rust" fn _start() { + main() } From 1f1aa5ada5ac3390302a8652910eddbf00ba1204 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 11:07:14 -0600 Subject: [PATCH 33/64] clear ui, when loading bin --- kernel/src/main.rs | 7 ++++--- kernel/src/ui.rs | 33 ++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index bf15531..1e94ac4 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -28,7 +28,7 @@ use crate::{ }, scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, - ui::{SELECTIONS, ui_handler}, + ui::{SELECTIONS, clear_selection, ui_handler}, usb::usb_handler, }; use abi_sys::EntryFn; @@ -140,7 +140,9 @@ async fn userland_task() { { ENABLE_UI.store(false, Ordering::Release); UI_CHANGE.signal(()); - // clear_fb(); + + clear_selection().await; + MSC_SHUTDOWN.signal(()); } @@ -151,7 +153,6 @@ async fn userland_task() { { ENABLE_UI.store(true, Ordering::Release); UI_CHANGE.signal(()); - // clear_fb(); } } } diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 6ff4b1a..4c912c1 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -7,8 +7,8 @@ use embedded_graphics::{ Drawable, mono_font::{MonoTextStyle, ascii::FONT_9X15}, pixelcolor::Rgb565, - prelude::{Dimensions, Point, RgbColor, Size}, - primitives::Rectangle, + prelude::{Dimensions, Point, Primitive, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle}, text::Text, }; use embedded_layout::{ @@ -50,12 +50,25 @@ pub async fn ui_handler() { } } - if SELECTIONS.lock().await.changed { + let changed = SELECTIONS.lock().await.changed; + if changed { + clear_selection().await; draw_selection().await; } } } +pub async fn clear_selection() { + let sel = SELECTIONS.lock().await; + + if let Some(area) = sel.last_bounds { + Rectangle::new(area.top_left, area.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(unsafe { &mut FRAMEBUFFER }) + .unwrap(); + } +} + async fn draw_selection() { let file_names: Vec = { let guard = SELECTIONS.lock().await; @@ -99,12 +112,14 @@ async fn draw_selection() { // )); // } - LinearLayout::vertical(chain) + let layout = LinearLayout::vertical(chain) .with_alignment(horizontal::Center) .arrange() - .align_to(&display_area, horizontal::Center, vertical::Center) - .draw(unsafe { &mut FRAMEBUFFER }) - .unwrap(); + .align_to(&display_area, horizontal::Center, vertical::Center); + + SELECTIONS.lock().await.last_bounds = Some(layout.bounds()); + + layout.draw(unsafe { &mut FRAMEBUFFER }).unwrap(); } let mut sel = SELECTIONS.lock().await; @@ -113,6 +128,9 @@ async fn draw_selection() { #[derive(Clone)] pub struct SelectionList { + // allows easy clearing of selection ui, + // based on previous bounds + last_bounds: Option, current_selection: u16, selections: Vec, changed: bool, @@ -121,6 +139,7 @@ pub struct SelectionList { impl SelectionList { pub const fn new() -> Self { Self { + last_bounds: None, selections: Vec::new(), current_selection: 0, changed: false, From e07700beea6a31a0b861760329f4400ae6766e71 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 11:50:28 -0600 Subject: [PATCH 34/64] wip calculator --- Cargo.lock | 1 + user-apps/calculator/Cargo.toml | 1 + user-apps/calculator/src/main.rs | 72 ++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a98415..7ea5cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,7 @@ version = "0.1.0" dependencies = [ "abi", "embedded-graphics", + "embedded-layout", ] [[package]] diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml index bcb1288..19115f3 100644 --- a/user-apps/calculator/Cargo.toml +++ b/user-apps/calculator/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" +embedded-layout = "0.4.2" diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 13f9499..4220235 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -11,12 +11,22 @@ use embedded_graphics::{ mono_font::{ MonoTextStyle, ascii::{self, FONT_6X10}, + iso_8859_1::FONT_10X20, }, pixelcolor::Rgb565, prelude::{Primitive, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle}, text::{Alignment, Text}, }; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::{ + LinearLayout, + spacing::{DistributeFill, FixedMargin}, + }, + object_chain::Chain, + prelude::*, +}; #[panic_handler] fn panic(info: &PanicInfo) -> ! { @@ -28,13 +38,16 @@ fn panic(info: &PanicInfo) -> ! { loop {} } +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() { + main() +} + pub fn main() { print("Starting Async Calculator app"); let mut display = Display; - let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); - - let mut text = vec!['T', 'y', 'p', 'e']; + let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; let mut dirty = true; let mut last_area: Option = None; @@ -47,17 +60,45 @@ pub fn main() { .unwrap(); } - let text = text.iter().cloned().collect::(); - let aligned_text = Text::with_alignment( + let text = input.iter().cloned().collect::(); + + let expr = Text::new( &text, - display.bounding_box().center() + Point::new(0, 15), - character_style, - Alignment::Center, + display.bounding_box().center(), + MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE), ); - last_area = Some(aligned_text.bounding_box()); + let layout = LinearLayout::vertical( + Chain::new(Text::new( + "Calculator!", + Point::zero(), + MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE), + )) + .append( + LinearLayout::horizontal(Chain::new(expr).append(Text::new( + " = 901", + Point::zero(), + MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE), + ))) + .with_spacing(DistributeFill(expr.size().width)) + .arrange() + .align_to( + &display.bounding_box(), + horizontal::Center, + vertical::Center, + ), + ), + ) + .with_spacing(DistributeFill(50)) + .arrange() + .align_to( + &display.bounding_box(), + horizontal::Center, + vertical::Center, + ); - aligned_text.draw(&mut display).unwrap(); + last_area = Some(layout.bounds()); + layout.draw(&mut display).unwrap(); dirty = false; } @@ -65,13 +106,13 @@ pub fn main() { if let Some(event) = get_key() { match event.key { KeyCode::Char(ch) => { - text.push(ch); + input.push(ch); } KeyCode::Del => { - text.clear(); + input.clear(); } KeyCode::Backspace => { - text.pop(); + input.pop(); } KeyCode::Esc => return, _ => (), @@ -80,8 +121,3 @@ pub fn main() { } } } - -#[unsafe(no_mangle)] -pub extern "Rust" fn _start() { - main() -} From 1cd0d292fe6d49577def7f16a3431f7748b8c182 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 15:53:35 -0600 Subject: [PATCH 35/64] calculator works pretty well minus the tearing --- user-apps/calculator/src/main.rs | 179 ++++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 52 deletions(-) diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 4220235..f66f5e3 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,28 +2,21 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, display::Display, get_key, print, sleep}; -use alloc::{boxed::Box, format, string::String, vec}; -use core::{panic::PanicInfo, pin::Pin}; +use abi::{KeyCode, display::Display, get_key, print}; +use alloc::{format, string::String, vec, vec::Vec}; +use core::panic::PanicInfo; use embedded_graphics::{ Drawable, geometry::{Dimensions, Point}, - mono_font::{ - MonoTextStyle, - ascii::{self, FONT_6X10}, - iso_8859_1::FONT_10X20, - }, + mono_font::{MonoTextStyle, ascii::FONT_7X14, iso_8859_1::FONT_10X20}, pixelcolor::Rgb565, - prelude::{Primitive, RgbColor, Size}, + prelude::{Primitive, RgbColor}, primitives::{PrimitiveStyle, Rectangle}, - text::{Alignment, Text}, + text::Text, }; use embedded_layout::{ align::{horizontal, vertical}, - layout::linear::{ - LinearLayout, - spacing::{DistributeFill, FixedMargin}, - }, + layout::linear::LinearLayout, object_chain::Chain, prelude::*, }; @@ -48,57 +41,63 @@ pub fn main() { let mut display = Display; let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; + let input_min = input.len(); let mut dirty = true; - let mut last_area: Option = None; + let mut last_area: Option<(Rectangle, Rectangle)> = None; + + LinearLayout::vertical(Chain::new(Text::new( + "Calculator!", + Point::zero(), + MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE), + ))) + .arrange() + .align_to(&display.bounding_box(), horizontal::Center, vertical::Top) + .draw(&mut display) + .expect("Failed to draw title"); loop { if dirty { + let style = PrimitiveStyle::with_fill(Rgb565::BLACK); if let Some(area) = last_area { - Rectangle::new(area.top_left, area.size) - .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + Rectangle::new(area.0.top_left, area.0.size) + .into_styled(style) + .draw(&mut display) + .unwrap(); + + Rectangle::new(area.1.top_left, area.1.size) + .into_styled(style) .draw(&mut display) .unwrap(); } let text = input.iter().cloned().collect::(); - let expr = Text::new( + let style = MonoTextStyle::new(&FONT_7X14, Rgb565::WHITE); + let expr_layout = LinearLayout::vertical(Chain::new(Text::new( &text, display.bounding_box().center(), - MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE), - ); - - let layout = LinearLayout::vertical( - Chain::new(Text::new( - "Calculator!", - Point::zero(), - MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE), - )) - .append( - LinearLayout::horizontal(Chain::new(expr).append(Text::new( - " = 901", - Point::zero(), - MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE), - ))) - .with_spacing(DistributeFill(expr.size().width)) - .arrange() - .align_to( - &display.bounding_box(), - horizontal::Center, - vertical::Center, - ), - ), - ) - .with_spacing(DistributeFill(50)) + style, + ))) .arrange() - .align_to( - &display.bounding_box(), - horizontal::Center, - vertical::Center, - ); + .align_to(&display.bounding_box(), horizontal::Left, vertical::Center); - last_area = Some(layout.bounds()); - layout.draw(&mut display).unwrap(); + let result = if let Ok(result) = evaluate(&input[input_min..]) { + &format!(" = {}", result) + } else { + " = Error" + }; + + let eq_layout = LinearLayout::vertical(Chain::new(Text::new( + result, + display.bounding_box().center(), + style, + ))) + .arrange() + .align_to(&display.bounding_box(), horizontal::Right, vertical::Center); + + last_area = Some((expr_layout.bounds(), eq_layout.bounds())); + expr_layout.draw(&mut display).unwrap(); + eq_layout.draw(&mut display).unwrap(); dirty = false; } @@ -109,10 +108,12 @@ pub fn main() { input.push(ch); } KeyCode::Del => { - input.clear(); + input.truncate(input_min); } KeyCode::Backspace => { - input.pop(); + if input.len() > input_min { + input.pop(); + } } KeyCode::Esc => return, _ => (), @@ -121,3 +122,77 @@ pub fn main() { } } } + +fn get_int(int: &[char]) -> Result { + let mut output: i32 = 0; + for &c in int { + let digit = c.to_digit(10).ok_or(())? as i32; + output = output + .checked_mul(10) + .and_then(|v| v.checked_add(digit)) + .ok_or(())?; + } + Ok(output) +} + +fn primary(input: &[char], pos: &mut usize) -> Result { + let mut digits = Vec::new(); + while *pos < input.len() && input[*pos].is_ascii_digit() { + digits.push(input[*pos]); + *pos += 1; + } + if digits.is_empty() { + return Err(()); + } + get_int(&digits) +} + +fn mul_div(input: &[char], pos: &mut usize) -> Result { + let mut value = primary(input, pos)?; + while *pos < input.len() { + let op = input[*pos]; + if op != '*' && op != '/' { + break; + } + *pos += 1; + let rhs = primary(input, pos)?; + value = match op { + '*' => value.checked_mul(rhs).ok_or(())?, + '/' => { + if rhs == 0 { + return Err(()); + } + value.checked_div(rhs).ok_or(())? + } + _ => unreachable!(), + }; + } + Ok(value) +} + +fn add_sub(input: &[char], pos: &mut usize) -> Result { + let mut value = mul_div(input, pos)?; + while *pos < input.len() { + let op = input[*pos]; + if op != '+' && op != '-' { + break; + } + *pos += 1; + let rhs = mul_div(input, pos)?; + value = match op { + '+' => value.checked_add(rhs).ok_or(())?, + '-' => value.checked_sub(rhs).ok_or(())?, + _ => unreachable!(), + }; + } + Ok(value) +} + +fn evaluate(input: &[char]) -> Result { + let mut pos = 0; + let result = add_sub(input, &mut pos)?; + if pos != input.len() { + return Err(()); + } + Ok(result) +} From 39abe19fd3d9499df10d7be8fc5845aa975d7092 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 16:25:24 -0600 Subject: [PATCH 36/64] can load & reload programs --- kernel/src/main.rs | 16 +++++++++++----- kernel/src/ui.rs | 7 +++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 1e94ac4..987b84f 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -21,7 +21,7 @@ mod utils; use core::sync::atomic::{AtomicBool, Ordering}; use crate::{ - display::{clear_fb, display_handler, init_display}, + display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, peripherals::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, @@ -31,7 +31,8 @@ use crate::{ ui::{SELECTIONS, clear_selection, ui_handler}, usb::usb_handler, }; -use abi_sys::EntryFn; +use abi_sys::{EntryFn, Rgb565, RgbColor}; +use embedded_graphics::prelude::DrawTarget; use {defmt_rtt as _, panic_probe as _}; @@ -149,10 +150,15 @@ async fn userland_task() { defmt::info!("Executing Binary"); entry(); + defmt::info!("Putting kernel back in UI mode"); // enable kernel ui { ENABLE_UI.store(true, Ordering::Release); UI_CHANGE.signal(()); + unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() }; + + let mut selections = SELECTIONS.lock().await; + selections.set_changed(true); } } } @@ -239,11 +245,11 @@ async fn kernel_task( loop { let ui_enabled = ENABLE_UI.load(Ordering::Relaxed); + defmt::info!("ui enabled? {:?}", ui_enabled); if ui_enabled { - let ui_fut = ui_handler(); - let binary_search_fut = prog_search_handler(); + defmt::info!("starting ui"); - select(join(ui_fut, binary_search_fut), UI_CHANGE.wait()).await; + select(join(ui_handler(), prog_search_handler()), UI_CHANGE.wait()).await; } else { select(key_handler(), UI_CHANGE.wait()).await; } diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 4c912c1..e37ccc1 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -3,6 +3,7 @@ use crate::{ }; use alloc::{str::FromStr, string::String, vec::Vec}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; +use embassy_time::Timer; use embedded_graphics::{ Drawable, mono_font::{MonoTextStyle, ascii::FONT_9X15}, @@ -55,6 +56,8 @@ pub async fn ui_handler() { clear_selection().await; draw_selection().await; } + + Timer::after_millis(50).await; } } @@ -146,6 +149,10 @@ impl SelectionList { } } + pub fn set_changed(&mut self, changed: bool) { + self.changed = changed + } + pub fn update_selections(&mut self, selections: Vec) { self.selections = selections; self.changed = true; From 46c69ead679f91d40cee524a113ab0c86094162b Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 16:28:32 -0600 Subject: [PATCH 37/64] remove debug comments --- kernel/src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 987b84f..68bbd34 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -150,7 +150,6 @@ async fn userland_task() { defmt::info!("Executing Binary"); entry(); - defmt::info!("Putting kernel back in UI mode"); // enable kernel ui { ENABLE_UI.store(true, Ordering::Release); @@ -245,10 +244,7 @@ async fn kernel_task( loop { let ui_enabled = ENABLE_UI.load(Ordering::Relaxed); - defmt::info!("ui enabled? {:?}", ui_enabled); if ui_enabled { - defmt::info!("starting ui"); - select(join(ui_handler(), prog_search_handler()), UI_CHANGE.wait()).await; } else { select(key_handler(), UI_CHANGE.wait()).await; From fab10dfa1fd033c7fcf254f1265dee6295e3ac75 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 16:44:18 -0600 Subject: [PATCH 38/64] cargo fix --- .helix/languages.toml | 5 +++++ abi_sys/src/lib.rs | 11 +++++++---- kernel/src/abi.rs | 16 ++-------------- kernel/src/elf.rs | 2 +- kernel/src/framebuffer.rs | 2 +- kernel/src/main.rs | 22 ++++++++++------------ kernel/src/scsi/mod.rs | 27 +++++++++++++++------------ kernel/src/scsi/scsi_types.rs | 5 ----- kernel/src/storage.rs | 2 +- kernel/src/usb.rs | 2 +- 10 files changed, 43 insertions(+), 51 deletions(-) create mode 100644 .helix/languages.toml diff --git a/.helix/languages.toml b/.helix/languages.toml new file mode 100644 index 0000000..9d6eb0f --- /dev/null +++ b/.helix/languages.toml @@ -0,0 +1,5 @@ +[[language]] +name = "rust" + +[language-server.rust-analyzer.config.check] +allTargets = false diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 54a8abb..e503c5b 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,15 +1,14 @@ #![no_std] extern crate alloc; -use alloc::boxed::Box; -use core::pin::Pin; -pub use embedded_graphics::{ +#[allow(unused)] +use embedded_graphics::{ Pixel, geometry::Point, pixelcolor::{Rgb565, RgbColor}, }; -use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; pub type EntryFn = fn(); @@ -32,6 +31,7 @@ impl CallAbiTable { pub type PrintAbi = extern "Rust" fn(msg: &str); +#[allow(unused)] pub fn print(msg: &str) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; @@ -42,6 +42,7 @@ pub fn print(msg: &str) { pub type SleepAbi = extern "Rust" fn(ms: u64); +#[allow(unused)] pub fn sleep(ms: u64) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::Sleep as usize]; @@ -52,6 +53,7 @@ pub fn sleep(ms: u64) { pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); +#[allow(unused)] pub fn draw_iter(pixels: &[Pixel]) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]; @@ -62,6 +64,7 @@ pub fn draw_iter(pixels: &[Pixel]) { pub type GetKeyAbi = extern "Rust" fn() -> Option; +#[allow(unused)] pub fn get_key() -> Option { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::GetKey as usize]; diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 8451ffd..0b59d51 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,18 +1,6 @@ -use core::{pin::Pin, time::Duration}; - -use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi, SleepAbi}; -use alloc::boxed::Box; -use defmt::info; -use embassy_futures::block_on; +use abi_sys::{DrawIterAbi, GetKeyAbi, PrintAbi, SleepAbi}; use embassy_rp::clocks::clk_sys_freq; -use embassy_time::Timer; -use embedded_graphics::{ - Drawable, - draw_target::DrawTarget, - pixelcolor::Rgb565, - prelude::{Point, RgbColor, Size}, - primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, -}; +use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; use shared::keyboard::KeyEvent; use crate::{KEY_CACHE, display::FRAMEBUFFER}; diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index e7e6c2b..0c9bd04 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -28,7 +28,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result { let mut sd_lock = SDCARD.get().lock().await; let sd = sd_lock.as_mut().unwrap(); - let mut error = ""; + let error = ""; let mut entry = 0; let mut header_buf = [0; ELF32_HDR_SIZE]; diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 8b5fa4c..a029231 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -13,7 +13,7 @@ use embedded_graphics::{ use embedded_hal_2::digital::OutputPin; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; use heapless::Vec; -use st7365p_lcd::{FrameBuffer, ST7365P}; +use st7365p_lcd::ST7365P; pub const TILE_SIZE: usize = 16; // 16x16 tile pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 68bbd34..ad16856 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -21,7 +21,7 @@ mod utils; use core::sync::atomic::{AtomicBool, Ordering}; use crate::{ - display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, + display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, @@ -29,20 +29,18 @@ use crate::{ scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, ui::{SELECTIONS, clear_selection, ui_handler}, - usb::usb_handler, }; -use abi_sys::{EntryFn, Rgb565, RgbColor}; -use embedded_graphics::prelude::DrawTarget; +use abi_sys::EntryFn; +use embedded_graphics::{ + pixelcolor::Rgb565, + prelude::{DrawTarget, RgbColor}, +}; use {defmt_rtt as _, panic_probe as _}; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; -use embassy_futures::{ - join::{join, join3, join5}, - select::select, - yield_now, -}; +use embassy_futures::{join::join, select::select}; use embassy_rp::{ Peri, gpio::{Input, Level, Output, Pull}, @@ -56,7 +54,7 @@ use embassy_rp::{ usb as embassy_rp_usb, }; use embassy_sync::{ - blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex, signal::Signal, + blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal, }; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; @@ -86,7 +84,7 @@ static ENABLE_UI: AtomicBool = AtomicBool::new(true); static UI_CHANGE: Signal = Signal::new(); #[embassy_executor::main] -async fn main(spawner: Spawner) { +async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); spawn_core1( @@ -239,7 +237,7 @@ async fn kernel_task( setup_display(display, spawner).await; setup_sd(sd).await; - let usb = embassy_rp_usb::Driver::new(usb, Irqs); + let _usb = embassy_rp_usb::Driver::new(usb, Irqs); // spawner.spawn(usb_handler(usb)).unwrap(); loop { diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index f8c72fb..b61acee 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -1,5 +1,3 @@ -use core::sync::atomic::AtomicBool; - use embassy_futures::select::select; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::lazy_lock::LazyLock; @@ -25,7 +23,7 @@ static mut BLOCK_BUF: LazyLock<[Block; BLOCKS]> = LazyLock::new(|| core::array::from_fn(|_| Block::new())); pub struct MassStorageClass<'d, D: Driver<'d>> { - temp_sd: Option, // temporarly owns sdcard when scsi is running + temp_sd: Option, // temporary owns sdcard when scsi is running ejected: bool, pending_eject: bool, bulk_out: D::EndpointOut, @@ -192,12 +190,15 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { Err(()) } } - ScsiCommand::RequestSense { desc, alloc_len } => Ok(()), + ScsiCommand::RequestSense { + desc: _, + alloc_len: _, + } => Ok(()), ScsiCommand::ModeSense6 { - dbd, - page_control, - page_code, - subpage_code, + dbd: _, + page_control: _, + page_code: _, + subpage_code: _, alloc_len, } => { // DBD=0, no block descriptors; total length = 4 @@ -213,10 +214,10 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { self.bulk_in.write(&response[..len]).await.map_err(|_| ()) } ScsiCommand::ModeSense10 { - dbd, - page_control, - page_code, - subpage_code, + dbd: _, + page_control: _, + page_code: _, + subpage_code: _, alloc_len, } => { let response = [ @@ -377,6 +378,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { } #[repr(C, packed)] +#[allow(non_snake_case)] struct CommandBlockWrapper { dCBWSignature: u32, dCBWTag: u32, @@ -387,6 +389,7 @@ struct CommandBlockWrapper { CBWCB: [u8; 16], } +#[allow(non_snake_case)] impl CommandBlockWrapper { fn parse(buf: &[u8]) -> Option { if buf.len() < 31 { diff --git a/kernel/src/scsi/scsi_types.rs b/kernel/src/scsi/scsi_types.rs index dbd27dd..76bbb8a 100644 --- a/kernel/src/scsi/scsi_types.rs +++ b/kernel/src/scsi/scsi_types.rs @@ -1,10 +1,5 @@ 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 diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index ee1fa1e..bac9f44 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -101,7 +101,7 @@ impl SdCard { 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); + res = sd.write(blocks, start_block_idx); DummyTimeSource {} }); res.map_err(|_| ()) diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs index 56e31b8..65c4fb0 100644 --- a/kernel/src/usb.rs +++ b/kernel/src/usb.rs @@ -1,6 +1,6 @@ use crate::{scsi::MassStorageClass, storage::SdCard}; use core::sync::atomic::{AtomicBool, Ordering}; -use embassy_futures::{join::join, select::select}; +use embassy_futures::select::select; use embassy_rp::{peripherals::USB, usb::Driver}; use embassy_usb::{Builder, Config, UsbDevice}; From 4f93ee4e9d976f01fa50bfc9344649b9fe69a3c9 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 16:45:03 -0600 Subject: [PATCH 39/64] cargo upgrade --- Cargo.lock | 310 ++++++++++++++++++++-------------------------- kernel/Cargo.toml | 6 +- shared/Cargo.toml | 2 +- 3 files changed, 139 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ea5cb8..c20c89d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ - "term 1.1.0", + "term 1.2.0", ] [[package]] @@ -168,9 +168,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bitvec" @@ -216,9 +216,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" [[package]] name = "byteorder" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "codespan-reporting" @@ -280,7 +280,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -377,7 +377,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -388,7 +388,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -426,7 +426,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -435,7 +435,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -502,14 +502,31 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8578db196d74db92efdd5ebc546736dac1685499ee245b22eff92fa5e4b57945" +checksum = "8c62a3bf127e03832fb97d8b01a058775e617653bc89e2a12c256485a7fb54c1" +dependencies = [ + "embassy-embedded-hal 0.4.0", + "embassy-futures", + "embassy-sync 0.6.2", + "embassy-time 0.4.0", + "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-embedded-hal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1611b7a7ab5d1fbed84c338df26d56fd9bded58006ebb029075112ed2c5e039" dependencies = [ "embassy-futures", "embassy-hal-internal 0.3.0", "embassy-sync 0.7.2", - "embassy-time 0.4.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -558,7 +575,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -625,7 +642,7 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", - "embassy-embedded-hal 0.3.1", + "embassy-embedded-hal 0.3.2", "embassy-futures", "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", @@ -923,7 +940,7 @@ checksum = "4f6e621fe4c7e05b695274b722dc0a60bacd1c8696b58191baa0154713d52400" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1091,7 +1108,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1143,9 +1160,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e961b33649994dcf69303af6b3a332c1228549e604d455d61ec5d2ab5e68d3a" +checksum = "d6a80adfd63bd7ffd94fefc3d22167880c440a724303080e5aa686fa36abaa96" dependencies = [ "plain", "scroll", @@ -1181,9 +1198,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heapless" @@ -1210,12 +1227,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -1249,9 +1266,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" dependencies = [ "once_cell", "wasm-bindgen", @@ -1272,7 +1289,7 @@ version = "0.1.0" dependencies = [ "abi_sys", "assign-resources", - "bitflags 2.9.1", + "bitflags 2.9.4", "bt-hci", "cortex-m", "cortex-m-rt", @@ -1280,7 +1297,7 @@ dependencies = [ "cyw43-pio", "defmt 0.3.100", "defmt-rtt", - "embassy-embedded-hal 0.3.1", + "embassy-embedded-hal 0.3.2", "embassy-executor", "embassy-futures", "embassy-rp 0.8.0", @@ -1358,10 +1375,10 @@ dependencies = [ "petgraph 0.7.1", "pico-args", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", "sha3", "string_cache", - "term 1.1.0", + "term 1.2.0", "unicode-xid", "walkdir", ] @@ -1393,25 +1410,25 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", ] [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -1425,9 +1442,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" @@ -1509,7 +1526,7 @@ checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1554,7 +1571,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1695,7 +1712,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1762,14 +1779,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1803,11 +1820,11 @@ checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -1823,25 +1840,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -1852,9 +1869,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rgb" @@ -1895,9 +1912,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -1952,7 +1969,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1975,7 +1992,7 @@ dependencies = [ name = "shared" version = "0.1.0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "defmt 1.0.1", ] @@ -2093,9 +2110,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2130,11 +2147,11 @@ dependencies = [ [[package]] name = "term" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43bddab41f8626c7bdaab872bbba75f8df5847b516d77c569c746e2ae5eb746" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2157,11 +2174,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -2172,18 +2189,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2224,7 +2241,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "uuid", ] @@ -2236,9 +2253,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-width" @@ -2301,9 +2318,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2354,35 +2371,36 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2390,22 +2408,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" dependencies = [ "unicode-ident", ] @@ -2428,11 +2446,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -2441,22 +2459,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" dependencies = [ - "windows-targets 0.53.2", + "windows-link", ] [[package]] @@ -2465,30 +2489,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2497,96 +2505,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wyz" version = "0.5.1" @@ -2598,20 +2558,20 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 260d754..e903a6a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -45,7 +45,7 @@ embassy-rp = { version = "0.8.0", features = [ 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-embedded-hal = "0.3.2" embassy-sync = { version = "0.7" } trouble-host = { version = "0.1", features = [ "derive", @@ -80,10 +80,10 @@ kolibri-embedded-gui = "0.1.0" once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" -bitflags = "2.9.1" +bitflags = "2.9.4" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } -goblin = { version = "0.10.0", default-features = false, features = ["elf32"] } +goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" spin = "0.10.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 4ceba1e..87b06c2 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -7,5 +7,5 @@ edition = "2024" defmt = ["dep:defmt"] [dependencies] -bitflags = "2.9.1" +bitflags = "2.9.4" defmt = { version = "1.0.1", optional = true } From bf359f0513ded1311951d77647e9e6a6a0d9c67c Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 17:43:49 -0600 Subject: [PATCH 40/64] selection menu works for multiple binaries, and shows selection box --- kernel/Cargo.toml | 1 + kernel/src/main.rs | 4 +-- kernel/src/ui.rs | 83 +++++++++++++++++++++++----------------------- shared/Cargo.toml | 1 + 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index e903a6a..c168653 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -17,6 +17,7 @@ rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", + "shared/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", diff --git a/kernel/src/main.rs b/kernel/src/main.rs index ad16856..f9f160c 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -18,8 +18,6 @@ mod ui; mod usb; mod utils; -use core::sync::atomic::{AtomicBool, Ordering}; - use crate::{ display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ @@ -31,6 +29,7 @@ use crate::{ ui::{SELECTIONS, clear_selection, ui_handler}, }; use abi_sys::EntryFn; +use alloc::string::String; use embedded_graphics::{ pixelcolor::Rgb565, prelude::{DrawTarget, RgbColor}, @@ -38,6 +37,7 @@ use embedded_graphics::{ use {defmt_rtt as _, panic_probe as _}; +use core::sync::atomic::{AtomicBool, Ordering}; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::{join::join, select::select}; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index e37ccc1..7ab9bd8 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -6,7 +6,10 @@ use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_time::Timer; use embedded_graphics::{ Drawable, - mono_font::{MonoTextStyle, ascii::FONT_9X15}, + mono_font::{ + MonoTextStyle, + ascii::{FONT_9X15, FONT_10X20}, + }, pixelcolor::Rgb565, prelude::{Dimensions, Point, Primitive, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle}, @@ -14,8 +17,7 @@ use embedded_graphics::{ }; use embedded_layout::{ align::{horizontal, vertical}, - layout::linear::LinearLayout, - object_chain::Chain, + layout::linear::{FixedMargin, LinearLayout}, prelude::*, }; use embedded_text::TextBox; @@ -29,19 +31,18 @@ pub async fn ui_handler() { if let Some(event) = keyboard::read_keyboard_fifo().await { if let KeyState::Pressed = event.state { match event.key { - KeyCode::JoyUp => { + KeyCode::Up => { let mut selections = SELECTIONS.lock().await; selections.up(); } - KeyCode::JoyDown => { + KeyCode::Down => { let mut selections = SELECTIONS.lock().await; selections.down(); } - KeyCode::Enter | KeyCode::JoyRight => { + KeyCode::Enter | KeyCode::Right => { let selections = SELECTIONS.lock().await; - let selection = selections.selections - [selections.current_selection as usize - 1] - .clone(); + let selection = + selections.selections[selections.current_selection as usize].clone(); let entry = unsafe { load_binary(&selection.short_name).await.unwrap() }; BINARY_CH.send(entry).await; @@ -56,8 +57,6 @@ pub async fn ui_handler() { clear_selection().await; draw_selection().await; } - - Timer::after_millis(50).await; } } @@ -73,12 +72,10 @@ pub async fn clear_selection() { } async fn draw_selection() { - let file_names: Vec = { - let guard = SELECTIONS.lock().await; - guard.selections.clone() - }; + let mut guard = SELECTIONS.lock().await; + let file_names = &guard.selections.clone(); - let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); 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"; @@ -96,37 +93,37 @@ async fn draw_selection() { .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(unsafe { &mut FRAMEBUFFER }) - .unwrap(); + let mut views: alloc::vec::Vec>> = Vec::new(); - return; - }; + for i in file_names { + views.push(Text::new(&i.long_name, Point::zero(), text_style)); + } - let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style)); + let views_group = Views::new(views.as_mut_slice()); - // for _ in 0..file_names.len() { - // let chain = chain.append(Text::new( - // file_names.next().unwrap(), - // Point::zero(), - // text_style, - // )); - // } - - let layout = LinearLayout::vertical(chain) + let layout = LinearLayout::vertical(views_group) .with_alignment(horizontal::Center) + .with_spacing(FixedMargin(5)) .arrange() .align_to(&display_area, horizontal::Center, vertical::Center); - SELECTIONS.lock().await.last_bounds = Some(layout.bounds()); + // draw selected box + let selected_bounds = layout + .inner() + .get(guard.current_selection as usize) + .expect("Selected binary missing") + .bounding_box(); + Rectangle::new(selected_bounds.top_left, selected_bounds.size) + .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) + .draw(unsafe { &mut FRAMEBUFFER }) + .unwrap(); + + guard.last_bounds = Some(layout.bounds()); layout.draw(unsafe { &mut FRAMEBUFFER }).unwrap(); } - let mut sel = SELECTIONS.lock().await; - sel.changed = false; + guard.changed = false; } #[derive(Clone)] @@ -167,15 +164,17 @@ impl SelectionList { self.changed = true; } - fn down(&mut self) { - if self.current_selection + 1 < self.selections.len() as u16 { - self.current_selection += 1 + fn up(&mut self) { + if self.current_selection > 0 { + self.current_selection -= 1; + self.changed = true; } } - fn up(&mut self) { - if self.current_selection > self.selections.len() as u16 { - self.current_selection -= 1 + fn down(&mut self) { + if self.current_selection + 1 < self.selections.len() as u16 { + self.current_selection += 1; + self.changed = true; } } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 87b06c2..89519f1 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [features] +default = [] defmt = ["dep:defmt"] [dependencies] From 9468ceab51df605726b814fb5254179110412ff5 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 17:48:21 -0600 Subject: [PATCH 41/64] fix first selection --- kernel/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 7ab9bd8..a7372e1 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -160,7 +160,7 @@ impl SelectionList { } pub fn reset(&mut self) { - self.current_selection = 1; + self.current_selection = 0; self.changed = true; } From e628c47e4b4d1b478119bd2ebc3fd68199ef2826 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 19:20:20 -0600 Subject: [PATCH 42/64] remove screen tearing --- abi/src/lib.rs | 2 +- abi_sys/src/lib.rs | 18 +++++++++++++++--- kernel/src/abi.rs | 14 ++++++++++++-- kernel/src/display.rs | 21 ++++++++++----------- kernel/src/elf.rs | 1 + kernel/src/main.rs | 1 - kernel/src/ui.rs | 16 ++++++++++------ user-apps/calculator/src/main.rs | 5 ++++- 8 files changed, 53 insertions(+), 25 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index db012a0..f4c8b4c 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, sleep}; +pub use abi_sys::{get_key, lock_display, print, sleep}; 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 e503c5b..43317da 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:: pub enum CallAbiTable { Print = 0, Sleep = 1, - DrawIter = 2, - GetKey = 3, + LockDisplay = 2, + DrawIter = 3, + GetKey = 4, } impl CallAbiTable { - pub const COUNT: usize = 4; + pub const COUNT: usize = 5; } pub type PrintAbi = extern "Rust" fn(msg: &str); @@ -51,6 +52,17 @@ pub fn sleep(ms: u64) { } } +pub type LockDisplay = extern "Rust" fn(lock: bool); + +#[allow(unused)] +pub fn lock_display(lock: bool) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]; + let f: LockDisplay = core::mem::transmute(ptr); + f(lock); + } +} + pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); #[allow(unused)] diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 0b59d51..fb17722 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,13 +1,19 @@ -use abi_sys::{DrawIterAbi, GetKeyAbi, PrintAbi, SleepAbi}; +use core::sync::atomic::Ordering; + +use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, SleepAbi}; use embassy_rp::clocks::clk_sys_freq; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; use shared::keyboard::KeyEvent; -use crate::{KEY_CACHE, display::FRAMEBUFFER}; +use crate::{ + KEY_CACHE, + display::{FB_PAUSED, FRAMEBUFFER}, +}; // ensure the abi and the kernel fn signatures are the same const _: PrintAbi = print; const _: SleepAbi = sleep; +const _: LockDisplay = lock_display; const _: DrawIterAbi = draw_iter; const _: GetKeyAbi = get_key; @@ -24,6 +30,10 @@ pub extern "Rust" fn sleep(ms: u64) { } } +pub extern "Rust" fn lock_display(lock: bool) { + FB_PAUSED.store(lock, Ordering::Relaxed); +} + // TODO: maybe return result pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 1c7e369..a1e0000 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,3 +1,5 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + use crate::framebuffer::AtomicFrameBuffer; use embassy_rp::{ Peri, @@ -24,6 +26,7 @@ pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new(); +pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -48,20 +51,16 @@ pub async fn init_display( display } -pub fn clear_fb() { - unsafe { - FRAMEBUFFER.clear(Rgb565::WHITE).unwrap(); - } -} - #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { loop { - unsafe { - FRAMEBUFFER - .partial_draw_batched(&mut display) - .await - .unwrap() + if !FB_PAUSED.load(Ordering::Acquire) { + unsafe { + FRAMEBUFFER + .partial_draw_batched(&mut display) + .await + .unwrap() + } } Timer::after_millis(32).await; // 30 fps diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 0c9bd04..608595b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -54,6 +54,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result { let entries: &[(CallAbiTable, usize)] = &[ (CallAbiTable::Print, abi::print as usize), (CallAbiTable::Sleep, abi::sleep as usize), + (CallAbiTable::LockDisplay, abi::lock_display 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 f9f160c..2de46c4 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -29,7 +29,6 @@ use crate::{ ui::{SELECTIONS, clear_selection, ui_handler}, }; use abi_sys::EntryFn; -use alloc::string::String; use embedded_graphics::{ pixelcolor::Rgb565, prelude::{DrawTarget, RgbColor}, diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index a7372e1..c7efd85 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,15 +1,16 @@ use crate::{ - BINARY_CH, display::FRAMEBUFFER, elf::load_binary, peripherals::keyboard, storage::FileName, + BINARY_CH, + display::{FB_PAUSED, FRAMEBUFFER}, + elf::load_binary, + peripherals::keyboard, + storage::FileName, }; use alloc::{str::FromStr, string::String, vec::Vec}; +use core::sync::atomic::Ordering; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; -use embassy_time::Timer; use embedded_graphics::{ Drawable, - mono_font::{ - MonoTextStyle, - ascii::{FONT_9X15, FONT_10X20}, - }, + mono_font::{MonoTextStyle, ascii::FONT_10X20}, pixelcolor::Rgb565, prelude::{Dimensions, Point, Primitive, RgbColor, Size}, primitives::{PrimitiveStyle, Rectangle}, @@ -81,6 +82,8 @@ async fn draw_selection() { 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(); + FB_PAUSED.store(true, Ordering::Release); // ensure all elements show up at once + if file_names.is_empty() { TextBox::new( &no_bins, @@ -124,6 +127,7 @@ async fn draw_selection() { } guard.changed = false; + FB_PAUSED.store(false, Ordering::Release); // ensure all elements show up at once } #[derive(Clone)] diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index f66f5e3..dfe45c0 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, get_key, print}; +use abi::{KeyCode, display::Display, get_key, lock_display, print}; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ @@ -57,6 +57,8 @@ pub fn main() { loop { if dirty { + lock_display(true); + let style = PrimitiveStyle::with_fill(Rgb565::BLACK); if let Some(area) = last_area { Rectangle::new(area.0.top_left, area.0.size) @@ -100,6 +102,7 @@ pub fn main() { eq_layout.draw(&mut display).unwrap(); dirty = false; + lock_display(false); } if let Some(event) = get_key() { From 2012e6899535561f116dd479cc7a7f1f58081f99 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 18 Sep 2025 19:21:17 -0600 Subject: [PATCH 43/64] remove unused --- kernel/src/display.rs | 4 --- kernel/src/framebuffer.rs | 63 --------------------------------------- 2 files changed, 67 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index a1e0000..5ed21e6 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -8,10 +8,6 @@ use embassy_rp::{ spi::{Async, Spi}, }; use embassy_time::{Delay, Timer}; -use embedded_graphics::{ - draw_target::DrawTarget, - pixelcolor::{Rgb565, RgbColor}, -}; use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index a029231..04823f8 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -184,69 +184,6 @@ impl AtomicFrameBuffer { 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, From d00e644100d39c48a3e5689af839eb853c421930 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 19 Sep 2025 13:08:16 -0600 Subject: [PATCH 44/64] semi-working snake game --- Cargo.lock | 84 +++++++++++++++++++++++--------- Cargo.toml | 2 +- abi/Cargo.toml | 1 + abi/src/lib.rs | 32 +++++++++++- abi_sys/src/lib.rs | 21 +++++++- justfile | 4 +- kernel/Cargo.toml | 1 + kernel/src/abi.rs | 29 ++++++++--- kernel/src/display.rs | 2 +- kernel/src/elf.rs | 1 + user-apps/calculator/src/main.rs | 8 +-- user-apps/snake/Cargo.toml | 10 ++++ user-apps/snake/build.rs | 28 +++++++++++ user-apps/snake/src/main.rs | 69 ++++++++++++++++++++++++++ 14 files changed, 252 insertions(+), 40 deletions(-) create mode 100644 user-apps/snake/Cargo.toml create mode 100644 user-apps/snake/build.rs create mode 100644 user-apps/snake/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c20c89d..d38c95f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ version = "0.1.0" dependencies = [ "abi_sys", "embedded-graphics", + "rand_core 0.9.3", "shared", "spin", "talc", @@ -956,6 +957,14 @@ dependencies = [ "heapless", ] +[[package]] +name = "embedded-snake" +version = "0.0.3" +dependencies = [ + "embedded-graphics", + "rand_core 0.9.3", +] + [[package]] name = "embedded-storage" version = "0.3.1" @@ -1198,9 +1207,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heapless" @@ -1227,12 +1236,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -1266,9 +1275,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -1319,6 +1328,7 @@ dependencies = [ "once_cell", "panic-probe", "portable-atomic", + "rand", "shared", "spin", "st7365p-lcd", @@ -1416,9 +1426,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", @@ -1806,6 +1816,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -1954,18 +1973,27 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -2026,6 +2054,16 @@ dependencies = [ "rgb", ] +[[package]] +name = "snake" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "embedded-snake", + "rand", +] + [[package]] name = "spin" version = "0.10.0" @@ -2371,9 +2409,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", @@ -2384,9 +2422,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", @@ -2398,9 +2436,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2408,9 +2446,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", @@ -2421,9 +2459,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 067cbbe..da625f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["kernel", "abi", "shared", "user-apps/calculator"] +members = ["kernel", "abi", "shared", "user-apps/calculator", "user-apps/snake"] [profile.release] debug = true diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 3273c92..3d46683 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -9,3 +9,4 @@ shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } talc = "4.4.3" spin = "0.10.0" +rand_core = "0.9.3" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index f4c8b4c..c45e543 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,7 +1,8 @@ #![no_std] -use abi_sys::draw_iter; +use abi_sys::{RngRequest, draw_iter, gen_rand}; pub use abi_sys::{get_key, lock_display, print, sleep}; +use rand_core::RngCore; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; @@ -71,3 +72,32 @@ pub mod display { } } } + +pub struct Rng; + +impl RngCore for Rng { + fn next_u32(&mut self) -> u32 { + let mut req = RngRequest::U32(0); + gen_rand(&mut req); + if let RngRequest::U32(i) = req { + return i; + }; + 0 + } + + fn next_u64(&mut self) -> u64 { + let mut req = RngRequest::U64(0); + gen_rand(&mut req); + if let RngRequest::U64(i) = req { + return i; + }; + 0 + } + fn fill_bytes(&mut self, dst: &mut [u8]) { + let mut req = RngRequest::Bytes { + ptr: dst.as_mut_ptr(), + len: dst.len(), + }; + gen_rand(&mut req); + } +} diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 43317da..8ec6e2e 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -24,10 +24,11 @@ pub enum CallAbiTable { LockDisplay = 2, DrawIter = 3, GetKey = 4, + GenRand = 5, } impl CallAbiTable { - pub const COUNT: usize = 5; + pub const COUNT: usize = 6; } pub type PrintAbi = extern "Rust" fn(msg: &str); @@ -84,3 +85,21 @@ pub fn get_key() -> Option { f() } } + +pub type GenRand = extern "Rust" fn(req: &mut RngRequest); + +#[repr(C)] +pub enum RngRequest { + U32(u32), + U64(u64), + Bytes { ptr: *mut u8, len: usize }, +} + +#[allow(unused)] +pub fn gen_rand(req: &mut RngRequest) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize]; + let f: GenRand = core::mem::transmute(ptr); + f(req) + } +} diff --git a/justfile b/justfile index 6b97834..499e29d 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,6 @@ -kernel: calculator +kernel: cargo run --bin kernel calculator: RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary +snake: + RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin snake --profile release-binary diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index c168653..54ed0d8 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -79,6 +79,7 @@ embedded-text = "0.7.2" embedded-layout = "0.4.2" kolibri-embedded-gui = "0.1.0" +rand = { version = "0.9.0", default-features = false } once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" bitflags = "2.9.4" diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index fb17722..f546f6e 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,8 +1,8 @@ -use core::sync::atomic::Ordering; - -use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, SleepAbi}; -use embassy_rp::clocks::clk_sys_freq; +use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, RngRequest, SleepAbi}; +use core::{ptr::slice_from_raw_parts, sync::atomic::Ordering}; +use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; +use rand::Rng; use shared::keyboard::KeyEvent; use crate::{ @@ -23,10 +23,10 @@ pub extern "Rust" fn print(msg: &str) { 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(); - } + let total_cycles = ms * cycles_per_ms as u64; + + for _ in 0..total_cycles { + cortex_m::asm::nop(); } } @@ -42,3 +42,16 @@ pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { pub extern "Rust" fn get_key() -> Option { unsafe { KEY_CACHE.dequeue() } } + +pub extern "Rust" fn gen_rand(req: &mut RngRequest) { + let mut rng = RoscRng; + + match req { + RngRequest::U32(i) => *i = rng.next_u32(), + RngRequest::U64(i) => *i = rng.next_u64(), + RngRequest::Bytes { ptr, len } => { + let slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(*ptr, *len) }; + rng.fill_bytes(slice); + } + } +} diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 5ed21e6..ee7996b 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -59,6 +59,6 @@ pub async fn display_handler(mut display: DISPLAY) { } } - Timer::after_millis(32).await; // 30 fps + Timer::after_millis(10).await; } } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 608595b..f4c8d56 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -57,6 +57,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result { (CallAbiTable::LockDisplay, abi::lock_display as usize), (CallAbiTable::DrawIter, abi::draw_iter as usize), (CallAbiTable::GetKey, abi::get_key as usize), + (CallAbiTable::GenRand, abi::gen_rand as usize), ]; assert!(entries.len() == CallAbiTable::COUNT); diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index dfe45c0..7bb663f 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -15,10 +15,10 @@ use embedded_graphics::{ text::Text, }; use embedded_layout::{ - align::{horizontal, vertical}, + View, + align::{Align, horizontal, vertical}, layout::linear::LinearLayout, - object_chain::Chain, - prelude::*, + prelude::Chain, }; #[panic_handler] @@ -37,7 +37,7 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Async Calculator app"); + print("Starting Calculator app"); let mut display = Display; let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; diff --git a/user-apps/snake/Cargo.toml b/user-apps/snake/Cargo.toml new file mode 100644 index 0000000..5ce4e74 --- /dev/null +++ b/user-apps/snake/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "snake" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../../abi" } +embedded-graphics = "0.8.1" +embedded-snake = { path = "../../../embedded-snake-rs" } +rand = { version = "0.9.0", default-features = false } diff --git a/user-apps/snake/build.rs b/user-apps/snake/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/snake/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/snake/src/main.rs b/user-apps/snake/src/main.rs new file mode 100644 index 0000000..9ac50df --- /dev/null +++ b/user-apps/snake/src/main.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use abi::{ + KeyCode, Rng, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, + get_key, lock_display, print, sleep, +}; +use alloc::format; +use core::panic::PanicInfo; +use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor}; +use embedded_snake::{Direction, SnakeGame}; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + print(&format!( + "user panic: {} @ {:?}", + info.message(), + info.location(), + )); + loop {} +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() { + main() +} + +const CELL_SIZE: usize = 8; + +pub fn main() { + print("Starting Snake app"); + let mut display = Display; + + let mut game = SnakeGame::<100, Rgb565, Rng>::new( + SCREEN_WIDTH as u16, + SCREEN_HEIGHT as u16, + CELL_SIZE as u16, + CELL_SIZE as u16, + Rng, + Rgb565::BLACK, + Rgb565::GREEN, + Rgb565::RED, + 50, + ); + + loop { + if let Some(event) = get_key() { + let direction = match event.key { + KeyCode::Up => Direction::Up, + KeyCode::Down => Direction::Down, + KeyCode::Right => Direction::Right, + KeyCode::Left => Direction::Left, + KeyCode::Esc => return, + _ => Direction::None, + }; + game.set_direction(direction); + } + + // ensure all draws show up at once + lock_display(true); + game.pre_draw(&mut display); + game.draw(&mut display); + lock_display(false); + + sleep(15); + } +} From 07b4905a06f91e25fbc3b63ff30152c4e30d0fd5 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 11:15:42 -0600 Subject: [PATCH 45/64] use strum for abicalltable --- Cargo.lock | 29 +++++++++++++++++++++++++++++ abi_sys/Cargo.toml | 1 + abi_sys/src/lib.rs | 3 ++- kernel/Cargo.toml | 1 + kernel/src/abi.rs | 3 +-- kernel/src/elf.rs | 32 +++++++++++++------------------- 6 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d38c95f..4e9918a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ version = "0.1.0" dependencies = [ "embedded-graphics", "shared", + "strum", ] [[package]] @@ -1222,6 +1223,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1333,6 +1340,7 @@ dependencies = [ "spin", "st7365p-lcd", "static_cell", + "strum", "talc", "trouble-host", ] @@ -2135,6 +2143,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index e460dea..e08c57c 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] embedded-graphics = "0.8.1" +strum = { version = "0.27.2", default-features = false, features = ["derive"] } shared = { path = "../shared" } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 8ec6e2e..2f15778 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -9,6 +9,7 @@ use embedded_graphics::{ pixelcolor::{Rgb565, RgbColor}, }; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +use strum::EnumIter; pub type EntryFn = fn(); @@ -17,7 +18,7 @@ pub type EntryFn = fn(); pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; #[repr(usize)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, EnumIter)] pub enum CallAbiTable { Print = 0, Sleep = 1, diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 54ed0d8..f1c8459 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -79,6 +79,7 @@ embedded-text = "0.7.2" embedded-layout = "0.4.2" kolibri-embedded-gui = "0.1.0" +strum = { version = "0.27.2", default-features = false } rand = { version = "0.9.0", default-features = false } once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index f546f6e..14a0194 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,8 +1,7 @@ use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, RngRequest, SleepAbi}; -use core::{ptr::slice_from_raw_parts, sync::atomic::Ordering}; +use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; -use rand::Rng; use shared::keyboard::KeyEvent; use crate::{ diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index f4c8d56..72e2161 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -15,6 +15,7 @@ use goblin::{ }, elf32::{section_header::SectionHeader, sym::Sym}, }; +use strum::IntoEnumIterator; const ELF32_HDR_SIZE: usize = 52; @@ -50,18 +51,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::LockDisplay, abi::lock_display as usize), - (CallAbiTable::DrawIter, abi::draw_iter as usize), - (CallAbiTable::GetKey, abi::get_key as usize), - (CallAbiTable::GenRand, abi::gen_rand as usize), - ]; - assert!(entries.len() == CallAbiTable::COUNT); - - patch_abi(entries, &elf_header, &mut file).unwrap(); + patch_abi(&elf_header, &mut file).unwrap(); // TODO: dynamically search for abi table @@ -77,11 +67,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result { } } -fn patch_abi( - entries: &[(CallAbiTable, usize)], - elf_header: &Header, - file: &mut File, -) -> Result<(), ()> { +fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { for i in 1..=elf_header.e_shnum { let sh = read_section(file, &elf_header, i.into()); @@ -117,9 +103,17 @@ fn patch_abi( if symbol_name == "CALL_ABI_TABLE" { let table_base = sym.st_value as *mut usize; - for &(abi_idx, func_ptr) in entries { + for (idx, call) in CallAbiTable::iter().enumerate() { + let ptr = match call { + CallAbiTable::Print => abi::print as usize, + CallAbiTable::Sleep => abi::sleep as usize, + CallAbiTable::LockDisplay => abi::lock_display as usize, + CallAbiTable::DrawIter => abi::draw_iter as usize, + CallAbiTable::GetKey => abi::get_key as usize, + CallAbiTable::GenRand => abi::gen_rand as usize, + }; unsafe { - table_base.add(abi_idx as usize).write(func_ptr); + table_base.add(idx as usize).write(ptr); } } return Ok(()); From 180d9e82d273f89c7eb2fd3e62556f337614065b Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 20:24:23 -0600 Subject: [PATCH 46/64] PIE binaries & fix currupted framebuffer --- Cargo.lock | 2 + abi_sys/Cargo.toml | 4 + abi_sys/src/lib.rs | 11 +- justfile | 6 +- kernel/Cargo.toml | 4 +- kernel/memory.x | 13 +- kernel/src/elf.rs | 274 ++++++++++++++++++++++++++++-------------- kernel/src/main.rs | 7 +- kernel/src/storage.rs | 9 +- kernel/src/ui.rs | 6 +- user-apps/memory.x | 18 ++- 11 files changed, 223 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e9918a..4f20b02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,7 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ + "defmt 0.3.100", "embedded-graphics", "shared", "strum", @@ -1307,6 +1308,7 @@ dependencies = [ "assign-resources", "bitflags 2.9.4", "bt-hci", + "bumpalo", "cortex-m", "cortex-m-rt", "cyw43", diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index e08c57c..502ac83 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -3,7 +3,11 @@ name = "abi_sys" version = "0.1.0" edition = "2024" +[features] +defmt = ["dep:defmt"] + [dependencies] embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } +defmt = { version = "0.3", optional = true } shared = { path = "../shared" } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2f15778..5a200f8 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -9,16 +9,17 @@ use embedded_graphics::{ pixelcolor::{Rgb565, RgbColor}, }; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -use strum::EnumIter; +use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); #[unsafe(no_mangle)] -#[unsafe(link_section = ".user_reloc")] +#[unsafe(link_section = ".syscall_table")] pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; #[repr(usize)] -#[derive(Clone, Copy, EnumIter)] +#[derive(Clone, Copy, EnumIter, EnumCount)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CallAbiTable { Print = 0, Sleep = 1, @@ -28,10 +29,6 @@ pub enum CallAbiTable { GenRand = 5, } -impl CallAbiTable { - pub const COUNT: usize = 6; -} - pub type PrintAbi = extern "Rust" fn(msg: &str); #[allow(unused)] diff --git a/justfile b/justfile index 499e29d..40f9fad 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,8 @@ +binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" + kernel: cargo run --bin kernel calculator: - RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary + {{binary-args}} cargo build --bin calculator --profile release-binary snake: - RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin snake --profile release-binary + {{binary-args}} cargo build --bin snake --profile release-binary diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index f1c8459..ab8d173 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -18,6 +18,7 @@ trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", "shared/defmt", + "abi_sys/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", @@ -85,10 +86,11 @@ once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" bitflags = "2.9.4" heapless = "0.8.0" +spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" -spin = "0.10.0" +bumpalo = "3.19.0" shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } diff --git a/kernel/memory.x b/kernel/memory.x index fd22299..a2edb86 100644 --- a/kernel/memory.x +++ b/kernel/memory.x @@ -1,10 +1,7 @@ MEMORY { FLASH : ORIGIN = 0x10000000, LENGTH = 4096K + RAM : ORIGIN = 0x20000000, LENGTH = 512K - - /* Reserve a block of RAM for the user app */ - USERAPP : ORIGIN = 0x20010000, LENGTH = 192K - SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } @@ -61,11 +58,3 @@ 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/elf.rs b/kernel/src/elf.rs index 72e2161..5f66daf 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,73 +1,175 @@ #![allow(static_mut_refs)] +use core::ptr; + use crate::{ abi, storage::{File, SDCARD}, }; use abi_sys::{CallAbiTable, EntryFn}; use alloc::{vec, vec::Vec}; +use bumpalo::Bump; use embedded_sdmmc::ShortFileName; use goblin::{ elf::{ header::header32::Header, program_header::program_header32::{PT_LOAD, ProgramHeader}, - section_header::SHT_SYMTAB, + reloc::R_ARM_RELATIVE, + section_header::{SHT_REL, SHT_SYMTAB}, }, - elf32::{section_header::SectionHeader, sym::Sym}, + elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym}, }; use strum::IntoEnumIterator; const ELF32_HDR_SIZE: usize = 52; -// userland ram region defined in memory.x -unsafe extern "C" { - static __userapp_start__: u8; - static __userapp_end__: u8; -} - -pub async unsafe fn load_binary(name: &ShortFileName) -> Result { +pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> { let mut sd_lock = SDCARD.get().lock().await; let sd = sd_lock.as_mut().unwrap(); - let error = ""; - let mut entry = 0; - let mut header_buf = [0; ELF32_HDR_SIZE]; - sd.read_file(name, |mut file| { - file.read(&mut header_buf).unwrap(); - let elf_header = Header::from_bytes(&header_buf); + let (entry, bump) = sd + .read_file(name, |mut file| { + file.read(&mut header_buf).unwrap(); + let elf_header = Header::from_bytes(&header_buf); - 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() + // reject non-PIE + if elf_header.e_type != header::ET_DYN { + return None; } - } - patch_abi(&elf_header, &mut file).unwrap(); + let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize]; - // TODO: dynamically search for abi table + let (total_size, min_vaddr, _max_vaddr) = + total_loadable_size(&mut file, &elf_header, &mut ph_buf); - entry = elf_header.e_entry as u32; - }) - .await - .unwrap(); + defmt::info!("total_size: {}", total_size); + let bump = Bump::with_capacity(total_size); + let base = bump.alloc_slice_fill_default::(total_size); + defmt::info!("base ptr: {}", base.as_ptr()); - if entry != 0 { - Ok(unsafe { core::mem::transmute(entry) }) - } else { - Err(error) - } + // load each segment into bump, relative to base_ptr + for i in 0..elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(&mut ph_buf).unwrap(); + let ph = cast_phdr(&ph_buf); + + let seg_offset = (ph.p_vaddr - min_vaddr) as usize; + defmt::info!("segment offset {}", seg_offset); + let mut segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize]; + + if ph.p_type == PT_LOAD { + load_segment(&mut file, &ph, &mut segment).unwrap(); + } + } + + let mut sh_buf = vec![0_u8; elf_header.e_shentsize as usize]; + + for i in 0..elf_header.e_shnum { + file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize * i) as u32) + .unwrap(); + file.read(&mut sh_buf).unwrap(); + let sh = cast_shdr(&sh_buf); + + match sh.sh_type { + SHT_REL => { + apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file).unwrap(); + } + _ => {} + } + } + + patch_abi(&elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap(); + + defmt::info!("elf entry point before offset: {}", elf_header.e_entry); + // entry pointer is base_ptr + (entry - min_vaddr) + let entry_ptr: EntryFn = unsafe { + core::mem::transmute(base.as_ptr().add((elf_header.e_entry - min_vaddr) as usize)) + }; + defmt::info!("entry ptr: {}", entry_ptr); + + Some((entry_ptr, bump)) + }) + .await + .expect("Failed to read file")?; + + Some((entry, bump)) } -fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { +fn load_segment(file: &mut File, ph: &ProgramHeader, segment: &mut [u8]) -> Result<(), ()> { + let filesz = ph.p_filesz as usize; + let memsz = ph.p_memsz as usize; + + // read file contents + let mut remaining = filesz; + let mut dst_offset = 0; + let mut file_offset = ph.p_offset; + let mut buf = [0u8; 512]; + + while remaining > 0 { + let to_read = core::cmp::min(remaining, buf.len()); + file.seek_from_start(file_offset).unwrap(); + file.read(&mut buf[..to_read]).unwrap(); + + segment[dst_offset..dst_offset + to_read].copy_from_slice(&buf[..to_read]); + + remaining -= to_read; + dst_offset += to_read; + file_offset += to_read as u32; + } + + // zero BSS if needed + if memsz > filesz { + segment[filesz..].fill(0); + } + + Ok(()) +} + +fn apply_relocations( + sh: &SectionHeader, + min_vaddr: u32, + base: *mut u8, + file: &mut File, +) -> Result<(), ()> { + let mut reloc = [0_u8; 8]; + + let num_relocs = sh.sh_size as usize / sh.sh_entsize as usize; + + for i in 0..num_relocs { + file.seek_from_start(sh.sh_offset + (i as u32 * 8)).unwrap(); + file.read(&mut reloc).unwrap(); + + let rel = cast_rel(&reloc); + + let reloc_type = rel.r_info & 0xff; + let reloc_addr = unsafe { base.add((rel.r_offset - min_vaddr) as usize) as *mut u32 }; + + match reloc_type { + R_ARM_RELATIVE => { + // REL: add base to the word already stored there + unsafe { + let val = ptr::read_unaligned(reloc_addr); + ptr::write_unaligned(reloc_addr, val.wrapping_add(base as u32)); + } + } + _ => { + defmt::warn!("Unsupported relocation type: {}", reloc_type); + return Err(()); + } + } + } + Ok(()) +} + +fn patch_abi( + elf_header: &Header, + base: *mut u8, + min_vaddr: u32, + file: &mut File, +) -> Result<(), ()> { for i in 1..=elf_header.e_shnum { let sh = read_section(file, &elf_header, i.into()); @@ -101,7 +203,11 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { let symbol_name = core::str::from_utf8(&name).unwrap(); if symbol_name == "CALL_ABI_TABLE" { - let table_base = sym.st_value as *mut usize; + let table_base = + unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } + as *mut usize; + defmt::info!("CALL_ABI_TABLE st_value: {:x}", sym.st_value); + defmt::info!("table base {}", table_base); for (idx, call) in CallAbiTable::iter().enumerate() { let ptr = match call { @@ -113,6 +219,13 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { CallAbiTable::GenRand => abi::gen_rand as usize, }; unsafe { + defmt::info!( + "table {:?}#{} @ {} -> 0x{:X}", + call, + idx, + table_base.wrapping_add(idx), + ptr + ); table_base.add(idx as usize).write(ptr); } } @@ -124,6 +237,33 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { Err(()) } +fn total_loadable_size( + file: &mut File, + elf_header: &Header, + ph_buf: &mut [u8], +) -> (usize, u32, u32) { + let mut min_vaddr = u32::MAX; + let mut max_vaddr = 0u32; + for i in 0..elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(ph_buf).unwrap(); + let ph = cast_phdr(&ph_buf); + + if ph.p_type == PT_LOAD { + if ph.p_vaddr < min_vaddr { + min_vaddr = ph.p_vaddr; + } + if ph.p_vaddr + ph.p_memsz > max_vaddr { + max_vaddr = ph.p_vaddr + ph.p_memsz; + } + } + } + + let total_size = (max_vaddr - min_vaddr) as usize; + (total_size, min_vaddr, max_vaddr) +} + 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]; @@ -134,57 +274,6 @@ fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHe 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(); - - unsafe { - // 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(()) -} - fn cast_phdr(buf: &[u8]) -> ProgramHeader { assert!(buf.len() >= core::mem::size_of::()); unsafe { core::ptr::read(buf.as_ptr() as *const ProgramHeader) } @@ -199,3 +288,8 @@ fn cast_sym(buf: &[u8]) -> Sym { assert!(buf.len() >= core::mem::size_of::()); unsafe { core::ptr::read(buf.as_ptr() as *const Sym) } } + +fn cast_rel(buf: &[u8]) -> Rel { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const Rel) } +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 2de46c4..894b971 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -29,6 +29,7 @@ use crate::{ ui::{SELECTIONS, clear_selection, ui_handler}, }; use abi_sys::EntryFn; +use bumpalo::Bump; use embedded_graphics::{ pixelcolor::Rgb565, prelude::{DrawTarget, RgbColor}, @@ -72,7 +73,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 10 * 1024] = [0; 10 * 1024]; +static mut ARENA: [u8; 200 * 1024] = [0; 200 * 1024]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -124,14 +125,14 @@ async fn main(_spawner: Spawner) { } // One-slot channel to pass EntryFn from core1 -static BINARY_CH: Channel = Channel::new(); +static BINARY_CH: Channel = Channel::new(); // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { let recv = BINARY_CH.receiver(); loop { - let entry = recv.receive().await; + let (entry, _bump) = recv.receive().await; defmt::info!("Got Entry"); // disable kernel ui diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index bac9f44..ad2b4a3 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -114,16 +114,15 @@ impl SdCard { access(root_dir); } - pub async fn read_file( + pub async fn read_file( &mut self, name: &ShortFileName, - mut access: impl FnMut(File), - ) -> Result<(), ()> { + mut access: impl FnMut(File) -> T, + ) -> 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 = Ok(access(file)); } }); diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index c7efd85..f4b9d85 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -45,7 +45,11 @@ pub async fn ui_handler() { let selection = selections.selections[selections.current_selection as usize].clone(); - let entry = unsafe { load_binary(&selection.short_name).await.unwrap() }; + let entry = unsafe { + load_binary(&selection.short_name) + .await + .expect("unable to load binary") + }; BINARY_CH.send(entry).await; } _ => (), diff --git a/user-apps/memory.x b/user-apps/memory.x index 19fa9a5..36c9663 100644 --- a/user-apps/memory.x +++ b/user-apps/memory.x @@ -1,19 +1,10 @@ MEMORY { - /* Must match the USERAPP region in the kernel linker script */ - RAM : ORIGIN = 0x20010000, LENGTH = 192K + RAM : ORIGIN = 0x0, LENGTH = 100K } 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.*); @@ -30,4 +21,11 @@ SECTIONS *(.bss .bss.*); *(COMMON); } > RAM + + .syscall_table (NOLOAD) : ALIGN(4) + { + __user_reloc_start = .; + KEEP(*(.user_reloc*)); + __user_reloc_end = .; + } > RAM } From fb2c3583deff9b87cf2dffc2d67cb051ac1b17b2 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 20:26:42 -0600 Subject: [PATCH 47/64] remove debugs --- kernel/src/elf.rs | 15 --------------- kernel/src/main.rs | 1 - 2 files changed, 16 deletions(-) diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 5f66daf..a042c0b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -44,10 +44,8 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> let (total_size, min_vaddr, _max_vaddr) = total_loadable_size(&mut file, &elf_header, &mut ph_buf); - defmt::info!("total_size: {}", total_size); let bump = Bump::with_capacity(total_size); let base = bump.alloc_slice_fill_default::(total_size); - defmt::info!("base ptr: {}", base.as_ptr()); // load each segment into bump, relative to base_ptr for i in 0..elf_header.e_phnum { @@ -57,7 +55,6 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> let ph = cast_phdr(&ph_buf); let seg_offset = (ph.p_vaddr - min_vaddr) as usize; - defmt::info!("segment offset {}", seg_offset); let mut segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize]; if ph.p_type == PT_LOAD { @@ -83,12 +80,10 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> patch_abi(&elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap(); - defmt::info!("elf entry point before offset: {}", elf_header.e_entry); // entry pointer is base_ptr + (entry - min_vaddr) let entry_ptr: EntryFn = unsafe { core::mem::transmute(base.as_ptr().add((elf_header.e_entry - min_vaddr) as usize)) }; - defmt::info!("entry ptr: {}", entry_ptr); Some((entry_ptr, bump)) }) @@ -156,7 +151,6 @@ fn apply_relocations( } } _ => { - defmt::warn!("Unsupported relocation type: {}", reloc_type); return Err(()); } } @@ -206,8 +200,6 @@ fn patch_abi( let table_base = unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } as *mut usize; - defmt::info!("CALL_ABI_TABLE st_value: {:x}", sym.st_value); - defmt::info!("table base {}", table_base); for (idx, call) in CallAbiTable::iter().enumerate() { let ptr = match call { @@ -219,13 +211,6 @@ fn patch_abi( CallAbiTable::GenRand => abi::gen_rand as usize, }; unsafe { - defmt::info!( - "table {:?}#{} @ {} -> 0x{:X}", - call, - idx, - table_base.wrapping_add(idx), - ptr - ); table_base.add(idx as usize).write(ptr); } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 894b971..1deaba8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -133,7 +133,6 @@ async fn userland_task() { let recv = BINARY_CH.receiver(); loop { let (entry, _bump) = recv.receive().await; - defmt::info!("Got Entry"); // disable kernel ui { From dbb811c9f55c4cdf7cb8edc07117965f16fad716 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 20:29:38 -0600 Subject: [PATCH 48/64] use section parser --- kernel/src/elf.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index a042c0b..cbd33b7 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -62,13 +62,8 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> } } - let mut sh_buf = vec![0_u8; elf_header.e_shentsize as usize]; - for i in 0..elf_header.e_shnum { - file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize * i) as u32) - .unwrap(); - file.read(&mut sh_buf).unwrap(); - let sh = cast_shdr(&sh_buf); + let sh = read_section(&mut file, elf_header, i.into()); match sh.sh_type { SHT_REL => { @@ -250,13 +245,13 @@ fn total_loadable_size( } 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 mut sh_buf = vec![0_u8; elf_header.e_shentsize as usize]; file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize as u32 * section)) .unwrap(); - file.read(&mut section_header_buf).unwrap(); + file.read(&mut sh_buf).unwrap(); - cast_shdr(§ion_header_buf) + cast_shdr(&sh_buf) } fn cast_phdr(buf: &[u8]) -> ProgramHeader { From 16e0374550a95bcd055aa49744a9b900c8ac4deb Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 20:30:21 -0600 Subject: [PATCH 49/64] remove unused --- kernel/src/elf.rs | 5 +---- kernel/src/main.rs | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index cbd33b7..619831d 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,7 +1,3 @@ -#![allow(static_mut_refs)] - -use core::ptr; - use crate::{ abi, storage::{File, SDCARD}, @@ -9,6 +5,7 @@ use crate::{ use abi_sys::{CallAbiTable, EntryFn}; use alloc::{vec, vec::Vec}; use bumpalo::Bump; +use core::ptr; use embedded_sdmmc::ShortFileName; use goblin::{ elf::{ diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 1deaba8..38246c8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,6 +1,4 @@ #![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)] From bf3b37783fb2fcb3016e20eaee59aab47a69204e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 22:51:35 -0600 Subject: [PATCH 50/64] Readme --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad9c6b6 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# PicoCalc OS (Rust) + +A simple operating system for the **Clockwork PicoCalc**, written in Rust. +This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware. + +## Project Structure + +- **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) +- **`abi/`** – Shared application binary interface definitions for kernel ↔ userspace interaction +- **`abi_sys/`** – System-level ABI helpers +- **`shared/`** – Shared utilities and common code across kernel and user applications +- **`user-apps/`** – Collection of userspace programs (calculator, snake, etc.) + +## Features + +- Minimal Rust-based kernel targeting the PicoCalc +- Custom ABI for safe communication between kernel and applications +- Support for multiple user-space applications +- Hardware drivers tailored for the PicoCalc + +## Getting Started + +```bash +git clone https://github.com/LegitCamper/picocalc-os-rs.git +cd picocalc-os-rs +just calculator +# copy the calculator to the sdcard and rename it to calculator.bin +just kernel From 7bd60127487ded7ac2b2741fd3e498d9eb35e1fb Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 26 Sep 2025 23:39:19 -0600 Subject: [PATCH 51/64] use C abi for syscalls --- abi_sys/src/lib.rs | 54 +++++++++++++------------------- kernel/src/abi.rs | 45 +++++++++++++++++--------- shared/src/lib.rs | 2 ++ user-apps/calculator/src/main.rs | 5 +-- user-apps/snake/src/main.rs | 7 +++-- 5 files changed, 61 insertions(+), 52 deletions(-) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 5a200f8..dc52ba4 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -29,63 +29,49 @@ pub enum CallAbiTable { GenRand = 5, } -pub type PrintAbi = extern "Rust" fn(msg: &str); +pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); #[allow(unused)] pub fn print(msg: &str) { - unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; - let f: PrintAbi = core::mem::transmute(ptr); - f(msg); - } + let f: PrintAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::Print as usize]) }; + f(msg.as_ptr(), msg.len()); } -pub type SleepAbi = extern "Rust" fn(ms: u64); +pub type SleepAbi = extern "C" fn(ms: u64); #[allow(unused)] pub fn sleep(ms: u64) { - unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::Sleep as usize]; - let f: SleepAbi = core::mem::transmute(ptr); - f(ms); - } + let f: SleepAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::Sleep as usize]) }; + f(ms); } -pub type LockDisplay = extern "Rust" fn(lock: bool); +pub type LockDisplay = extern "C" fn(lock: bool); #[allow(unused)] pub fn lock_display(lock: bool) { - unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]; - let f: LockDisplay = core::mem::transmute(ptr); - f(lock); - } + let f: LockDisplay = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]) }; + f(lock); } -pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); +pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); #[allow(unused)] 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); - } + let f: DrawIterAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; + f(pixels.as_ptr(), pixels.len()); } -pub type GetKeyAbi = extern "Rust" fn() -> Option; +pub type GetKeyAbi = extern "C" fn() -> KeyEvent; #[allow(unused)] -pub fn get_key() -> Option { - unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::GetKey as usize]; - let f: GetKeyAbi = core::mem::transmute(ptr); - f() - } +pub fn get_key() -> KeyEvent { + let f: GetKeyAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; + f() } -pub type GenRand = extern "Rust" fn(req: &mut RngRequest); - #[repr(C)] pub enum RngRequest { U32(u32), @@ -93,6 +79,8 @@ pub enum RngRequest { Bytes { ptr: *mut u8, len: usize }, } +pub type GenRand = extern "C" fn(req: &mut RngRequest); + #[allow(unused)] pub fn gen_rand(req: &mut RngRequest) { unsafe { diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 14a0194..95baf20 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,4 +1,6 @@ -use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, RngRequest, SleepAbi}; +use abi_sys::{ + DrawIterAbi, GenRand, GetKeyAbi, LockDisplay, Modifiers, PrintAbi, RngRequest, SleepAbi, +}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; @@ -9,18 +11,20 @@ use crate::{ display::{FB_PAUSED, FRAMEBUFFER}, }; -// ensure the abi and the kernel fn signatures are the same const _: PrintAbi = print; -const _: SleepAbi = sleep; -const _: LockDisplay = lock_display; -const _: DrawIterAbi = draw_iter; -const _: GetKeyAbi = get_key; +pub extern "C" fn print(ptr: *const u8, len: usize) { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; -pub extern "Rust" fn print(msg: &str) { - defmt::info!("{:?}", msg); + if let Ok(msg) = core::str::from_utf8(slice) { + defmt::info!("print: {}", msg); + } else { + defmt::warn!("print: "); + } } -pub extern "Rust" fn sleep(ms: u64) { +const _: SleepAbi = sleep; +pub extern "C" fn sleep(ms: u64) { let cycles_per_ms = clk_sys_freq() / 1000; let total_cycles = ms * cycles_per_ms as u64; @@ -29,20 +33,33 @@ pub extern "Rust" fn sleep(ms: u64) { } } -pub extern "Rust" fn lock_display(lock: bool) { +const _: LockDisplay = lock_display; +pub extern "C" fn lock_display(lock: bool) { FB_PAUSED.store(lock, Ordering::Relaxed); } +const _: DrawIterAbi = draw_iter; // TODO: maybe return result -pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { +pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { + let pixels = unsafe { core::slice::from_raw_parts(pixels, len) }; unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } } -pub extern "Rust" fn get_key() -> Option { - unsafe { KEY_CACHE.dequeue() } +const _: GetKeyAbi = get_key; +pub extern "C" fn get_key() -> KeyEvent { + if let Some(event) = unsafe { KEY_CACHE.dequeue() } { + event + } else { + KeyEvent { + key: abi_sys::KeyCode::Unknown(0), + state: abi_sys::KeyState::Idle, + mods: Modifiers::empty(), + } + } } -pub extern "Rust" fn gen_rand(req: &mut RngRequest) { +const _: GenRand = gen_rand; +pub extern "C" fn gen_rand(req: &mut RngRequest) { let mut rng = RoscRng; match req { diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3823869..b78e205 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -14,6 +14,7 @@ pub mod keyboard { } #[derive(Debug)] + #[repr(C)] pub struct KeyEvent { pub key: KeyCode, pub state: KeyState, @@ -22,6 +23,7 @@ pub mod keyboard { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] pub enum KeyState { Idle = 0, Pressed = 1, diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 7bb663f..54b9cbb 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, get_key, lock_display, print}; +use abi::{KeyCode, KeyState, display::Display, get_key, lock_display, print}; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ @@ -105,7 +105,8 @@ pub fn main() { lock_display(false); } - if let Some(event) = get_key() { + let event = get_key(); + if event.state != KeyState::Idle { match event.key { KeyCode::Char(ch) => { input.push(ch); diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 9ac50df..78d9451 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; use abi::{ - KeyCode, Rng, + KeyCode, KeyState, Rng, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, get_key, lock_display, print, sleep, }; @@ -46,7 +46,8 @@ pub fn main() { ); loop { - if let Some(event) = get_key() { + let event = get_key(); + if event.state != KeyState::Idle { let direction = match event.key { KeyCode::Up => Direction::Up, KeyCode::Down => Direction::Down, @@ -56,7 +57,7 @@ pub fn main() { _ => Direction::None, }; game.set_direction(direction); - } + }; // ensure all draws show up at once lock_display(true); From 2221ffdbdecd08063a150c926dcc7477a876cd51 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 28 Sep 2025 14:07:03 -0600 Subject: [PATCH 52/64] basic fs syscall(s) --- Cargo.lock | 1 + abi_sys/Cargo.toml | 1 + abi_sys/src/lib.rs | 48 +++++++++++++++++-- kernel/src/abi.rs | 105 +++++++++++++++++++++++++++++++++++++++++- kernel/src/elf.rs | 6 ++- kernel/src/main.rs | 19 ++++---- kernel/src/storage.rs | 2 +- 7 files changed, 162 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f20b02..905918f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ version = "0.1.0" dependencies = [ "defmt 0.3.100", "embedded-graphics", + "embedded-sdmmc", "shared", "strum", ] diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index 502ac83..ee7bad7 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -11,3 +11,4 @@ embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } defmt = { version = "0.3", optional = true } shared = { path = "../shared" } +embedded-sdmmc = { version = "0.9.0", default-features = false } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index dc52ba4..c8cd23f 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -8,6 +8,7 @@ use embedded_graphics::{ geometry::Point, pixelcolor::{Rgb565, RgbColor}, }; +use embedded_sdmmc::DirEntry; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; @@ -21,19 +22,22 @@ pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable:: #[derive(Clone, Copy, EnumIter, EnumCount)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CallAbiTable { - Print = 0, - Sleep = 1, + PrintString = 0, + SleepMs = 1, LockDisplay = 2, DrawIter = 3, GetKey = 4, GenRand = 5, + ListDir = 6, + ReadFile = 7, } pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); #[allow(unused)] pub fn print(msg: &str) { - let f: PrintAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::Print as usize]) }; + let f: PrintAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::PrintString as usize]) }; f(msg.as_ptr(), msg.len()); } @@ -41,7 +45,8 @@ pub type SleepAbi = extern "C" fn(ms: u64); #[allow(unused)] pub fn sleep(ms: u64) { - let f: SleepAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::Sleep as usize]) }; + let f: SleepAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; f(ms); } @@ -89,3 +94,38 @@ pub fn gen_rand(req: &mut RngRequest) { f(req) } } + +pub type ListDir = + extern "C" fn(str: *const u8, len: usize, files: *mut Option, file_len: usize); + +#[allow(unused)] +pub fn list_dir(path: &str, files: &mut [Option]) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; + let f: ListDir = core::mem::transmute(ptr); + f(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + } +} + +pub type ReadFile = extern "C" fn( + str: *const u8, + len: usize, + read_from: usize, + buf: *mut u8, + buf_len: usize, +) -> usize; + +#[allow(unused)] +pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::ReadFile as usize]; + let f: ReadFile = core::mem::transmute(ptr); + f( + file.as_ptr(), + file.len(), + read_from, + buf.as_mut_ptr(), + buf.len(), + ) + } +} diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 95baf20..1504af9 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,14 +1,18 @@ use abi_sys::{ - DrawIterAbi, GenRand, GetKeyAbi, LockDisplay, Modifiers, PrintAbi, RngRequest, SleepAbi, + DrawIterAbi, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile, + RngRequest, SleepAbi, }; +use alloc::vec::Vec; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; +use embedded_sdmmc::{DirEntry, ShortFileName}; +use heapless::spsc::Queue; use shared::keyboard::KeyEvent; use crate::{ - KEY_CACHE, display::{FB_PAUSED, FRAMEBUFFER}, + storage::{Dir, File, SDCARD}, }; const _: PrintAbi = print; @@ -41,10 +45,13 @@ pub extern "C" fn lock_display(lock: bool) { const _: DrawIterAbi = draw_iter; // TODO: maybe return result pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes let pixels = unsafe { core::slice::from_raw_parts(pixels, len) }; unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } } +pub static mut KEY_CACHE: Queue = Queue::new(); + const _: GetKeyAbi = get_key; pub extern "C" fn get_key() -> KeyEvent { if let Some(event) = unsafe { KEY_CACHE.dequeue() } { @@ -66,8 +73,102 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) { RngRequest::U32(i) => *i = rng.next_u32(), RngRequest::U64(i) => *i = rng.next_u64(), RngRequest::Bytes { ptr, len } => { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes let slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(*ptr, *len) }; rng.fill_bytes(slice); } } } + +fn get_dir_entries(dir: &Dir, files: &mut [Option]) { + let mut files = files.iter_mut(); + + dir.iterate_dir(|entry| { + if let Some(f) = files.next() { + *f = Some(entry.clone()) + } + }) + .unwrap() +} + +fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option]) { + if dirs.is_empty() { + get_dir_entries(dir, files); + return; + } + + let dir = dir.open_dir(dirs[0]).unwrap(); + recurse_dir(&dir, &dirs[1..], files); +} + +const _: ListDir = list_dir; +pub extern "C" fn list_dir( + dir: *const u8, + len: usize, + files: *mut Option, + files_len: usize, +) { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) }; + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let dir = unsafe { core::str::from_raw_parts(dir, len) }; + let dirs: Vec<&str> = dir.split('/').collect(); + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + sd.access_root_dir(|root| { + if !dir.is_empty() { + if dirs[0].is_empty() { + get_dir_entries(&root, files); + } else { + recurse_dir(&root, &dirs[1..], files); + } + } + }); +} + +fn recurse_file(dir: &Dir, dirs: &[&str], mut access: impl FnMut(&mut File) -> T) -> T { + if dirs.len() == 1 { + let file_name = ShortFileName::create_from_str(dirs[0]).unwrap(); + + let mut file = dir + .open_file_in_dir(file_name, embedded_sdmmc::Mode::ReadWriteAppend) + .unwrap(); + return access(&mut file); + } + + let dir = dir.open_dir(dirs[0]).unwrap(); + recurse_file(&dir, &dirs[1..], access) +} + +const _: ReadFile = read_file; +pub extern "C" fn read_file( + str: *const u8, + len: usize, + start_from: usize, + buf: *mut u8, + buf_len: usize, +) -> usize { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let file = unsafe { core::str::from_raw_parts(str, len) }; + let file: Vec<&str> = file.split('/').collect(); + + let mut res = 0; + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + if !file.is_empty() { + if file[0].is_empty() { + } else { + sd.access_root_dir(|root| { + res = recurse_file(&root, &file[1..], |file| { + file.seek_from_start(start_from as u32).unwrap(); + file.read(&mut buf).unwrap() + }) + }); + } + } + res +} diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 619831d..5e00074 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -195,12 +195,14 @@ fn patch_abi( for (idx, call) in CallAbiTable::iter().enumerate() { let ptr = match call { - CallAbiTable::Print => abi::print as usize, - CallAbiTable::Sleep => abi::sleep as usize, + CallAbiTable::PrintString => abi::print as usize, + CallAbiTable::SleepMs => abi::sleep as usize, CallAbiTable::LockDisplay => abi::lock_display as usize, CallAbiTable::DrawIter => abi::draw_iter as usize, CallAbiTable::GetKey => abi::get_key as usize, CallAbiTable::GenRand => abi::gen_rand as usize, + CallAbiTable::ListDir => abi::list_dir as usize, + CallAbiTable::ReadFile => abi::read_file as usize, }; unsafe { table_base.add(idx as usize).write(ptr); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 38246c8..431fc50 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,4 +1,5 @@ #![feature(impl_trait_in_assoc_type)] +#![feature(str_from_raw_parts)] #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![allow(static_mut_refs)] @@ -17,6 +18,7 @@ mod usb; mod utils; use crate::{ + abi::KEY_CACHE, display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ conf_peripherals, @@ -57,8 +59,6 @@ use embassy_sync::{ 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::*; @@ -251,23 +251,20 @@ async fn prog_search_handler() { loop { { let mut guard = SDCARD.get().lock().await; + let sd = guard.as_mut().unwrap(); - if let Some(sd) = guard.as_mut() { - let files = sd.list_files_by_extension(".bin").unwrap(); - let mut select = SELECTIONS.lock().await; + let files = sd.list_files_by_extension(".bin").unwrap(); + let mut select = SELECTIONS.lock().await; - if *select.selections() != files { - select.update_selections(files); - select.reset(); - } + if *select.selections() != files { + select.update_selections(files); + select.reset(); } } Timer::after_secs(5).await; } } -static mut KEY_CACHE: Queue = Queue::new(); - async fn key_handler() { loop { if let Some(event) = read_keyboard_fifo().await { diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index ad2b4a3..15093d8 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -22,7 +22,7 @@ type Device = ExclusiveDevice, Output<'static>, emb 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 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>> = From d986f1409f0ee80252c06b6b28197a0a5d1a71ee Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 28 Sep 2025 17:46:49 -0600 Subject: [PATCH 53/64] lower kernel version --- Cargo.lock | 2 +- kernel/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 905918f..dd498de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,7 +1303,7 @@ dependencies = [ [[package]] name = "kernel" -version = "0.1.0" +version = "0.0.1" dependencies = [ "abi_sys", "assign-resources", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index ab8d173..4c582d3 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kernel" -version = "0.1.0" +version = "0.0.1" edition = "2024" [[bin]] From f353cad108bdf22dde30f444634c0cceeef199cf Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 28 Sep 2025 22:24:29 -0600 Subject: [PATCH 54/64] WIP gallery app --- Cargo.lock | 18 ++++++++ Cargo.toml | 9 +++- abi/src/lib.rs | 2 +- abi_sys/src/lib.rs | 22 ++++++++-- justfile | 16 ++++--- kernel/src/abi.rs | 83 +++++++++++++++++++++++------------ kernel/src/elf.rs | 1 + user-apps/gallery/Cargo.toml | 9 ++++ user-apps/gallery/build.rs | 28 ++++++++++++ user-apps/gallery/src/main.rs | 56 +++++++++++++++++++++++ 10 files changed, 204 insertions(+), 40 deletions(-) create mode 100644 user-apps/gallery/Cargo.toml create mode 100644 user-apps/gallery/build.rs create mode 100644 user-apps/gallery/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index dd498de..d5cafc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1149,6 +1149,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "gallery" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "tinybmp", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2282,6 +2291,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinybmp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6" +dependencies = [ + "embedded-graphics", +] + [[package]] name = "trouble-host" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index da625f9..3080f3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,13 @@ [workspace] resolver = "3" -members = ["kernel", "abi", "shared", "user-apps/calculator", "user-apps/snake"] +members = [ + "kernel", + "abi", + "shared", + "user-apps/calculator", + "user-apps/snake", + "user-apps/gallery", +] [profile.release] debug = true diff --git a/abi/src/lib.rs b/abi/src/lib.rs index c45e543..e24f88c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use abi_sys::{RngRequest, draw_iter, gen_rand}; -pub use abi_sys::{get_key, lock_display, print, sleep}; +pub use abi_sys::{file_len, get_key, list_dir, lock_display, print, read_file, sleep}; use rand_core::RngCore; 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 c8cd23f..2384c0a 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -30,6 +30,7 @@ pub enum CallAbiTable { GenRand = 5, ListDir = 6, ReadFile = 7, + FileLen = 8, } pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); @@ -95,11 +96,15 @@ pub fn gen_rand(req: &mut RngRequest) { } } -pub type ListDir = - extern "C" fn(str: *const u8, len: usize, files: *mut Option, file_len: usize); +pub type ListDir = extern "C" fn( + str: *const u8, + len: usize, + files: *mut Option, + file_len: usize, +) -> usize; #[allow(unused)] -pub fn list_dir(path: &str, files: &mut [Option]) { +pub fn list_dir(path: &str, files: &mut [Option]) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); @@ -129,3 +134,14 @@ pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { ) } } + +pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; + +#[allow(unused)] +pub fn file_len(file: &str) -> usize { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize]; + let f: FileLen = core::mem::transmute(ptr); + f(file.as_ptr(), file.len()) + } +} diff --git a/justfile b/justfile index 40f9fad..527af57 100644 --- a/justfile +++ b/justfile @@ -1,8 +1,12 @@ -binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" - kernel: cargo run --bin kernel -calculator: - {{binary-args}} cargo build --bin calculator --profile release-binary -snake: - {{binary-args}} cargo build --bin snake --profile release-binary + +binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" + +userapp app: + {{binary-args}} cargo build --bin {{app}} --profile release-binary + +userapps: + just userapp calculator + just userapp snake + just userapp gallery diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 1504af9..d181bec 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,12 +1,12 @@ use abi_sys::{ - DrawIterAbi, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile, + DrawIterAbi, FileLen, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile, RngRequest, SleepAbi, }; -use alloc::vec::Vec; +use alloc::{format, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; -use embedded_sdmmc::{DirEntry, ShortFileName}; +use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName}; use heapless::spsc::Queue; use shared::keyboard::KeyEvent; @@ -80,25 +80,25 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) { } } -fn get_dir_entries(dir: &Dir, files: &mut [Option]) { - let mut files = files.iter_mut(); - +fn get_dir_entries(dir: &Dir, files: &mut [Option]) -> usize { + let mut i = 0; dir.iterate_dir(|entry| { - if let Some(f) = files.next() { - *f = Some(entry.clone()) + if i < files.len() { + files[i] = Some(entry.clone()); + i += 1; } }) - .unwrap() + .unwrap(); + i } -fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option]) { +fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option]) -> usize { if dirs.is_empty() { - get_dir_entries(dir, files); - return; + return get_dir_entries(dir, files); } let dir = dir.open_dir(dirs[0]).unwrap(); - recurse_dir(&dir, &dirs[1..], files); + recurse_dir(&dir, &dirs[1..], files) } const _: ListDir = list_dir; @@ -107,7 +107,7 @@ pub extern "C" fn list_dir( len: usize, files: *mut Option, files_len: usize, -) { +) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) }; // SAFETY: caller guarantees `ptr` is valid for `len` bytes @@ -116,23 +116,35 @@ pub extern "C" fn list_dir( let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let sd = guard.as_mut().unwrap(); + + let mut wrote = 0; sd.access_root_dir(|root| { - if !dir.is_empty() { - if dirs[0].is_empty() { - get_dir_entries(&root, files); + if dirs[0] == "" && dirs.len() >= 2 { + if dir == "/" { + wrote = get_dir_entries(&root, files); } else { - recurse_dir(&root, &dirs[1..], files); + wrote = recurse_dir(&root, &dirs[1..], files); } } }); + wrote } fn recurse_file(dir: &Dir, dirs: &[&str], mut access: impl FnMut(&mut File) -> T) -> T { if dirs.len() == 1 { - let file_name = ShortFileName::create_from_str(dirs[0]).unwrap(); - + let mut b = [0_u8; 50]; + let mut buf = LfnBuffer::new(&mut b); + let mut short_name = None; + dir.iterate_dir_lfn(&mut buf, |entry, name| { + if let Some(name) = name { + if name == dirs[0] { + short_name = Some(entry.name.clone()); + } + } + }) + .unwrap(); let mut file = dir - .open_file_in_dir(file_name, embedded_sdmmc::Mode::ReadWriteAppend) + .open_file_in_dir(short_name.unwrap(), embedded_sdmmc::Mode::ReadWriteAppend) .unwrap(); return access(&mut file); } @@ -160,15 +172,28 @@ pub extern "C" fn read_file( let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let sd = guard.as_mut().unwrap(); if !file.is_empty() { - if file[0].is_empty() { - } else { - sd.access_root_dir(|root| { - res = recurse_file(&root, &file[1..], |file| { - file.seek_from_start(start_from as u32).unwrap(); - file.read(&mut buf).unwrap() - }) + sd.access_root_dir(|root| { + res = recurse_file(&root, &file[1..], |file| { + file.seek_from_start(start_from as u32).unwrap(); + file.read(&mut buf).unwrap() }); - } + }); } res } + +const _: FileLen = file_len; +pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let file = unsafe { core::str::from_raw_parts(str, len) }; + let file: Vec<&str> = file.split('/').collect(); + + let mut res = 0; + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + if !file.is_empty() { + sd.access_root_dir(|root| res = recurse_file(&root, &file[1..], |file| file.length())); + } + res as usize +} diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 5e00074..488e11e 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -203,6 +203,7 @@ fn patch_abi( CallAbiTable::GenRand => abi::gen_rand as usize, CallAbiTable::ListDir => abi::list_dir as usize, CallAbiTable::ReadFile => abi::read_file as usize, + CallAbiTable::FileLen => abi::file_len as usize, }; unsafe { table_base.add(idx as usize).write(ptr); diff --git a/user-apps/gallery/Cargo.toml b/user-apps/gallery/Cargo.toml new file mode 100644 index 0000000..610805a --- /dev/null +++ b/user-apps/gallery/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gallery" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../../abi" } +embedded-graphics = "0.8.1" +tinybmp = "0.6.0" diff --git a/user-apps/gallery/build.rs b/user-apps/gallery/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/gallery/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/gallery/src/main.rs b/user-apps/gallery/src/main.rs new file mode 100644 index 0000000..4d06c76 --- /dev/null +++ b/user-apps/gallery/src/main.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use abi::{ + KeyCode, KeyState, Rng, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, + file_len, get_key, list_dir, lock_display, print, read_file, sleep, +}; +use alloc::{format, vec::Vec}; +use core::panic::PanicInfo; +use embedded_graphics::{Drawable, image::Image, prelude::*}; +use tinybmp::Bmp; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + print(&format!( + "user panic: {} @ {:?}", + info.message(), + info.location(), + )); + loop {} +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() { + main() +} + +pub fn main() { + print("Starting Gallery app"); + let mut display = Display; + + let file = "/images/ferriseyes_tiny.bmp"; + + let mut bmp_buf = [0_u8; 3_000]; + let read = read_file(file, 0, &mut bmp_buf); + let bmp = Bmp::from_slice(&bmp_buf[..read]).unwrap(); + + // ensure all draws show up at once + lock_display(true); + Image::new(&bmp, Point::new(10, 20)) + .draw(&mut display) + .unwrap(); + lock_display(false); + + loop { + let event = get_key(); + if event.state != KeyState::Idle { + match event.key { + KeyCode::Esc => return, + _ => (), + } + }; + } +} From 15a09b52d70a242ff10f83815b3bcf27e7706217 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 29 Sep 2025 12:35:54 -0600 Subject: [PATCH 55/64] gallery works --- assets/gallery/Sprite-0001.bmp | Bin 0 -> 12342 bytes assets/gallery/Sprite-0002.bmp | Bin 0 -> 12342 bytes assets/gallery/Sprite-0003.bmp | Bin 0 -> 12342 bytes assets/gallery/Sprite-0004.bmp | Bin 0 -> 12342 bytes assets/gallery/Sprite-0005.bmp | Bin 0 -> 12342 bytes kernel/src/abi.rs | 45 ++++++++++++++++++---------- user-apps/gallery/src/main.rs | 53 ++++++++++++++++++++++++++------- user-apps/memory.x | 2 +- 8 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 assets/gallery/Sprite-0001.bmp create mode 100644 assets/gallery/Sprite-0002.bmp create mode 100644 assets/gallery/Sprite-0003.bmp create mode 100644 assets/gallery/Sprite-0004.bmp create mode 100644 assets/gallery/Sprite-0005.bmp diff --git a/assets/gallery/Sprite-0001.bmp b/assets/gallery/Sprite-0001.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8e24bd6c2d3e4cb1152c01351038df2b1c636406 GIT binary patch literal 12342 zcmeH|K@Ng25JhqA&Xq@S>58>+?=?J^*YY+_teG@G$N)2x;v=*f+UC!nM=AG5du_f~ zEB!^}gl9A%us(sRzm%$vsjAJ(My-11?7;s%N?8BIBO?SL zU?EWN37`J0%l&)lSZL>Kyz+YKk literal 0 HcmV?d00001 diff --git a/assets/gallery/Sprite-0002.bmp b/assets/gallery/Sprite-0002.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b2678b5664794b142257b250523dbd2c3e8399e3 GIT binary patch literal 12342 zcmeHFF>+Hu6bm&S6-S`MRceNo;SAKI;T{}{8*mE_f|7?7jo$8hZNJBiXPo(V^t5`C z_9vgeJ{}%kejf4t0iRF*K5ri01K|hf@7~5Ih~xL4-{0^Yr`O-X#}V;R`MPsH3g^Y% zW+G)_Xe0c|D_G=i9;q9##hqnkyXqM}!6?p}okVUCaElB8$sG`jp;QyY5Rg$@T~~gA zxk6xK@WybAmHbUXfGjX(s0SG`I~|T|!{JJhwzy6-u{z`k6Azt9?ud6HvS!9;;!G2~ zDh)$mOpN3&a9*$EQzqu4lgzNZk|!M~&@JHLKYssW5A^{KKYk7$lbtmVn`}DhE>=)p z7kL+wC{Ii#Us>*QWHjX=lZKiM7t!S#fPjpBp;^5ckEEJF_!Ec|@xrYZJ7_m7B9R zL3u>0A!`$~#+93EZN=q5O@`W8aZ_BaEedOA#kE0AZdTaFrqDuLD{Nz7P?MDv#%N4a zZk26ro0+Pt4xii>OeQ;`OFV~Fhh#}yxyi69$RS^ze!bzW=gBJ^em&`PeUr-^X8qx| zz5>cKkryFz0icOGpY&Pu$L{_Wp2aVJk&}rt0oMs;O6Ax6pQXlX-{s+t^3L;mTHJn~&0DVwJeZQ@xU zm8k|ieF#)sf``tmkt6aEnIYgU;xrpx$>dN?oSEB}$!4cbSFpmfbStS_x-yexzJt+M uv~I<8lj&%fE3E*b<{veT%p=?8qZ?v2FMC5bx>vowf%_Y{zkyqS1AhUl#a!6{ literal 0 HcmV?d00001 diff --git a/assets/gallery/Sprite-0003.bmp b/assets/gallery/Sprite-0003.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ee8e1ea3eec87aa07cb2c2e8b0114c663cc9a4e3 GIT binary patch literal 12342 zcmeH}F>b>!3`NtqTc;kOW2ekT_ul_FE@KuSf+bSll43(H4OpPbPx6tlgM9zoKX2=G zm-9pJufO}{_AZuO5`TNuTOD!624VxTf!IK7;Bp&SmSyUhgn#N0X6KieXX^bc{{=>v z{VSfD{{mmZkP%M*HS7G4JJW}}!#xaSuEd^o+sO^feOo1&YCF7+}$?4R;?_aT1J_SzXi*XkS*0A{U0pNGeiR z?5j>~>IfP_7^GU9=ik&!9~%gFrKG(!z?(;_tX4>)ny7xVNSfYbqbc84m8l-K7_fV& z8glv8IU#akGyn`qQGlE%z#ucigBeq(0spfNX*aw@^DGT8 y4Jo_k-OSSP=(N*nj^Ol=Kkw;y0T3IA4a5dw1F?bFKx`m35F3aM#0LJ=27Un;ak{zy literal 0 HcmV?d00001 diff --git a/assets/gallery/Sprite-0004.bmp b/assets/gallery/Sprite-0004.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1988ce5e642074e86efaa4f6695f48ef574c9ffc GIT binary patch literal 12342 zcmeHKJ5B>Z3=K6M6-S_?N{#3_11IJdoPah3SQtHx{2Nauo81jwWk;UpXZzW++9a=U z$LGWEb)@x)?w7y&?(j$!UF3hb=bM3AYXUWan!wr<`22VmpS2au))(#2b~fYM@i*3y zZf6N(?L({jHG!HyP2gWlAbx@GeFSlL>PdCLA2hz*cUvAy|i39YXUWa zn!wT%Fu#VDE*j_mwyrp&_3DmV@2G8Q>0_b_2M92^)$K6S4Ts7uGee+k8CsHKHYH;7 z6*J?_XA?5c-ey!$@;nW{s6!0aM@}v!!)80fAhB7gYp}w>mI`ASVnR1s55yH$&iT zlt-izTQimBa5k&~fn0onY#PKdaSe8}iBw%al@K9ZlmktdsY-($5=hdnjDQTs6GQlU z;VhL`P7Z`$n5`?Qezt(NXq-RxLN)}G{y0QLNCuAd(dtopo$^qc>r yCP6L9L%0j!9nvpiFS7O8PbObozo!#@c?95_Z2<~nBdH$)xv~+9Cok#GwD1E}{AKX~ literal 0 HcmV?d00001 diff --git a/assets/gallery/Sprite-0005.bmp b/assets/gallery/Sprite-0005.bmp new file mode 100644 index 0000000000000000000000000000000000000000..172ddc8988df96902f94986575f2460ff138be4c GIT binary patch literal 12342 zcmeH|F-`)O1{J`NHVS%1k_FCh_bdud*x0 zalT)Ef_Qj5>~}w(1AcGtz5o4Q?5+{vgY~OR`AQ;x(}8p#9Y_b#fpp+h9r*ls>pFU$ z8}R*FbQKrut}VKX7yP$(122Lx11#xSDkU?{Qzw$2P1gj)iO!B+-wWbke&ZzordZ{a z0WXf3VE)3rF*6Bkfsk*@Ut=5R$vIw<%qITVU>4+)f7Uc@5#Iv?ZfE9CfflD!+rVx? z+GR~`@nxFDuC6V*ipxQCid64 zKf3c=I%aZNV4Jf{v)F|eVjc5(Dlt!_r#i;oVk&Ae{L)?XGhP{l@vr2pZI9%id;m2t zXFif2NH~HM%vl!9@1*jVCI75fu;}&qrPGM}5F%1UvaXcEKyY zA5XofCjIxgBsI4G!|?8xVrBYID`o@R|Jm)61*hZx{_AOy&`xg74{Qz_b#nC-_$R^a zB>&jKe>@&H{QtG=+3<8{!B4k|%~Nm1&o@sJvUvh-2S4Hs-;amZr~yA#(dir: &Dir, dirs: &[&str], mut access: impl FnMut(&mut File) -> T) -> T { +fn recurse_file( + dir: &Dir, + dirs: &[&str], + mut access: impl FnMut(&mut File) -> T, +) -> Result { if dirs.len() == 1 { let mut b = [0_u8; 50]; let mut buf = LfnBuffer::new(&mut b); let mut short_name = None; dir.iterate_dir_lfn(&mut buf, |entry, name| { if let Some(name) = name { - if name == dirs[0] { + if name == dirs[0] || entry.name.to_string().as_str() == dirs[0] { short_name = Some(entry.name.clone()); } } }) .unwrap(); - let mut file = dir - .open_file_in_dir(short_name.unwrap(), embedded_sdmmc::Mode::ReadWriteAppend) - .unwrap(); - return access(&mut file); + if let Some(name) = short_name { + let mut file = dir + .open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend) + .map_err(|_| ())?; + return Ok(access(&mut file)); + } + return Err(()); } let dir = dir.open_dir(dirs[0]).unwrap(); @@ -161,25 +168,27 @@ pub extern "C" fn read_file( buf: *mut u8, buf_len: usize, ) -> usize { - // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; // SAFETY: caller guarantees `ptr` is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; let file: Vec<&str> = file.split('/').collect(); + // SAFETY: caller guarantees `ptr` is valid for `len` bytes + let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; - let mut res = 0; + let mut read = 0; let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let sd = guard.as_mut().unwrap(); if !file.is_empty() { sd.access_root_dir(|root| { - res = recurse_file(&root, &file[1..], |file| { + if let Ok(result) = recurse_file(&root, &file[1..], |file| { file.seek_from_start(start_from as u32).unwrap(); file.read(&mut buf).unwrap() - }); + }) { + read = result + }; }); } - res + read } const _: FileLen = file_len; @@ -188,12 +197,16 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { let file = unsafe { core::str::from_raw_parts(str, len) }; let file: Vec<&str> = file.split('/').collect(); - let mut res = 0; + let mut len = 0; let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let sd = guard.as_mut().unwrap(); if !file.is_empty() { - sd.access_root_dir(|root| res = recurse_file(&root, &file[1..], |file| file.length())); + sd.access_root_dir(|root| { + if let Ok(result) = recurse_file(&root, &file[1..], |file| file.length()) { + len = result + } + }); } - res as usize + len as usize } diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index 4d06c76..7422b22 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![allow(static_mut_refs)] extern crate alloc; use abi::{ @@ -8,7 +9,7 @@ use abi::{ file_len, get_key, list_dir, lock_display, print, read_file, sleep, }; use alloc::{format, vec::Vec}; -use core::panic::PanicInfo; +use core::{cell::RefCell, panic::PanicInfo}; use embedded_graphics::{Drawable, image::Image, prelude::*}; use tinybmp::Bmp; @@ -29,20 +30,50 @@ pub extern "Rust" fn _start() { pub fn main() { print("Starting Gallery app"); + static mut BMP_BUF: [u8; 100_000] = [0_u8; 100_000]; let mut display = Display; - let file = "/images/ferriseyes_tiny.bmp"; + // Grid parameters + let grid_cols = 3; + let grid_rows = 3; + let cell_width = 64; + let cell_height = 64; - let mut bmp_buf = [0_u8; 3_000]; - let read = read_file(file, 0, &mut bmp_buf); - let bmp = Bmp::from_slice(&bmp_buf[..read]).unwrap(); + let mut images_drawn = 0; - // ensure all draws show up at once - lock_display(true); - Image::new(&bmp, Point::new(10, 20)) - .draw(&mut display) - .unwrap(); - lock_display(false); + let mut files = [const { None }; 18]; + let files_num = list_dir("/images", &mut files); + + for file in &files[2..files_num] { + if images_drawn >= grid_cols * grid_rows { + break; // only draw 3x3 + } + + if let Some(f) = file { + print(&format!("file: {}", f.name)); + if f.name.extension() == b"bmp" || f.name.extension() == b"BMP" { + let file = format!("/images/{}", f.name); + + let read = read_file(&file, 0, &mut unsafe { &mut BMP_BUF[..] }); + if read > 0 { + let bmp = Bmp::from_slice(unsafe { &BMP_BUF }).expect("failed to parse bmp"); + + let row = images_drawn / grid_cols; + let col = images_drawn % grid_cols; + let x = (col * cell_width) as i32 + 10; // 10px margin + let y = (row * cell_height) as i32 + 10; + + lock_display(true); + Image::new(&bmp, Point::new(x, y)) + .draw(&mut display) + .unwrap(); + lock_display(false); + + images_drawn += 1; + } + } + } + } loop { let event = get_key(); diff --git a/user-apps/memory.x b/user-apps/memory.x index 36c9663..5517509 100644 --- a/user-apps/memory.x +++ b/user-apps/memory.x @@ -1,6 +1,6 @@ MEMORY { - RAM : ORIGIN = 0x0, LENGTH = 100K + RAM : ORIGIN = 0x0, LENGTH = 150K } SECTIONS From 59e4a7faa2d4cc300cb7afb0b5c35f403a923f25 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 29 Sep 2025 18:33:37 -0600 Subject: [PATCH 56/64] file names in gallery --- user-apps/gallery/src/main.rs | 38 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index 7422b22..a896070 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -4,13 +4,16 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, Rng, + KeyCode, KeyState, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - file_len, get_key, list_dir, lock_display, print, read_file, sleep, + get_key, list_dir, lock_display, print, read_file, +}; +use alloc::{format, string::ToString}; +use core::panic::PanicInfo; +use embedded_graphics::{ + Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10, + pixelcolor::Rgb565, prelude::*, text::Text, }; -use alloc::{format, vec::Vec}; -use core::{cell::RefCell, panic::PanicInfo}; -use embedded_graphics::{Drawable, image::Image, prelude::*}; use tinybmp::Bmp; #[panic_handler] @@ -36,8 +39,8 @@ pub fn main() { // Grid parameters let grid_cols = 3; let grid_rows = 3; - let cell_width = 64; - let cell_height = 64; + let cell_width = SCREEN_WIDTH as i32 / grid_cols; + let cell_height = SCREEN_HEIGHT as i32 / grid_rows; let mut images_drawn = 0; @@ -60,13 +63,30 @@ pub fn main() { let row = images_drawn / grid_cols; let col = images_drawn % grid_cols; - let x = (col * cell_width) as i32 + 10; // 10px margin - let y = (row * cell_height) as i32 + 10; + let cell_x = col * cell_width; + let cell_y = row * cell_height; + + // Center image inside cell + let bmp_w = bmp.size().width as i32; + let bmp_h = bmp.size().height as i32; + let x = cell_x + (cell_width - bmp_w) / 2; + let y = cell_y + 5; // 5px top margin lock_display(true); Image::new(&bmp, Point::new(x, y)) .draw(&mut display) .unwrap(); + + let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + let text_y = y + bmp_h + 2; // 2px gap under image + Text::new( + f.name.to_string().as_str(), + Point::new(cell_x + 2, text_y), + text_style, + ) + .draw(&mut display) + .unwrap(); + lock_display(false); images_drawn += 1; From 2021ef97539b9679cc3bff0021c0e53013849f16 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 30 Sep 2025 12:11:57 -0600 Subject: [PATCH 57/64] update readme and just for release builds --- .gitignore | 1 + README.md | 11 ++++++++--- justfile | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..84faace 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.uf2 diff --git a/README.md b/README.md index ad9c6b6..d2a8c2f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ A simple operating system for the **Clockwork PicoCalc**, written in Rust. This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware. +## Status + +Basic synchronous applications are working great. +Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI driver** to make application development easier and smoother. + ## Project Structure - **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) @@ -23,6 +28,6 @@ This project provides a minimal kernel, ABI, and user-space applications to expe ```bash git clone https://github.com/LegitCamper/picocalc-os-rs.git cd picocalc-os-rs -just calculator -# copy the calculator to the sdcard and rename it to calculator.bin -just kernel +just userapps +# copy the build applications from target/thumbv8m.main-none-eabihf/release-binary/application to the sdcard and rename them to app.bin +just kernel-release # keep in mind that https://github.com/StripedMonkey/elf2uf2-rs version is required until https://github.com/JoNil/elf2uf2-rs/pull/41 is merged diff --git a/justfile b/justfile index 527af57..d087935 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,8 @@ -kernel: +kernel-dev: cargo run --bin kernel +kernel-release: + cargo build --bin kernel --release + elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" From f73df7e0b89142c370042b059a93f1c5afacc24f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 30 Sep 2025 12:14:51 -0600 Subject: [PATCH 58/64] fix abi in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2a8c2f..657623e 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI ## Project Structure - **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) -- **`abi/`** – Shared application binary interface definitions for kernel ↔ userspace interaction -- **`abi_sys/`** – System-level ABI helpers +- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace interaction +- **`abi/`** – ABI helpers and abstractions for easier development - **`shared/`** – Shared utilities and common code across kernel and user applications - **`user-apps/`** – Collection of userspace programs (calculator, snake, etc.) From f2a58012840e3a923bdc349099f4d770caa37913 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 15:43:57 -0600 Subject: [PATCH 59/64] fully c ffi compat --- abi/src/lib.rs | 6 +++- abi_sys/src/lib.rs | 5 +-- kernel/src/abi.rs | 7 ++-- shared/src/lib.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index e24f88c..271c445 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use abi_sys::{RngRequest, draw_iter, gen_rand}; -pub use abi_sys::{file_len, get_key, list_dir, lock_display, print, read_file, sleep}; +pub use abi_sys::{file_len, list_dir, lock_display, print, read_file, sleep}; use rand_core::RngCore; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; @@ -13,6 +13,10 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +pub fn get_key() -> KeyEvent { + abi_sys::get_key().into() +} + pub mod display { use crate::draw_iter; use embedded_graphics::{ diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2384c0a..fb6c084 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -9,6 +9,7 @@ use embedded_graphics::{ pixelcolor::{Rgb565, RgbColor}, }; use embedded_sdmmc::DirEntry; +use shared::keyboard::KeyEventC; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; @@ -69,10 +70,10 @@ pub fn draw_iter(pixels: &[Pixel]) { f(pixels.as_ptr(), pixels.len()); } -pub type GetKeyAbi = extern "C" fn() -> KeyEvent; +pub type GetKeyAbi = extern "C" fn() -> KeyEventC; #[allow(unused)] -pub fn get_key() -> KeyEvent { +pub fn get_key() -> KeyEventC { let f: GetKeyAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; f() diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 3c31cf2..820af43 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -8,7 +8,7 @@ use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName}; use heapless::spsc::Queue; -use shared::keyboard::KeyEvent; +use shared::keyboard::{KeyEvent, KeyEventC}; use crate::{ display::{FB_PAUSED, FRAMEBUFFER}, @@ -53,15 +53,16 @@ pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { pub static mut KEY_CACHE: Queue = Queue::new(); const _: GetKeyAbi = get_key; -pub extern "C" fn get_key() -> KeyEvent { +pub extern "C" fn get_key() -> KeyEventC { if let Some(event) = unsafe { KEY_CACHE.dequeue() } { - event + event.into() } else { KeyEvent { key: abi_sys::KeyCode::Unknown(0), state: abi_sys::KeyState::Idle, mods: Modifiers::empty(), } + .into() } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index b78e205..874213e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -3,6 +3,7 @@ pub mod keyboard { bitflags::bitflags! { #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + #[repr(C)] pub struct Modifiers: u8 { const NONE = 0; const CTRL = 1; @@ -13,17 +14,43 @@ pub mod keyboard { } } - #[derive(Debug)] #[repr(C)] + pub struct KeyEventC { + pub key: u8, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEventC { + fn into(self) -> KeyEvent { + KeyEvent { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[derive(Debug)] pub struct KeyEvent { pub key: KeyCode, pub state: KeyState, pub mods: Modifiers, } + impl Into for KeyEvent { + fn into(self) -> KeyEventC { + KeyEventC { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(u8)] + #[repr(C)] pub enum KeyState { Idle = 0, Pressed = 1, @@ -44,7 +71,6 @@ pub mod keyboard { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(C)] #[repr(u8)] pub enum KeyCode { JoyUp = 0x01, @@ -91,6 +117,55 @@ pub mod keyboard { Unknown(u8), } + impl Into for KeyCode { + fn into(self) -> u8 { + match self { + KeyCode::JoyUp => 0x01, + KeyCode::JoyDown => 0x02, + KeyCode::JoyLeft => 0x03, + KeyCode::JoyRight => 0x04, + KeyCode::JoyCenter => 0x05, + KeyCode::BtnLeft1 => 0x06, + KeyCode::BtnRight1 => 0x07, + KeyCode::BtnLeft2 => 0x11, + KeyCode::BtnRight2 => 0x12, + KeyCode::Backspace => 0x08, + KeyCode::Tab => 0x09, + KeyCode::Enter => 0x0A, + KeyCode::ModAlt => 0xA1, + KeyCode::ModShiftLeft => 0xA2, + KeyCode::ModShiftRight => 0xA3, + KeyCode::ModSym => 0xA4, + KeyCode::ModCtrl => 0xA5, + KeyCode::Esc => 0xB1, + KeyCode::Left => 0xB4, + KeyCode::Up => 0xB5, + KeyCode::Down => 0xB6, + KeyCode::Right => 0xB7, + KeyCode::Break => 0xD0, + KeyCode::Insert => 0xD1, + KeyCode::Home => 0xD2, + KeyCode::Del => 0xD4, + KeyCode::End => 0xD5, + KeyCode::PageUp => 0xD6, + KeyCode::PageDown => 0xD7, + KeyCode::CapsLock => 0xC1, + KeyCode::F1 => 0x81, + KeyCode::F2 => 0x82, + KeyCode::F3 => 0x83, + KeyCode::F4 => 0x84, + KeyCode::F5 => 0x85, + KeyCode::F6 => 0x86, + KeyCode::F7 => 0x87, + KeyCode::F8 => 0x88, + KeyCode::F9 => 0x89, + KeyCode::F10 => 0x90, + KeyCode::Char(char) => char as u8, + KeyCode::Unknown(i) => i, + } + } + } + impl From for KeyCode { fn from(value: u8) -> Self { match value { From af238b2847f36232d8a494e12881eb19c794af26 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 17:14:38 -0600 Subject: [PATCH 60/64] cbindgen abi_sys --- .gitignore | 1 + Cargo.lock | 249 ++++++++++++++++++++++- abi/Cargo.toml | 1 + abi/src/lib.rs | 50 ++++- abi_sys/Cargo.toml | 10 +- abi_sys/src/lib.rs | 316 ++++++++++++++++++++++++----- justfile | 5 +- kernel/Cargo.toml | 1 - kernel/src/abi.rs | 11 +- kernel/src/elf.rs | 3 +- kernel/src/peripherals/keyboard.rs | 2 +- kernel/src/ui.rs | 2 +- shared/Cargo.toml | 3 +- shared/src/lib.rs | 218 -------------------- user-apps/calculator/src/main.rs | 7 +- user-apps/gallery/src/main.rs | 8 +- user-apps/snake/src/main.rs | 8 +- 17 files changed, 590 insertions(+), 305 deletions(-) diff --git a/.gitignore b/.gitignore index 84faace..f73c1bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target *.uf2 +abi_sys.h diff --git a/Cargo.lock b/Cargo.lock index d5cafc5..21df7c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ version = "0.1.0" dependencies = [ "abi_sys", "embedded-graphics", + "embedded-sdmmc", "rand_core 0.9.3", "shared", "spin", @@ -28,10 +29,11 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ + "bitflags 2.9.4", + "cbindgen", "defmt 0.3.100", "embedded-graphics", "embedded-sdmmc", - "shared", "strum", ] @@ -95,6 +97,17 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -239,12 +252,55 @@ dependencies = [ "embedded-layout", ] +[[package]] +name = "cbindgen" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d" +dependencies = [ + "clap", + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -380,7 +436,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.106", ] @@ -1015,6 +1071,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fixed" version = "1.29.0" @@ -1176,7 +1248,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -1208,6 +1292,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1234,12 +1324,27 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1252,6 +1357,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.11.4" @@ -1268,7 +1383,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", "windows-sys 0.59.0", ] @@ -1291,6 +1406,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.80" @@ -1454,6 +1575,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litrs" version = "0.4.2" @@ -1571,6 +1698,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1617,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap", + "indexmap 2.11.4", ] [[package]] @@ -1627,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap", + "indexmap 2.11.4", ] [[package]] @@ -1830,6 +1963,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -1872,7 +2011,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -1949,12 +2088,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.0", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1998,6 +2156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -2020,6 +2179,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -2040,8 +2212,9 @@ dependencies = [ name = "shared" version = "0.1.0" dependencies = [ + "abi_sys", "bitflags 2.9.4", - "defmt 1.0.1", + "defmt 0.3.100", ] [[package]] @@ -2149,6 +2322,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -2170,7 +2349,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", @@ -2213,6 +2392,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.0", +] + [[package]] name = "term" version = "0.7.0" @@ -2242,6 +2434,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "1.0.69" @@ -2300,6 +2498,15 @@ dependencies = [ "embedded-graphics", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "trouble-host" version = "0.1.0" @@ -2457,6 +2664,24 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.103" @@ -2635,6 +2860,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wyz" version = "0.5.1" diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 3d46683..bb7337f 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +embedded-sdmmc = { version = "0.9.0", default-features = false } 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 271c445..e8daf7c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,9 +1,8 @@ #![no_std] -use abi_sys::{RngRequest, draw_iter, gen_rand}; -pub use abi_sys::{file_len, list_dir, lock_display, print, read_file, sleep}; +pub use abi_sys::keyboard; +use abi_sys::{RngRequest, keyboard::KeyEvent}; use rand_core::RngCore; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; static mut ARENA: [u8; 10000] = [0; 10000]; @@ -13,12 +12,19 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +pub fn print(msg: &str) { + abi_sys::print(msg.as_ptr(), msg.len()); +} + +pub fn sleep(ms: u64) { + abi_sys::sleep(ms); +} + pub fn get_key() -> KeyEvent { - abi_sys::get_key().into() + abi_sys::keyboard::get_key().into() } pub mod display { - use crate::draw_iter; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, @@ -32,6 +38,14 @@ pub mod display { pub type Pixel565 = Pixel; + pub fn lock_display(lock: bool) { + abi_sys::lock_display(lock); + } + + fn draw_iter(pixels: &[Pixel]) { + abi_sys::draw_iter(pixels.as_ptr(), pixels.len()) + } + pub struct Display; impl Dimensions for Display { @@ -77,6 +91,10 @@ pub mod display { } } +fn gen_rand(req: &mut RngRequest) { + abi_sys::gen_rand(req); +} + pub struct Rng; impl RngCore for Rng { @@ -105,3 +123,25 @@ impl RngCore for Rng { gen_rand(&mut req); } } + +pub mod fs { + use embedded_sdmmc::DirEntry; + + pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { + abi_sys::read_file( + file.as_ptr(), + file.len(), + read_from, + buf.as_mut_ptr(), + buf.len(), + ) + } + + pub fn list_dir(path: &str, files: &mut [Option]) -> usize { + abi_sys::list_dir(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + } + + pub fn file_len(str: &str) -> usize { + abi_sys::file_len(str.as_ptr(), str.len()) + } +} diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index ee7bad7..a64b5e5 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2024" [features] +default = [] defmt = ["dep:defmt"] [dependencies] -embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } -defmt = { version = "0.3", optional = true } -shared = { path = "../shared" } +bitflags = "2.9.4" +embedded-graphics = "0.8.1" embedded-sdmmc = { version = "0.9.0", default-features = false } +defmt = { version = "0.3", optional = true } + +[build-dependencies] +cbindgen = "0.24.0" diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index fb6c084..89c0427 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,27 +1,11 @@ #![no_std] -extern crate alloc; - -#[allow(unused)] -use embedded_graphics::{ - Pixel, - geometry::Point, - pixelcolor::{Rgb565, RgbColor}, -}; +use embedded_graphics::{Pixel, pixelcolor::Rgb565}; use embedded_sdmmc::DirEntry; -use shared::keyboard::KeyEventC; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; -pub type EntryFn = fn(); - -#[unsafe(no_mangle)] -#[unsafe(link_section = ".syscall_table")] -pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; - -#[repr(usize)] #[derive(Clone, Copy, EnumIter, EnumCount)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] pub enum CallAbiTable { PrintString = 0, SleepMs = 1, @@ -33,20 +17,25 @@ pub enum CallAbiTable { ReadFile = 7, FileLen = 8, } +pub type EntryFn = fn(); + +#[unsafe(no_mangle)] +#[unsafe(link_section = ".syscall_table")] +pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); -#[allow(unused)] -pub fn print(msg: &str) { +#[unsafe(no_mangle)] +pub extern "C" fn print(ptr: *const u8, len: usize) { let f: PrintAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::PrintString as usize]) }; - f(msg.as_ptr(), msg.len()); + f(ptr, len); } pub type SleepAbi = extern "C" fn(ms: u64); -#[allow(unused)] -pub fn sleep(ms: u64) { +#[unsafe(no_mangle)] +pub extern "C" fn sleep(ms: u64) { let f: SleepAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; f(ms); @@ -54,8 +43,8 @@ pub fn sleep(ms: u64) { pub type LockDisplay = extern "C" fn(lock: bool); -#[allow(unused)] -pub fn lock_display(lock: bool) { +#[unsafe(no_mangle)] +pub extern "C" fn lock_display(lock: bool) { let f: LockDisplay = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]) }; f(lock); @@ -63,20 +52,240 @@ pub fn lock_display(lock: bool) { pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); -#[allow(unused)] -pub fn draw_iter(pixels: &[Pixel]) { +#[unsafe(no_mangle)] +pub extern "C" fn draw_iter(ptr: *const Pixel, len: usize) { let f: DrawIterAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; - f(pixels.as_ptr(), pixels.len()); + f(ptr, len); } -pub type GetKeyAbi = extern "C" fn() -> KeyEventC; +pub mod keyboard { + use crate::{CALL_ABI_TABLE, CallAbiTable}; -#[allow(unused)] -pub fn get_key() -> KeyEventC { - let f: GetKeyAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; - f() + bitflags::bitflags! { + #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + #[repr(C)] + pub struct Modifiers: u8 { + const NONE = 0; + const CTRL = 1; + const ALT = 2; + const LSHIFT = 4; + const RSHIFT = 8; + const SYM = 16; + } + } + + #[repr(C)] + pub struct KeyEventC { + pub key: u8, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEventC { + fn into(self) -> KeyEvent { + KeyEvent { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[derive(Debug)] + pub struct KeyEvent { + pub key: KeyCode, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEvent { + fn into(self) -> KeyEventC { + KeyEventC { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(C)] + 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 Into for KeyCode { + fn into(self) -> u8 { + match self { + KeyCode::JoyUp => 0x01, + KeyCode::JoyDown => 0x02, + KeyCode::JoyLeft => 0x03, + KeyCode::JoyRight => 0x04, + KeyCode::JoyCenter => 0x05, + KeyCode::BtnLeft1 => 0x06, + KeyCode::BtnRight1 => 0x07, + KeyCode::BtnLeft2 => 0x11, + KeyCode::BtnRight2 => 0x12, + KeyCode::Backspace => 0x08, + KeyCode::Tab => 0x09, + KeyCode::Enter => 0x0A, + KeyCode::ModAlt => 0xA1, + KeyCode::ModShiftLeft => 0xA2, + KeyCode::ModShiftRight => 0xA3, + KeyCode::ModSym => 0xA4, + KeyCode::ModCtrl => 0xA5, + KeyCode::Esc => 0xB1, + KeyCode::Left => 0xB4, + KeyCode::Up => 0xB5, + KeyCode::Down => 0xB6, + KeyCode::Right => 0xB7, + KeyCode::Break => 0xD0, + KeyCode::Insert => 0xD1, + KeyCode::Home => 0xD2, + KeyCode::Del => 0xD4, + KeyCode::End => 0xD5, + KeyCode::PageUp => 0xD6, + KeyCode::PageDown => 0xD7, + KeyCode::CapsLock => 0xC1, + KeyCode::F1 => 0x81, + KeyCode::F2 => 0x82, + KeyCode::F3 => 0x83, + KeyCode::F4 => 0x84, + KeyCode::F5 => 0x85, + KeyCode::F6 => 0x86, + KeyCode::F7 => 0x87, + KeyCode::F8 => 0x88, + KeyCode::F9 => 0x89, + KeyCode::F10 => 0x90, + KeyCode::Char(char) => char as u8, + KeyCode::Unknown(i) => i, + } + } + } + + 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), + }, + } + } + } + + pub type GetKeyAbi = extern "C" fn() -> KeyEventC; + + #[unsafe(no_mangle)] + pub extern "C" fn get_key() -> KeyEventC { + let f: GetKeyAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; + f() + } } #[repr(C)] @@ -88,8 +297,8 @@ pub enum RngRequest { pub type GenRand = extern "C" fn(req: &mut RngRequest); -#[allow(unused)] -pub fn gen_rand(req: &mut RngRequest) { +#[unsafe(no_mangle)] +pub extern "C" fn gen_rand(req: &mut RngRequest) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize]; let f: GenRand = core::mem::transmute(ptr); @@ -104,12 +313,17 @@ pub type ListDir = extern "C" fn( file_len: usize, ) -> usize; -#[allow(unused)] -pub fn list_dir(path: &str, files: &mut [Option]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn list_dir( + str: *const u8, + len: usize, + files: *mut Option, + file_len: usize, +) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); - f(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + f(str, len, files, file_len) } } @@ -121,28 +335,28 @@ pub type ReadFile = extern "C" fn( buf_len: usize, ) -> usize; -#[allow(unused)] -pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn read_file( + str: *const u8, + len: usize, + read_from: usize, + buf: *mut u8, + buf_len: usize, +) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::ReadFile as usize]; let f: ReadFile = core::mem::transmute(ptr); - f( - file.as_ptr(), - file.len(), - read_from, - buf.as_mut_ptr(), - buf.len(), - ) + f(str, len, read_from, buf, buf_len) } } pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; -#[allow(unused)] -pub fn file_len(file: &str) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize]; let f: FileLen = core::mem::transmute(ptr); - f(file.as_ptr(), file.len()) + f(str, len) } } diff --git a/justfile b/justfile index d087935..0f1a419 100644 --- a/justfile +++ b/justfile @@ -6,10 +6,13 @@ kernel-release: binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" +cbindgen: + cbindgen abi_sys --output abi_sys.h -q + userapp app: {{binary-args}} cargo build --bin {{app}} --profile release-binary -userapps: +userapps: cbindgen just userapp calculator just userapp snake just userapp gallery diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 4c582d3..13d4eef 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -18,7 +18,6 @@ trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", "shared/defmt", - "abi_sys/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 820af43..e61bc24 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,14 +1,13 @@ use abi_sys::{ - DrawIterAbi, FileLen, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile, - RngRequest, SleepAbi, + DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, SleepAbi, + keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; -use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName}; +use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; -use shared::keyboard::{KeyEvent, KeyEventC}; use crate::{ display::{FB_PAUSED, FRAMEBUFFER}, @@ -58,8 +57,8 @@ pub extern "C" fn get_key() -> KeyEventC { event.into() } else { KeyEvent { - key: abi_sys::KeyCode::Unknown(0), - state: abi_sys::KeyState::Idle, + key: KeyCode::Unknown(0), + state: KeyState::Idle, mods: Modifiers::empty(), } .into() diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 488e11e..0bc6591 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -2,7 +2,8 @@ use crate::{ abi, storage::{File, SDCARD}, }; -use abi_sys::{CallAbiTable, EntryFn}; +use abi_sys::CallAbiTable; +use abi_sys::EntryFn; use alloc::{vec, vec::Vec}; use bumpalo::Bump; use core::ptr; diff --git a/kernel/src/peripherals/keyboard.rs b/kernel/src/peripherals/keyboard.rs index a9ef605..28b65e7 100644 --- a/kernel/src/peripherals/keyboard.rs +++ b/kernel/src/peripherals/keyboard.rs @@ -1,5 +1,5 @@ use crate::peripherals::PERIPHERAL_BUS; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +pub use abi_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; const REG_ID_KEY: u8 = 0x04; const REG_ID_FIF: u8 = 0x09; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index f4b9d85..1c91c40 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -5,6 +5,7 @@ use crate::{ peripherals::keyboard, storage::FileName, }; +use abi_sys::keyboard::{KeyCode, KeyState}; use alloc::{str::FromStr, string::String, vec::Vec}; use core::sync::atomic::Ordering; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; @@ -22,7 +23,6 @@ use embedded_layout::{ prelude::*, }; use embedded_text::TextBox; -use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = Mutex::new(SelectionList::new()); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 89519f1..8796b8a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -8,5 +8,6 @@ default = [] defmt = ["dep:defmt"] [dependencies] +abi_sys = { path = "../abi_sys" } bitflags = "2.9.4" -defmt = { version = "1.0.1", optional = true } +defmt = { version = "0.3", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 874213e..0c9ac1a 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -1,219 +1 @@ #![no_std] - -pub mod keyboard { - bitflags::bitflags! { - #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] - #[repr(C)] - pub struct Modifiers: u8 { - const NONE = 0; - const CTRL = 1; - const ALT = 2; - const LSHIFT = 4; - const RSHIFT = 8; - const SYM = 16; - } - } - - #[repr(C)] - pub struct KeyEventC { - pub key: u8, - pub state: KeyState, - pub mods: Modifiers, - } - - impl Into for KeyEventC { - fn into(self) -> KeyEvent { - KeyEvent { - key: self.key.into(), - state: self.state, - mods: self.mods, - } - } - } - - #[derive(Debug)] - pub struct KeyEvent { - pub key: KeyCode, - pub state: KeyState, - pub mods: Modifiers, - } - - impl Into for KeyEvent { - fn into(self) -> KeyEventC { - KeyEventC { - key: self.key.into(), - state: self.state, - mods: self.mods, - } - } - } - - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(C)] - 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 Into for KeyCode { - fn into(self) -> u8 { - match self { - KeyCode::JoyUp => 0x01, - KeyCode::JoyDown => 0x02, - KeyCode::JoyLeft => 0x03, - KeyCode::JoyRight => 0x04, - KeyCode::JoyCenter => 0x05, - KeyCode::BtnLeft1 => 0x06, - KeyCode::BtnRight1 => 0x07, - KeyCode::BtnLeft2 => 0x11, - KeyCode::BtnRight2 => 0x12, - KeyCode::Backspace => 0x08, - KeyCode::Tab => 0x09, - KeyCode::Enter => 0x0A, - KeyCode::ModAlt => 0xA1, - KeyCode::ModShiftLeft => 0xA2, - KeyCode::ModShiftRight => 0xA3, - KeyCode::ModSym => 0xA4, - KeyCode::ModCtrl => 0xA5, - KeyCode::Esc => 0xB1, - KeyCode::Left => 0xB4, - KeyCode::Up => 0xB5, - KeyCode::Down => 0xB6, - KeyCode::Right => 0xB7, - KeyCode::Break => 0xD0, - KeyCode::Insert => 0xD1, - KeyCode::Home => 0xD2, - KeyCode::Del => 0xD4, - KeyCode::End => 0xD5, - KeyCode::PageUp => 0xD6, - KeyCode::PageDown => 0xD7, - KeyCode::CapsLock => 0xC1, - KeyCode::F1 => 0x81, - KeyCode::F2 => 0x82, - KeyCode::F3 => 0x83, - KeyCode::F4 => 0x84, - KeyCode::F5 => 0x85, - KeyCode::F6 => 0x86, - KeyCode::F7 => 0x87, - KeyCode::F8 => 0x88, - KeyCode::F9 => 0x89, - KeyCode::F10 => 0x90, - KeyCode::Char(char) => char as u8, - KeyCode::Unknown(i) => i, - } - } - } - - 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/src/main.rs b/user-apps/calculator/src/main.rs index 54b9cbb..2e0bfec 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,7 +2,12 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, KeyState, display::Display, get_key, lock_display, print}; +use abi::{ + display::{Display, lock_display}, + get_key, + keyboard::{KeyCode, KeyState}, + print, +}; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index a896070..be73636 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -4,9 +4,11 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, list_dir, lock_display, print, read_file, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + fs::{list_dir, read_file}, + get_key, + keyboard::{KeyCode, KeyState}, + print, }; use alloc::{format, string::ToString}; use core::panic::PanicInfo; diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 78d9451..a979b33 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -3,9 +3,11 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, Rng, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, lock_display, print, sleep, + Rng, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + get_key, + keyboard::{KeyCode, KeyState}, + print, sleep, }; use alloc::format; use core::panic::PanicInfo; From ddcdd5942a7e2b15337895fdc897bc5a949f8077 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 18:24:44 -0600 Subject: [PATCH 61/64] make all syscalls ffi compat --- abi/src/lib.rs | 25 ++++++++++++++++++++++--- abi_sys/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ cbindgen.toml | 4 ++++ kernel/src/abi.rs | 12 +++++++----- 4 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 cbindgen.toml diff --git a/abi/src/lib.rs b/abi/src/lib.rs index e8daf7c..efbb865 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -25,11 +25,12 @@ pub fn get_key() -> KeyEvent { } pub mod display { + use abi_sys::CPixel; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, pixelcolor::{Rgb565, RgbColor}, - prelude::{DrawTarget, Size}, + prelude::{DrawTarget, IntoStorage, Size}, primitives::Rectangle, }; @@ -42,8 +43,27 @@ pub mod display { abi_sys::lock_display(lock); } + const BUF_SIZE: usize = 1024; // tune this for performance + fn draw_iter(pixels: &[Pixel]) { - abi_sys::draw_iter(pixels.as_ptr(), pixels.len()) + let mut cpixels: [CPixel; BUF_SIZE] = [const { + CPixel { + x: 0, + y: 0, + color: 0, + } + }; BUF_SIZE]; + for (px, cpx) in pixels.iter().zip(cpixels.iter_mut()) { + let Pixel(pos, color) = px; + let color: u16 = color.into_storage(); // convert Rgb565 -> u16 + *cpx = CPixel { + x: pos.x, + y: pos.y, + color, + }; + } + + abi_sys::draw_iter(cpixels.as_ptr(), cpixels.len()) } pub struct Display; @@ -68,7 +88,6 @@ pub mod display { 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; diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 89c0427..5f0aa78 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,10 +1,16 @@ #![no_std] -use embedded_graphics::{Pixel, pixelcolor::Rgb565}; +use embedded_graphics::{ + Pixel, + pixelcolor::Rgb565, + prelude::{IntoStorage, Point}, +}; use embedded_sdmmc::DirEntry; -use strum::{EnumCount, EnumIter}; +use strum::EnumIter; -#[derive(Clone, Copy, EnumIter, EnumCount)] +pub const ABI_CALL_TABLE_COUNT: usize = 9; + +#[derive(Clone, Copy, EnumIter)] #[repr(u8)] pub enum CallAbiTable { PrintString = 0, @@ -17,11 +23,12 @@ pub enum CallAbiTable { ReadFile = 7, FileLen = 8, } + pub type EntryFn = fn(); #[unsafe(no_mangle)] #[unsafe(link_section = ".syscall_table")] -pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; +pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT]; pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); @@ -50,10 +57,37 @@ pub extern "C" fn lock_display(lock: bool) { f(lock); } -pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); +#[repr(C)] +#[derive(Copy, Clone)] +pub struct CPixel { + pub x: i32, + pub y: i32, + pub color: u16, +} + +impl Into for Pixel { + fn into(self) -> CPixel { + CPixel { + x: self.0.x, + y: self.0.y, + color: self.1.into_storage(), + } + } +} + +impl Into> for CPixel { + fn into(self) -> Pixel { + let r5 = ((self.color >> 11) & 0x1F) as u8; + let g6 = ((self.color >> 5) & 0x3F) as u8; + let b5 = (self.color & 0x1F) as u8; + Pixel(Point::new(self.x, self.y), Rgb565::new(r5, g6, b5)) + } +} + +pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize); #[unsafe(no_mangle)] -pub extern "C" fn draw_iter(ptr: *const Pixel, len: usize) { +pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { let f: DrawIterAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; f(ptr, len); diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..56fe889 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,4 @@ +language = "C" + +[macro_expansion] +bitflags = true diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index e61bc24..9f1a607 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ - DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, SleepAbi, - keyboard::*, + CPixel, DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, + SleepAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; @@ -43,10 +43,12 @@ pub extern "C" fn lock_display(lock: bool) { const _: DrawIterAbi = draw_iter; // TODO: maybe return result -pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { +pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let pixels = unsafe { core::slice::from_raw_parts(pixels, len) }; - unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } + let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; + + let iter = cpixels.iter().copied().map(|c: CPixel| c.into()); + unsafe { FRAMEBUFFER.draw_iter(iter).unwrap() } } pub static mut KEY_CACHE: Queue = Queue::new(); From 28417cce694d6645061fd35c45cc5e4e2c449317 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 00:18:44 -0600 Subject: [PATCH 62/64] fix bug in abi_sys causing display driver crashes --- abi/src/lib.rs | 35 ++++++++--------------------------- abi_sys/src/lib.rs | 17 ++++++++++++----- kernel/src/abi.rs | 2 +- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index efbb865..0e775a5 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -5,6 +5,8 @@ use abi_sys::{RngRequest, keyboard::KeyEvent}; use rand_core::RngCore; use talc::*; +extern crate alloc; + static mut ARENA: [u8; 10000] = [0; 10000]; #[global_allocator] @@ -29,8 +31,8 @@ pub mod display { use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, - pixelcolor::{Rgb565, RgbColor}, - prelude::{DrawTarget, IntoStorage, Size}, + pixelcolor::Rgb565, + prelude::{DrawTarget, Size}, primitives::Rectangle, }; @@ -45,27 +47,6 @@ pub mod display { const BUF_SIZE: usize = 1024; // tune this for performance - fn draw_iter(pixels: &[Pixel]) { - let mut cpixels: [CPixel; BUF_SIZE] = [const { - CPixel { - x: 0, - y: 0, - color: 0, - } - }; BUF_SIZE]; - for (px, cpx) in pixels.iter().zip(cpixels.iter_mut()) { - let Pixel(pos, color) = px; - let color: u16 = color.into_storage(); // convert Rgb565 -> u16 - *cpx = CPixel { - x: pos.x, - y: pos.y, - color, - }; - } - - abi_sys::draw_iter(cpixels.as_ptr(), cpixels.len()) - } - pub struct Display; impl Dimensions for Display { @@ -88,21 +69,21 @@ pub mod display { where I: IntoIterator>, { - let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; + let mut buf: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; let mut count = 0; for p in pixels { - buf[count] = p; + buf[count] = p.into(); count += 1; if count == BUF_SIZE { - draw_iter(&buf[..count]); + abi_sys::draw_iter(buf.as_ptr(), count); count = 0; } } if count > 0 { - draw_iter(&buf[..count]); + abi_sys::draw_iter(buf.as_ptr(), count); } Ok(()) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 5f0aa78..2f583ac 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -2,7 +2,7 @@ use embedded_graphics::{ Pixel, - pixelcolor::Rgb565, + pixelcolor::{Rgb565, raw::RawU16}, prelude::{IntoStorage, Point}, }; use embedded_sdmmc::DirEntry; @@ -65,6 +65,16 @@ pub struct CPixel { pub color: u16, } +impl CPixel { + pub fn new() -> Self { + Self { + x: 0, + y: 0, + color: 0, + } + } +} + impl Into for Pixel { fn into(self) -> CPixel { CPixel { @@ -77,10 +87,7 @@ impl Into for Pixel { impl Into> for CPixel { fn into(self) -> Pixel { - let r5 = ((self.color >> 11) & 0x1F) as u8; - let g6 = ((self.color >> 5) & 0x3F) as u8; - let b5 = (self.color & 0x1F) as u8; - Pixel(Point::new(self.x, self.y), Rgb565::new(r5, g6, b5)) + Pixel(Point::new(self.x, self.y), RawU16::new(self.color).into()) } } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 9f1a607..df6f4e9 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -5,7 +5,7 @@ use abi_sys::{ use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; -use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; +use embedded_graphics::draw_target::DrawTarget; use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; From 76c7870a798420346253a297fb83380b267c4fad Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 00:25:56 -0600 Subject: [PATCH 63/64] remove shared crate --- Cargo.lock | 11 ----------- Cargo.toml | 1 - README.md | 5 ++--- abi/Cargo.toml | 1 - kernel/Cargo.toml | 2 -- shared/Cargo.toml | 13 ------------- shared/src/lib.rs | 1 - 7 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 shared/Cargo.toml delete mode 100644 shared/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 21df7c7..19a99f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,6 @@ dependencies = [ "embedded-graphics", "embedded-sdmmc", "rand_core 0.9.3", - "shared", "spin", "talc", ] @@ -1469,7 +1468,6 @@ dependencies = [ "panic-probe", "portable-atomic", "rand", - "shared", "spin", "st7365p-lcd", "static_cell", @@ -2208,15 +2206,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "shared" -version = "0.1.0" -dependencies = [ - "abi_sys", - "bitflags 2.9.4", - "defmt 0.3.100", -] - [[package]] name = "siphasher" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 3080f3d..5f3e4be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "3" members = [ "kernel", "abi", - "shared", "user-apps/calculator", "user-apps/snake", "user-apps/gallery", diff --git a/README.md b/README.md index 657623e..9bffec0 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI ## Project Structure - **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) -- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace interaction -- **`abi/`** – ABI helpers and abstractions for easier development -- **`shared/`** – Shared utilities and common code across kernel and user applications +- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace (Repr "C") +- **`abi/`** – Rust focused ABI helpers and abstractions for easier development - **`user-apps/`** – Collection of userspace programs (calculator, snake, etc.) ## Features diff --git a/abi/Cargo.toml b/abi/Cargo.toml index bb7337f..935faa5 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] embedded-sdmmc = { version = "0.9.0", default-features = false } embedded-graphics = "0.8.1" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } talc = "4.4.3" spin = "0.10.0" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 13d4eef..2db6363 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -17,7 +17,6 @@ rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", - "shared/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", @@ -91,5 +90,4 @@ goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" bumpalo = "3.19.0" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } diff --git a/shared/Cargo.toml b/shared/Cargo.toml deleted file mode 100644 index 8796b8a..0000000 --- a/shared/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "shared" -version = "0.1.0" -edition = "2024" - -[features] -default = [] -defmt = ["dep:defmt"] - -[dependencies] -abi_sys = { path = "../abi_sys" } -bitflags = "2.9.4" -defmt = { version = "0.3", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/shared/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] From 38cdfcd54959184b44e879127b2c90d23f579a60 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 18:23:31 -0600 Subject: [PATCH 64/64] add syscall to get time in ms since application start --- abi_sys/src/lib.rs | 29 +++++++++++++++++++---------- kernel/src/abi.rs | 16 +++++++++++++--- kernel/src/elf.rs | 1 + kernel/src/main.rs | 5 +++-- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2f583ac..b7ac704 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -8,20 +8,21 @@ use embedded_graphics::{ use embedded_sdmmc::DirEntry; use strum::EnumIter; -pub const ABI_CALL_TABLE_COUNT: usize = 9; +pub const ABI_CALL_TABLE_COUNT: usize = 10; #[derive(Clone, Copy, EnumIter)] #[repr(u8)] pub enum CallAbiTable { PrintString = 0, SleepMs = 1, - LockDisplay = 2, - DrawIter = 3, - GetKey = 4, - GenRand = 5, - ListDir = 6, - ReadFile = 7, - FileLen = 8, + GetMs = 2, + LockDisplay = 3, + DrawIter = 4, + GetKey = 5, + GenRand = 6, + ListDir = 7, + ReadFile = 8, + FileLen = 9, } pub type EntryFn = fn(); @@ -39,15 +40,23 @@ pub extern "C" fn print(ptr: *const u8, len: usize) { f(ptr, len); } -pub type SleepAbi = extern "C" fn(ms: u64); +pub type SleepMsAbi = extern "C" fn(ms: u64); #[unsafe(no_mangle)] pub extern "C" fn sleep(ms: u64) { - let f: SleepAbi = + let f: SleepMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; f(ms); } +pub type GetMsAbi = extern "C" fn() -> u64; + +#[unsafe(no_mangle)] +pub extern "C" fn get_ms() -> u64 { + let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetMs as usize]) }; + f() +} + pub type LockDisplay = extern "C" fn(lock: bool); #[unsafe(no_mangle)] diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index df6f4e9..3bed7c0 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,10 +1,11 @@ use abi_sys::{ - CPixel, DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, - SleepAbi, keyboard::*, + CPixel, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, LockDisplay, PrintAbi, ReadFile, + RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; +use embassy_time::Instant; use embedded_graphics::draw_target::DrawTarget; use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; @@ -26,7 +27,7 @@ pub extern "C" fn print(ptr: *const u8, len: usize) { } } -const _: SleepAbi = sleep; +const _: SleepMsAbi = sleep; pub extern "C" fn sleep(ms: u64) { let cycles_per_ms = clk_sys_freq() / 1000; let total_cycles = ms * cycles_per_ms as u64; @@ -36,6 +37,15 @@ pub extern "C" fn sleep(ms: u64) { } } +pub static mut MS_SINCE_LAUNCH: Option = None; + +const _: GetMsAbi = get_ms; +pub extern "C" fn get_ms() -> u64 { + Instant::now() + .duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() }) + .as_millis() +} + const _: LockDisplay = lock_display; pub extern "C" fn lock_display(lock: bool) { FB_PAUSED.store(lock, Ordering::Relaxed); diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 0bc6591..2fcec1b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -198,6 +198,7 @@ fn patch_abi( let ptr = match call { CallAbiTable::PrintString => abi::print as usize, CallAbiTable::SleepMs => abi::sleep as usize, + CallAbiTable::GetMs => abi::get_ms as usize, CallAbiTable::LockDisplay => abi::lock_display 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 431fc50..57490ec 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -18,7 +18,7 @@ mod usb; mod utils; use crate::{ - abi::KEY_CACHE, + abi::{KEY_CACHE, MS_SINCE_LAUNCH}, display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ conf_peripherals, @@ -56,7 +56,7 @@ use embassy_rp::{ use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal, }; -use embassy_time::{Delay, Timer}; +use embassy_time::{Delay, Instant, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; use static_cell::StaticCell; @@ -142,6 +142,7 @@ async fn userland_task() { MSC_SHUTDOWN.signal(()); } + unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) }; defmt::info!("Executing Binary"); entry();