From f353cad108bdf22dde30f444634c0cceeef199cf Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 28 Sep 2025 22:24:29 -0600 Subject: [PATCH] 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, + _ => (), + } + }; + } +}