9 Commits

Author SHA1 Message Date
d17c3de72f Merge branch 'main' into nes 2025-11-17 08:12:31 -07:00
ce48dde824 ugh 2025-11-13 17:46:53 -07:00
b1a0351399 WIP 2025-11-13 12:31:40 -07:00
35d9cd092d overclock 2025-11-12 14:11:13 -07:00
f7a0562625 Merge branch 'main' into nes 2025-11-12 14:07:57 -07:00
026a5f2404 updates 2025-11-09 16:05:11 -07:00
56d5f02907 Merge branch 'main' into nes 2025-11-09 14:26:39 -07:00
e966763fed runnintg 2025-11-02 19:29:17 -07:00
957189cd0b init 2025-11-02 15:46:22 -07:00
47 changed files with 947 additions and 1008 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
/target /target
*.uf2 *.uf2
userlib_sys.h abi_sys.h
assets/gif/bad_apple.gif assets/gif/bad_apple.gif
picolibc picolibc
user-apps/gboy/Peanut-GB user-apps/gboy/Peanut-GB

204
Cargo.lock generated
View File

@@ -12,6 +12,27 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "abi"
version = "0.1.0"
dependencies = [
"abi_sys",
"embedded-graphics",
"once_cell",
"rand_core 0.9.3",
]
[[package]]
name = "abi_sys"
version = "0.1.0"
dependencies = [
"bitflags 2.10.0",
"cbindgen",
"defmt 0.3.100",
"embedded-graphics",
"strum",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.12" version = "0.8.12"
@@ -110,6 +131,26 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
"bitflags 2.10.0",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.110",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.3" version = "0.5.3"
@@ -222,9 +263,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
name = "calculator" name = "calculator"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi",
"embedded-graphics", "embedded-graphics",
"embedded-layout", "embedded-layout",
"userlib",
] ]
[[package]] [[package]]
@@ -246,12 +287,42 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "cc"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.25" version = "3.2.25"
@@ -356,9 +427,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"typenum", "typenum",
@@ -877,14 +948,6 @@ dependencies = [
"rlsf", "rlsf",
] ]
[[package]]
name = "embedded-audio"
version = "0.1.0"
source = "git+https://github.com/LegitCamper/embedded-audio#087784644d810b94dd659a03dbed4795dfb0bd24"
dependencies = [
"heapless",
]
[[package]] [[package]]
name = "embedded-graphics" name = "embedded-graphics"
version = "0.8.1" version = "0.8.1"
@@ -1077,6 +1140,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]] [[package]]
name = "fixed" name = "fixed"
version = "1.29.0" version = "1.29.0"
@@ -1209,16 +1278,27 @@ dependencies = [
name = "gallery" name = "gallery"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi",
"embedded-graphics", "embedded-graphics",
"tinybmp", "tinybmp",
"userlib", ]
[[package]]
name = "gboy"
version = "0.1.0"
dependencies = [
"abi",
"bindgen",
"cc",
"embedded-graphics",
"selection_ui",
] ]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@@ -1251,12 +1331,18 @@ dependencies = [
name = "gif" name = "gif"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi",
"embedded-graphics", "embedded-graphics",
"selection_ui", "selection_ui",
"tinygif", "tinygif",
"userlib",
] ]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]] [[package]]
name = "goblin" name = "goblin"
version = "0.10.3" version = "0.10.3"
@@ -1429,6 +1515,7 @@ dependencies = [
name = "kernel" name = "kernel"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"abi_sys",
"assign-resources", "assign-resources",
"bitflags 2.10.0", "bitflags 2.10.0",
"bt-hci", "bt-hci",
@@ -1456,10 +1543,8 @@ dependencies = [
"embedded-layout", "embedded-layout",
"embedded-sdmmc", "embedded-sdmmc",
"embedded-text", "embedded-text",
"fixed",
"goblin", "goblin",
"heapless", "heapless",
"micromath",
"num_enum 0.7.5", "num_enum 0.7.5",
"once_cell", "once_cell",
"panic-probe", "panic-probe",
@@ -1471,7 +1556,6 @@ dependencies = [
"strum", "strum",
"talc", "talc",
"trouble-host", "trouble-host",
"userlib_sys",
] ]
[[package]] [[package]]
@@ -1549,6 +1633,16 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.10" version = "0.1.10"
@@ -1604,6 +1698,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@@ -1625,6 +1725,16 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1888,6 +1998,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "prettyplease"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
dependencies = [
"proc-macro2",
"syn 2.0.110",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@@ -2077,6 +2197,12 @@ dependencies = [
"crc-any", "crc-any",
] ]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@@ -2136,10 +2262,10 @@ checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
name = "selection_ui" name = "selection_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi",
"embedded-graphics", "embedded-graphics",
"embedded-layout", "embedded-layout",
"embedded-text", "embedded-text",
"userlib",
] ]
[[package]] [[package]]
@@ -2216,6 +2342,12 @@ dependencies = [
"keccak", "keccak",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.1" version = "1.0.1"
@@ -2250,10 +2382,10 @@ dependencies = [
name = "snake" name = "snake"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi",
"embedded-graphics", "embedded-graphics",
"embedded-snake", "embedded-snake",
"rand", "rand",
"userlib",
] ]
[[package]] [[package]]
@@ -2626,27 +2758,6 @@ dependencies = [
"usbd-hid-descriptors", "usbd-hid-descriptors",
] ]
[[package]]
name = "userlib"
version = "0.1.0"
dependencies = [
"embedded-graphics",
"once_cell",
"rand_core 0.9.3",
"userlib_sys",
]
[[package]]
name = "userlib_sys"
version = "0.1.0"
dependencies = [
"bitflags 2.10.0",
"cbindgen",
"defmt 0.3.100",
"embedded-graphics",
"strum",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.18.1" version = "1.18.1"
@@ -2754,17 +2865,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "wav_player"
version = "0.1.0"
dependencies = [
"embedded-audio",
"embedded-graphics",
"rand",
"selection_ui",
"userlib",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -2,14 +2,14 @@
resolver = "3" resolver = "3"
members = [ members = [
"kernel", "kernel",
"userlib_sys", "abi_sys",
"userlib", "abi",
"selection_ui", "selection_ui",
"user_apps/calculator", "user-apps/calculator",
"user_apps/snake", "user-apps/snake",
"user_apps/gallery", "user-apps/gallery",
"user_apps/gif", "user-apps/gif",
"user_apps/wav_player", "user-apps/gboy",
] ]
[profile.release] [profile.release]

View File

@@ -1,27 +1,26 @@
# PicoCalc OS (Rust) # PicoCalc OS (Rust)
A simple kernel and applications for the **Clockwork PicoCalc**, written in Rust. A simple operating system for the **Clockwork PicoCalc**, written in Rust.
This project provides a minimal kernel, syscall table, and user-space applications to experiment with kernel development on constrained hardware. This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware.
## Status ## Status
Basic synchronous applications are working great. Basic synchronous applications are working great.
Current focus is on exanding applications and porting software, finding bugs in ffi, and making sure the kernel is as stable as possible. Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI driver** to make application development easier and smoother.
## Project Structure ## Project Structure
- **`kernel/`** The core OS kernel - **`kernel/`** The core OS kernel (task scheduling, drivers, memory, etc.)
- **`userlib_sys/`** C FFI bindings for kernel syscall - **`abi_sys/`** Shared application binary interface definitions for kernel ↔ userspace (Repr "C")
- **`userlib/`** Rust wrapper on top of `userlib_sys` - **`abi/`** Rust focused ABI helpers and abstractions for easier development
- **`picolib/`** Built with ```just newlib```, and provides libc symbols when linking with C libraries - **`user-apps/`** Collection of userspace programs (calculator, snake, etc.)
- **`user_apps/`** Collection of userspace programs (gif player, wav player, calculator, snake, etc.)
## Features ## Features
- Minimal Rust-based kernel targeting the PicoCalc - Minimal Rust-based kernel targeting the PicoCalc
- Custom ABI for *Mostly* safe communication between kernel and applications - Custom ABI for safe communication between kernel and applications
- Support for multiple user-space applications - Support for multiple user-space applications
- Hardware drivers tailored for the PicoCalc( Audio, Display, Keyboard, ans Storage ) - Hardware drivers tailored for the PicoCalc
## Getting Started ## Getting Started

View File

@@ -1,10 +1,10 @@
[package] [package]
name = "userlib" name = "abi"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib_sys = { path = "../userlib_sys" } abi_sys = { path = "../abi_sys" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
once_cell = { version = "1", default-features = false } once_cell = { version = "1", default-features = false }
rand_core = "0.9.3" rand_core = "0.9.3"

View File

@@ -3,11 +3,11 @@
extern crate alloc; extern crate alloc;
use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent};
pub use abi_sys::{keyboard, print};
pub use alloc::format; pub use alloc::format;
use core::alloc::{GlobalAlloc, Layout}; use core::alloc::{GlobalAlloc, Layout};
use rand_core::RngCore; use rand_core::RngCore;
use userlib_sys::{RngRequest, keyboard::KeyEvent};
pub use userlib_sys::{keyboard, print};
#[global_allocator] #[global_allocator]
static ALLOC: Alloc = Alloc; static ALLOC: Alloc = Alloc;
@@ -16,11 +16,11 @@ struct Alloc;
unsafe impl GlobalAlloc for Alloc { unsafe impl GlobalAlloc for Alloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
userlib_sys::alloc(layout.into()) alloc(layout.into())
} }
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
userlib_sys::dealloc(ptr, layout.into()); dealloc(ptr, layout.into());
} }
} }
@@ -33,20 +33,21 @@ macro_rules! println {
} }
pub fn sleep(ms: u64) { pub fn sleep(ms: u64) {
userlib_sys::sleep(ms); abi_sys::sleep(ms);
} }
pub fn get_ms() -> u64 { pub fn get_ms() -> u64 {
userlib_sys::get_ms() abi_sys::get_ms()
} }
pub fn get_key() -> KeyEvent { pub fn get_key() -> KeyEvent {
userlib_sys::keyboard::get_key().into() abi_sys::keyboard::get_key().into()
} }
pub mod display { pub mod display {
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use abi_sys::CPixel;
use embedded_graphics::{ use embedded_graphics::{
Pixel, Pixel,
geometry::{Dimensions, Point}, geometry::{Dimensions, Point},
@@ -54,7 +55,6 @@ pub mod display {
prelude::{DrawTarget, Size}, prelude::{DrawTarget, Size},
primitives::Rectangle, primitives::Rectangle,
}; };
use userlib_sys::CPixel;
pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_WIDTH: usize = 320;
pub const SCREEN_HEIGHT: usize = 320; pub const SCREEN_HEIGHT: usize = 320;
@@ -110,13 +110,13 @@ pub mod display {
count += 1; count += 1;
if count == BUF_SIZE - 1 { if count == BUF_SIZE - 1 {
userlib_sys::draw_iter(unsafe { BUF.as_ptr() }, count); abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
count = 0; count = 0;
} }
} }
if count > 0 { if count > 0 {
userlib_sys::draw_iter(unsafe { BUF.as_ptr() }, count); abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
} }
Ok(()) Ok(())
@@ -125,7 +125,7 @@ pub mod display {
} }
fn gen_rand(req: &mut RngRequest) { fn gen_rand(req: &mut RngRequest) {
userlib_sys::gen_rand(req); abi_sys::gen_rand(req);
} }
pub struct Rng; pub struct Rng;
@@ -162,7 +162,7 @@ pub mod fs {
use core::fmt::Display; use core::fmt::Display;
pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize { pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize {
userlib_sys::read_file( abi_sys::read_file(
file.as_ptr(), file.as_ptr(),
file.len(), file.len(),
start_from, start_from,
@@ -172,7 +172,7 @@ pub mod fs {
} }
pub fn write_file(file: &str, start_from: usize, buf: &[u8]) { pub fn write_file(file: &str, start_from: usize, buf: &[u8]) {
userlib_sys::write_file( abi_sys::write_file(
file.as_ptr(), file.as_ptr(),
file.len(), file.len(),
start_from, start_from,
@@ -228,12 +228,6 @@ pub mod fs {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]);
impl Default for Entries {
fn default() -> Self {
Self::new()
}
}
impl Entries { impl Entries {
pub fn new() -> Self { pub fn new() -> Self {
Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]) Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES])
@@ -260,7 +254,7 @@ pub mod fs {
} }
pub fn list_dir(path: &str, entries: &mut Entries) -> usize { pub fn list_dir(path: &str, entries: &mut Entries) -> usize {
userlib_sys::list_dir( abi_sys::list_dir(
path.as_ptr(), path.as_ptr(),
path.len(), path.len(),
entries.as_ptrs().as_mut_ptr(), entries.as_ptrs().as_mut_ptr(),
@@ -270,14 +264,6 @@ pub mod fs {
} }
pub fn file_len(str: &str) -> usize { pub fn file_len(str: &str) -> usize {
userlib_sys::file_len(str.as_ptr(), str.len()) abi_sys::file_len(str.as_ptr(), str.len())
}
}
pub mod audio {
pub use userlib_sys::{AUDIO_BUFFER_LEN, AUDIO_BUFFER_SAMPLES, audio_buffer_ready};
pub fn send_audio_buffer(buf: &[u8]) {
userlib_sys::send_audio_buffer(buf.as_ptr(), buf.len())
} }
} }

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "userlib_sys" name = "abi_sys"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View File

@@ -12,12 +12,12 @@ use strum::{EnumCount, EnumIter};
pub type EntryFn = fn(); pub type EntryFn = fn();
pub const SYS_CALL_TABLE_COUNT: usize = 15; pub const ABI_CALL_TABLE_COUNT: usize = 12;
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT); const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
#[derive(Clone, Copy, EnumIter, EnumCount)] #[derive(Clone, Copy, EnumIter, EnumCount)]
#[repr(u8)] #[repr(u8)]
pub enum SyscallTable { pub enum CallTable {
Alloc = 0, Alloc = 0,
Dealloc = 1, Dealloc = 1,
PrintString = 2, PrintString = 2,
@@ -30,14 +30,11 @@ pub enum SyscallTable {
ReadFile = 9, ReadFile = 9,
WriteFile = 10, WriteFile = 10,
FileLen = 11, FileLen = 11,
ReconfigureAudioSampleRate = 12,
AudioBufferReady = 13,
SendAudioBuffer = 14,
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
#[unsafe(link_section = ".syscall_table")] #[unsafe(link_section = ".syscall_table")]
pub static mut SYS_CALL_TABLE: [usize; SYS_CALL_TABLE_COUNT] = [0; SYS_CALL_TABLE_COUNT]; pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT];
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[repr(C)] #[repr(C)]
@@ -47,9 +44,9 @@ pub struct CLayout {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl From<CLayout> for Layout { impl Into<Layout> for CLayout {
fn from(val: CLayout) -> Self { fn into(self) -> Layout {
unsafe { Layout::from_size_align_unchecked(val.size, val.alignment) } unsafe { Layout::from_size_align_unchecked(self.size, self.alignment) }
} }
} }
@@ -63,51 +60,51 @@ impl From<Layout> for CLayout {
} }
} }
pub type Alloc = extern "C" fn(layout: CLayout) -> *mut u8; pub type AllocAbi = extern "C" fn(layout: CLayout) -> *mut u8;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
let f: Alloc = unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Alloc as usize]) }; let f: AllocAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Alloc as usize]) };
f(layout) f(layout)
} }
pub type Dealloc = extern "C" fn(ptr: *mut u8, layout: CLayout); pub type DeallocAbi = extern "C" fn(ptr: *mut u8, layout: CLayout);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
let f: Dealloc = let f: DeallocAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Dealloc as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) };
f(ptr, layout) f(ptr, layout)
} }
pub type Print = extern "C" fn(ptr: *const u8, len: usize); pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn print(ptr: *const u8, len: usize) { pub extern "C" fn print(ptr: *const u8, len: usize) {
let f: Print = let f: PrintAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::PrintString as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) };
f(ptr, len); f(ptr, len);
} }
pub type SleepMs = extern "C" fn(ms: u64); pub type SleepMsAbi = extern "C" fn(ms: u64);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn sleep(ms: u64) { pub extern "C" fn sleep(ms: u64) {
let f: SleepMs = let f: SleepMsAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::SleepMs as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) };
f(ms); f(ms);
} }
pub type GetMs = extern "C" fn() -> u64; pub type GetMsAbi = extern "C" fn() -> u64;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn get_ms() -> u64 { pub extern "C" fn get_ms() -> u64 {
let f: GetMs = unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetMs as usize]) }; let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetMs as usize]) };
f() f()
} }
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone)] #[derive(Copy, Clone)]
pub struct CPixel { pub struct CPixel {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
@@ -124,36 +121,33 @@ impl CPixel {
} }
} }
impl From<CPixel> for Pixel<Rgb565> { impl Into<CPixel> for Pixel<Rgb565> {
fn from(value: CPixel) -> Self { fn into(self) -> CPixel {
Pixel(
Point::new(value.x, value.y),
RawU16::new(value.color).into(),
)
}
}
impl From<Pixel<Rgb565>> for CPixel {
fn from(value: Pixel<Rgb565>) -> Self {
CPixel { CPixel {
x: value.0.x, x: self.0.x,
y: value.0.y, y: self.0.y,
color: value.1.into_storage(), color: self.1.into_storage(),
} }
} }
} }
pub type DrawIter = extern "C" fn(ptr: *const CPixel, len: usize); impl Into<Pixel<Rgb565>> for CPixel {
fn into(self) -> Pixel<Rgb565> {
Pixel(Point::new(self.x, self.y), RawU16::new(self.color).into())
}
}
pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) {
let f: DrawIter = let f: DrawIterAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::DrawIter as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) };
f(ptr, len); f(ptr, len);
} }
pub mod keyboard { pub mod keyboard {
use crate::{SYS_CALL_TABLE, SyscallTable}; use crate::{CALL_ABI_TABLE, CallTable};
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -175,12 +169,12 @@ pub mod keyboard {
pub mods: Modifiers, pub mods: Modifiers,
} }
impl From<KeyEventC> for KeyEvent { impl Into<KeyEvent> for KeyEventC {
fn from(val: KeyEventC) -> Self { fn into(self) -> KeyEvent {
KeyEvent { KeyEvent {
key: val.key.into(), key: self.key.into(),
state: val.state, state: self.state,
mods: val.mods, mods: self.mods,
} }
} }
} }
@@ -192,12 +186,12 @@ pub mod keyboard {
pub mods: Modifiers, pub mods: Modifiers,
} }
impl From<KeyEvent> for KeyEventC { impl Into<KeyEventC> for KeyEvent {
fn from(val: KeyEvent) -> Self { fn into(self) -> KeyEventC {
KeyEventC { KeyEventC {
key: val.key.into(), key: self.key.into(),
state: val.state, state: self.state,
mods: val.mods, mods: self.mods,
} }
} }
} }
@@ -218,7 +212,7 @@ pub mod keyboard {
1 => KeyState::Pressed, 1 => KeyState::Pressed,
2 => KeyState::Hold, 2 => KeyState::Hold,
3 => KeyState::Released, 3 => KeyState::Released,
_ => KeyState::Idle, 0 | _ => KeyState::Idle,
} }
} }
} }
@@ -271,9 +265,9 @@ pub mod keyboard {
Unknown(u8), Unknown(u8),
} }
impl From<KeyCode> for u8 { impl Into<u8> for KeyCode {
fn from(val: KeyCode) -> Self { fn into(self) -> u8 {
match val { match self {
KeyCode::JoyUp => 0x01, KeyCode::JoyUp => 0x01,
KeyCode::JoyDown => 0x02, KeyCode::JoyDown => 0x02,
KeyCode::JoyLeft => 0x03, KeyCode::JoyLeft => 0x03,
@@ -371,12 +365,12 @@ pub mod keyboard {
} }
} }
pub type GetKey = extern "C" fn() -> KeyEventC; pub type GetKeyAbi = extern "C" fn() -> KeyEventC;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn get_key() -> KeyEventC { pub extern "C" fn get_key() -> KeyEventC {
let f: GetKey = let f: GetKeyAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetKey as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) };
f() f()
} }
} }
@@ -393,7 +387,7 @@ pub type GenRand = extern "C" fn(req: &mut RngRequest);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn gen_rand(req: &mut RngRequest) { pub extern "C" fn gen_rand(req: &mut RngRequest) {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::GenRand as usize]; let ptr = CALL_ABI_TABLE[CallTable::GenRand as usize];
let f: GenRand = core::mem::transmute(ptr); let f: GenRand = core::mem::transmute(ptr);
f(req) f(req)
} }
@@ -416,7 +410,7 @@ pub extern "C" fn list_dir(
max_entry_str_len: usize, max_entry_str_len: usize,
) -> usize { ) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::ListDir as usize]; let ptr = CALL_ABI_TABLE[CallTable::ListDir as usize];
let f: ListDir = core::mem::transmute(ptr); let f: ListDir = core::mem::transmute(ptr);
f(str, len, entries, entry_count, max_entry_str_len) f(str, len, entries, entry_count, max_entry_str_len)
} }
@@ -439,7 +433,7 @@ pub extern "C" fn read_file(
buf_len: usize, buf_len: usize,
) -> usize { ) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::ReadFile as usize]; let ptr = CALL_ABI_TABLE[CallTable::ReadFile as usize];
let f: ReadFile = core::mem::transmute(ptr); let f: ReadFile = core::mem::transmute(ptr);
f(str, len, read_from, buf, buf_len) f(str, len, read_from, buf, buf_len)
} }
@@ -457,7 +451,7 @@ pub extern "C" fn write_file(
buf_len: usize, buf_len: usize,
) { ) {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::WriteFile as usize]; let ptr = CALL_ABI_TABLE[CallTable::WriteFile as usize];
let f: WriteFile = core::mem::transmute(ptr); let f: WriteFile = core::mem::transmute(ptr);
f(str, len, write_from, buf, buf_len) f(str, len, write_from, buf, buf_len)
} }
@@ -468,44 +462,8 @@ pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::FileLen as usize]; let ptr = CALL_ABI_TABLE[CallTable::FileLen as usize];
let f: FileLen = core::mem::transmute(ptr); let f: FileLen = core::mem::transmute(ptr);
f(str, len) f(str, len)
} }
} }
pub type ReconfigureAudioSampleRate = extern "C" fn(sample_rate: u32);
#[allow(unused)]
pub fn reconfigure_audio_sample_rate(sample_rate: u32) {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::ReconfigureAudioSampleRate as usize];
let f: ReconfigureAudioSampleRate = core::mem::transmute(ptr);
f(sample_rate)
}
}
pub type AudioBufferReady = extern "C" fn() -> bool;
#[allow(unused)]
pub fn audio_buffer_ready() -> bool {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::AudioBufferReady as usize];
let f: AudioBufferReady = core::mem::transmute(ptr);
f()
}
}
pub const AUDIO_BUFFER_SAMPLES: usize = 1024;
pub const AUDIO_BUFFER_LEN: usize = AUDIO_BUFFER_SAMPLES * 2;
pub type SendAudioBuffer = extern "C" fn(ptr: *const u8, len: usize);
#[allow(unused)]
pub fn send_audio_buffer(buf: *const u8, len: usize) {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::SendAudioBuffer as usize];
let f: SendAudioBuffer = core::mem::transmute(ptr);
f(buf, len)
}
}

View File

@@ -1,17 +1,15 @@
target := "thumbv8m.main-none-eabihf"
kernel-dev board: kernel-dev board:
cargo run --bin kernel --features {{board}} --features fps --features defmt cargo run --bin kernel --features {{board}} --features fps
kernel-release-probe board: kernel-release-probe board:
cargo run --bin kernel --profile release --features {{board}} --features fps cargo run --bin kernel --profile release --features {{board}} --features fps
kernel-release board: kernel-release board:
cargo build --bin kernel --release --no-default-features --features {{board}} cargo build --bin kernel --release --no-default-features --features {{board}}
elf2uf2-rs -d target/{{target}}/release/kernel elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel
binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\""
cbindgen: cbindgen:
cbindgen userlib_sys --output userlib_sys.h -q cbindgen abi_sys --output abi_sys.h -q
newlib: newlib:
#!/bin/bash #!/bin/bash
@@ -41,10 +39,10 @@ userapps: cbindgen
just userapp snake just userapp snake
just userapp gallery just userapp gallery
just userapp gif just userapp gif
just userapp wav_player just userapp gboy
copy-userapp app: copy-userapp app:
cp ./target/{{target}}/release-binary/{{app}} /run/media/$(whoami)/PICOCALC/{{app}}.bin cp ./target/thumbv8m.main-none-eabihf/release-binary/{{app}} /run/media/$(whoami)/PICOCALC/{{app}}.bin
copy-userapps: copy-userapps:
#!/bin/bash #!/bin/bash
@@ -53,7 +51,6 @@ copy-userapps:
just copy-userapp snake just copy-userapp snake
just copy-userapp gallery just copy-userapp gallery
just copy-userapp gif just copy-userapp gif
just copy-userapp wav_player
DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}') DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}')
udisksctl unmount -b "$DEV" udisksctl unmount -b "$DEV"

View File

@@ -11,7 +11,7 @@ doctest = false
bench = false bench = false
[features] [features]
default = ["rp235x"] default = ["rp235x", "defmt"]
pimoroni2w = ["rp235x", "psram"] pimoroni2w = ["rp235x", "psram"]
# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb # rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb
rp235x = ["embassy-rp/rp235xb"] rp235x = ["embassy-rp/rp235xb"]
@@ -82,8 +82,6 @@ embedded-graphics = { version = "0.8.1" }
embedded-text = "0.7.2" embedded-text = "0.7.2"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"
micromath = "2.1.0"
fixed = "1.29.0"
strum = { version = "0.27.2", default-features = false } strum = { version = "0.27.2", default-features = false }
rand = { version = "0.9.0", default-features = false } rand = { version = "0.9.0", default-features = false }
once_cell = { version = "1.21.3", default-features = false } once_cell = { version = "1.21.3", default-features = false }
@@ -99,4 +97,4 @@ embedded-alloc = { version = "0.6.0", features = [
], optional = true } ], optional = true }
bumpalo = "3.19.0" bumpalo = "3.19.0"
userlib_sys = { path = "../userlib_sys" } abi_sys = { path = "../abi_sys" }

View File

@@ -14,7 +14,7 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))] #[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))]
const MEMORY: &[u8] = include_bytes!("rp2350.x"); const MEMORY: &'static [u8] = include_bytes!("rp2350.x");
#[cfg(feature = "pimoroni2w")] #[cfg(feature = "pimoroni2w")]
const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); const MEMORY: &'static [u8] = include_bytes!("rp2350.x");

View File

@@ -1,3 +1,7 @@
use abi_sys::{
AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir,
PrintAbi, ReadFile, RngRequest, SleepMsAbi, WriteFile, keyboard::*,
};
use alloc::{string::ToString, vec::Vec}; use alloc::{string::ToString, vec::Vec};
use core::{ffi::c_char, ptr, sync::atomic::Ordering}; use core::{ffi::c_char, ptr, sync::atomic::Ordering};
use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embassy_rp::clocks::{RoscRng, clk_sys_freq};
@@ -5,11 +9,6 @@ use embassy_time::Instant;
use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::draw_target::DrawTarget;
use embedded_sdmmc::LfnBuffer; use embedded_sdmmc::LfnBuffer;
use heapless::spsc::Queue; use heapless::spsc::Queue;
use userlib_sys::{
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen,
GenRand, GetMs, ListDir, Print, ReadFile, ReconfigureAudioSampleRate, RngRequest,
SendAudioBuffer, SleepMs, WriteFile, keyboard::*,
};
#[cfg(feature = "psram")] #[cfg(feature = "psram")]
use crate::heap::HEAP; use crate::heap::HEAP;
@@ -18,13 +17,12 @@ use crate::heap::HEAP;
use core::alloc::GlobalAlloc; use core::alloc::GlobalAlloc;
use crate::{ use crate::{
audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_SAMPLE_RATE, AUDIO_BUFFER_WRITTEN},
display::FRAMEBUFFER, display::FRAMEBUFFER,
framebuffer::FB_PAUSED, framebuffer::FB_PAUSED,
storage::{Dir, File, SDCARD}, storage::{Dir, File, SDCARD},
}; };
const _: Alloc = alloc; const _: AllocAbi = alloc;
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
// SAFETY: caller guarantees layout is valid // SAFETY: caller guarantees layout is valid
unsafe { unsafe {
@@ -35,12 +33,12 @@ pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
#[cfg(not(feature = "psram"))] #[cfg(not(feature = "psram"))]
{ {
alloc::alloc::alloc(layout.into()) return alloc::alloc::alloc(layout.into());
} }
} }
} }
const _: Dealloc = dealloc; const _: DeallocAbi = dealloc;
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
// SAFETY: caller guarantees ptr and layout are valid // SAFETY: caller guarantees ptr and layout are valid
#[cfg(feature = "psram")] #[cfg(feature = "psram")]
@@ -54,7 +52,7 @@ pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
} }
} }
const _: Print = print; const _: PrintAbi = print;
pub extern "C" fn print(ptr: *const u8, len: usize) { pub extern "C" fn print(ptr: *const u8, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
@@ -68,7 +66,7 @@ pub extern "C" fn print(ptr: *const u8, len: usize) {
} }
} }
const _: SleepMs = sleep; const _: SleepMsAbi = sleep;
pub extern "C" fn sleep(ms: u64) { pub extern "C" fn sleep(ms: u64) {
let cycles_per_ms = clk_sys_freq() / 1000; let cycles_per_ms = clk_sys_freq() / 1000;
let total_cycles = ms * cycles_per_ms as u64; let total_cycles = ms * cycles_per_ms as u64;
@@ -80,14 +78,14 @@ pub extern "C" fn sleep(ms: u64) {
pub static mut MS_SINCE_LAUNCH: Option<Instant> = None; pub static mut MS_SINCE_LAUNCH: Option<Instant> = None;
const _: GetMs = get_ms; const _: GetMsAbi = get_ms;
pub extern "C" fn get_ms() -> u64 { pub extern "C" fn get_ms() -> u64 {
Instant::now() Instant::now()
.duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() }) .duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() })
.as_millis() .as_millis()
} }
const _: DrawIter = draw_iter; const _: DrawIterAbi = draw_iter;
pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) };
@@ -101,7 +99,7 @@ pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new(); pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
const _: GetKey = get_key; const _: GetKeyAbi = get_key;
pub extern "C" fn get_key() -> KeyEventC { pub extern "C" fn get_key() -> KeyEventC {
if let Some(event) = unsafe { KEY_CACHE.dequeue() } { if let Some(event) = unsafe { KEY_CACHE.dequeue() } {
event.into() event.into()
@@ -134,7 +132,7 @@ unsafe fn copy_entry_to_user_buf(name: &[u8], dest: *mut c_char, max_str_len: us
if !dest.is_null() { if !dest.is_null() {
let len = name.len().min(max_str_len - 1); let len = name.len().min(max_str_len - 1);
unsafe { unsafe {
ptr::copy_nonoverlapping(name.as_ptr(), dest, len); ptr::copy_nonoverlapping(name.as_ptr(), dest as *mut u8, len);
*dest.add(len) = 0; // nul terminator *dest.add(len) = 0; // nul terminator
} }
} }
@@ -190,8 +188,8 @@ pub extern "C" fn list_dir(
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
let mut wrote = 0; let mut wrote = 0;
let _ = sd.access_root_dir(|root| { sd.access_root_dir(|root| {
if dirs[0].is_empty() && dirs.len() >= 2 { if dirs[0] == "" && dirs.len() >= 2 {
unsafe { unsafe {
if dir == "/" { if dir == "/" {
wrote = get_dir_entries(&root, files, max_entry_str_len); wrote = get_dir_entries(&root, files, max_entry_str_len);
@@ -209,16 +207,17 @@ fn recurse_file<T>(
dirs: &[&str], dirs: &[&str],
mut access: impl FnMut(&mut File) -> T, mut access: impl FnMut(&mut File) -> T,
) -> Result<T, ()> { ) -> Result<T, ()> {
defmt::info!("dir: {}, dirs: {}", dir, dirs);
if dirs.len() == 1 { if dirs.len() == 1 {
let mut b = [0_u8; 50]; let mut b = [0_u8; 50];
let mut buf = LfnBuffer::new(&mut b); let mut buf = LfnBuffer::new(&mut b);
let mut short_name = None; let mut short_name = None;
dir.iterate_dir_lfn(&mut buf, |entry, name| { dir.iterate_dir_lfn(&mut buf, |entry, name| {
if let Some(name) = name if let Some(name) = name {
&& (name == dirs[0] || entry.name.to_string().as_str() == dirs[0]) if name == dirs[0] || entry.name.to_string().as_str() == dirs[0] {
{
short_name = Some(entry.name.clone()); short_name = Some(entry.name.clone());
} }
}
}) })
.expect("Failed to iterate dir"); .expect("Failed to iterate dir");
@@ -257,17 +256,17 @@ pub extern "C" fn read_file(
} }
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
let mut read = 0; let mut read = 0;
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
if !file.is_empty() { if !file.is_empty() {
let _ = sd.access_root_dir(|root| { sd.access_root_dir(|root| {
if let Ok(result) = recurse_file(&root, &components[1..count], |file| { if let Ok(result) = recurse_file(&root, &components[1..count], |file| {
file.seek_from_start(start_from as u32).unwrap_or(()); file.seek_from_start(start_from as u32).unwrap_or(());
file.read(buf).unwrap() file.read(&mut buf).unwrap()
}) { }) {
read = result read = result
}; };
@@ -303,10 +302,10 @@ pub extern "C" fn write_file(
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
if !file.is_empty() { if !file.is_empty() {
let _ = sd.access_root_dir(|root| { sd.access_root_dir(|root| {
recurse_file(&root, &components[1..count], |file| { recurse_file(&root, &components[1..count], |file| {
file.seek_from_start(start_from as u32).unwrap(); file.seek_from_start(start_from as u32).unwrap();
file.write(buf).unwrap() file.write(&buf).unwrap()
}) })
.unwrap_or(()) .unwrap_or(())
}); });
@@ -324,7 +323,7 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
if !file.is_empty() { if !file.is_empty() {
let _ = sd.access_root_dir(|root| { sd.access_root_dir(|root| {
if let Ok(result) = recurse_file(&root, &file[1..], |file| file.length()) { if let Ok(result) = recurse_file(&root, &file[1..], |file| file.length()) {
len = result len = result
} }
@@ -332,36 +331,3 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
} }
len as usize len as usize
} }
const _: ReconfigureAudioSampleRate = reconfigure_audio_sample_rate;
pub extern "C" fn reconfigure_audio_sample_rate(sample_rate: u32) {
AUDIO_BUFFER_SAMPLE_RATE.store(sample_rate, Ordering::Release);
}
const _: AudioBufferReady = audio_buffer_ready;
pub extern "C" fn audio_buffer_ready() -> bool {
AUDIO_BUFFER_READY.load(Ordering::Acquire)
}
const _: SendAudioBuffer = send_audio_buffer;
pub extern "C" fn send_audio_buffer(ptr: *const u8, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
let buf = unsafe { core::slice::from_raw_parts(ptr, len) };
while !AUDIO_BUFFER_READY.load(Ordering::Acquire) {
core::hint::spin_loop();
}
if buf.len() == AUDIO_BUFFER_SAMPLES * 2 {
AUDIO_BUFFER_READY.store(false, Ordering::Release);
unsafe { AUDIO_BUFFER.copy_from_slice(buf) };
AUDIO_BUFFER_WRITTEN.store(true, Ordering::Release);
} else {
#[cfg(feature = "defmt")]
defmt::warn!(
"user audio stream was wrong size: {} should be {}",
buf.len(),
AUDIO_BUFFER_SAMPLES * 2
)
}
}

View File

@@ -1,204 +0,0 @@
use crate::Audio;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use embassy_futures::{join::join, yield_now};
use embassy_rp::{
Peri,
clocks::clk_sys_freq,
dma::{AnyChannel, Channel},
gpio::Level,
pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig,
StateMachine, program::pio_asm,
},
};
use fixed::traits::ToFixed;
pub const SAMPLE_RATE_HZ: u32 = 22_050;
const AUDIO_BUFFER_SAMPLES: usize = 1024;
const _: () = assert!(AUDIO_BUFFER_SAMPLES == userlib_sys::AUDIO_BUFFER_SAMPLES);
const SILENCE: u8 = u8::MAX / 2;
// 8bit stereo interleaved PCM audio buffers
pub static mut AUDIO_BUFFER: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2];
static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2];
// atomics for user applications to signal changes to audio buffers
pub static AUDIO_BUFFER_READY: AtomicBool = AtomicBool::new(true);
pub static AUDIO_BUFFER_WRITTEN: AtomicBool = AtomicBool::new(false);
pub static AUDIO_BUFFER_SAMPLE_RATE: AtomicU32 = AtomicU32::new(SAMPLE_RATE_HZ);
/// resets audio buffers after user applications are unloaded
pub fn clear_audio_buffers() {
unsafe {
AUDIO_BUFFER.fill(SILENCE);
AUDIO_BUFFER_1.fill(SILENCE);
}
}
#[embassy_executor::task]
pub async fn audio_handler(mut audio: Audio) {
let prg = PioPwmAudioProgram8Bit::new(&mut audio.pio);
let mut pwm_pio_left =
PioPwmAudio::new(audio.dma0, &mut audio.pio, audio.sm0, audio.left, &prg);
let mut pwm_pio_right =
PioPwmAudio::new(audio.dma1, &mut audio.pio, audio.sm1, audio.right, &prg);
// enable sms at the same time to ensure they are synced
audio.pio.apply_sm_batch(|pio| {
pio.set_enable(&mut pwm_pio_right.sm, true);
pio.set_enable(&mut pwm_pio_left.sm, true);
});
let mut sample_rate = SAMPLE_RATE_HZ;
loop {
unsafe {
let new_sample_rate = AUDIO_BUFFER_SAMPLE_RATE.load(Ordering::Acquire);
if new_sample_rate != sample_rate {
sample_rate = new_sample_rate;
pwm_pio_left.reconfigure(sample_rate);
pwm_pio_right.reconfigure(sample_rate);
// restart sms at the same time to ensure they are synced
audio.pio.apply_sm_batch(|pio| {
pio.restart(&mut pwm_pio_right.sm);
pio.restart(&mut pwm_pio_left.sm);
});
}
if AUDIO_BUFFER_WRITTEN.load(Ordering::Acquire) {
write_samples(&mut pwm_pio_left, &mut pwm_pio_right, &AUDIO_BUFFER_1).await;
AUDIO_BUFFER_1.fill(SILENCE);
core::mem::swap(&mut AUDIO_BUFFER, &mut AUDIO_BUFFER_1);
AUDIO_BUFFER_READY.store(true, Ordering::Release)
} else {
yield_now().await;
}
}
}
}
async fn write_samples<PIO: Instance>(
left: &mut PioPwmAudio<'static, PIO, 0>,
right: &mut PioPwmAudio<'static, PIO, 1>,
buf: &[u8],
) {
// pack two samples per word
static mut PACKED_BUF_L: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
static mut PACKED_BUF_R: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
unsafe {
for ((pl, pr), sample) in PACKED_BUF_L
.iter_mut()
.zip(PACKED_BUF_R.iter_mut())
.zip(buf.chunks(4))
{
*pl = pack_u8_samples(sample[0], sample[2]);
*pr = pack_u8_samples(sample[1], sample[3]);
}
let left_fut = left
.sm
.tx()
.dma_push(left.dma.reborrow(), &PACKED_BUF_L, false);
let right_fut = right
.sm
.tx()
.dma_push(right.dma.reborrow(), &PACKED_BUF_R, false);
join(left_fut, right_fut).await;
}
}
struct PioPwmAudioProgram8Bit<'d, PIO: Instance>(LoadedProgram<'d, PIO>);
/// Writes one sample to pwm as high and low time
impl<'d, PIO: Instance> PioPwmAudioProgram8Bit<'d, PIO> {
fn new(common: &mut Common<'d, PIO>) -> Self {
// only uses 16 bits top for pwm high, bottom for pwm low
// allows storing two samples per word
let prg = pio_asm!(
".side_set 1",
"set x, 0 side 1",
"set y, 0 side 0",
".wrap_target",
"check:",
"pull ifempty noblock side 1", // gets new osr or loads 0 into x, gets second sample if osr not empty
"out x, 8 side 0", // pwm high time
"out y, 8 side 1", // pwm low time
"jmp x!=y play_sample side 0", // x & y are never equal unless osr was empty
"set x, 1 side 1",
"set y, 1 side 0",
"play_sample:"
"loop_high:",
"jmp x-- loop_high side 1", // keep pwm high, decrement X until 0
"loop_low:",
"jmp y-- loop_low side 0", // keep pwm low, decrement Y until 0
".wrap"
);
let prg = common.load_program(&prg.program);
Self(prg)
}
}
struct PioPwmAudio<'d, PIO: Instance, const SM: usize> {
dma: Peri<'d, AnyChannel>,
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> {
fn get_sm_divider(sample_rate: u32) -> u32 {
let target_clock = (u8::MAX as u32 + 1) * sample_rate;
clk_sys_freq() / target_clock
}
fn new(
dma: Peri<'d, impl Channel>,
pio: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: Peri<'d, impl PioPin>,
prg: &PioPwmAudioProgram8Bit<'d, PIO>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.fifo_join = FifoJoin::TxOnly;
let shift_cfg = ShiftConfig {
auto_fill: false,
..Default::default()
};
cfg.shift_out = shift_cfg;
cfg.use_program(&prg.0, &[&pin]);
sm.set_config(&cfg);
sm.set_clock_divider(Self::get_sm_divider(SAMPLE_RATE_HZ).to_fixed());
Self {
dma: dma.into(),
sm,
}
}
fn reconfigure(&mut self, sample_rate: u32) {
self.sm
.set_clock_divider(Self::get_sm_divider(sample_rate).to_fixed());
}
}
/// packs two u8 samples into 32bit word
fn pack_u8_samples(sample1: u8, sample2: u8) -> u32 {
(u8_pcm_to_pwm(sample1) as u32) << 16 | u8_pcm_to_pwm(sample2) as u32
}
fn u8_pcm_to_pwm(sample: u8) -> u16 {
((sample as u16) << 8) | ((u8::MAX - sample) as u16)
}

View File

@@ -1,27 +1,27 @@
use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED};
use core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::Ordering; use core::sync::atomic::Ordering;
use embassy_futures::yield_now;
use embassy_rp::{ use embassy_rp::{
Peri, Peri,
gpio::{Level, Output}, gpio::{Level, Output},
peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
spi::{Async, Spi}, spi::{Async, Spi},
}; };
use embassy_time::Delay; use embassy_time::{Delay, Timer};
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::{DrawTarget, RgbColor},
};
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use st7365p_lcd::ST7365P; use st7365p_lcd::ST7365P;
#[cfg(feature = "psram")] #[cfg(feature = "psram")]
use crate::heap::HEAP; use crate::heap::HEAP;
#[cfg(feature = "psram")]
use core::alloc::{GlobalAlloc, Layout};
#[cfg(feature = "psram")]
use embedded_graphics::{draw_target::DrawTarget, pixelcolor::Rgb565, prelude::RgbColor};
#[cfg(feature = "fps")] #[cfg(feature = "fps")]
pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER}; pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER};
type Display = ST7365P< type DISPLAY = ST7365P<
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>, ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
Output<'static>, Output<'static>,
Output<'static>, Output<'static>,
@@ -58,7 +58,7 @@ pub async fn init_display(
cs: Peri<'static, PIN_13>, cs: Peri<'static, PIN_13>,
data: Peri<'static, PIN_14>, data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>, reset: Peri<'static, PIN_15>,
) -> Display { ) -> DISPLAY {
init_fb(); init_fb();
let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap();
@@ -86,7 +86,7 @@ pub async fn init_display(
} }
#[embassy_executor::task] #[embassy_executor::task]
pub async fn display_handler(mut display: Display) { pub async fn display_handler(mut display: DISPLAY) {
use embassy_time::{Instant, Timer}; use embassy_time::{Instant, Timer};
// Target ~60 Hz refresh (≈16.67 ms per frame) // Target ~60 Hz refresh (≈16.67 ms per frame)
@@ -113,11 +113,11 @@ pub async fn display_handler(mut display: Display) {
} }
} }
let elapsed = start.elapsed().as_millis(); let elapsed = start.elapsed().as_millis() as u64;
if elapsed < FRAME_TIME_MS { if elapsed < FRAME_TIME_MS {
Timer::after_millis(FRAME_TIME_MS - elapsed).await; Timer::after_millis(FRAME_TIME_MS - elapsed).await;
} else { } else {
yield_now().await; Timer::after_millis(1).await;
} }
} }
} }

View File

@@ -1,52 +1,46 @@
use crate::{ use crate::{
abi,
storage::{File, SDCARD}, storage::{File, SDCARD},
syscalls,
}; };
use abi_sys::CallTable;
use abi_sys::EntryFn;
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use bumpalo::Bump; use bumpalo::Bump;
use core::ptr; use core::ptr;
use embedded_sdmmc::ShortFileName; use embedded_sdmmc::ShortFileName;
use goblin::{ use goblin::{
elf::{ elf::{
header::{EM_ARM, ET_DYN, EV_CURRENT, header32::Header}, header::header32::Header,
program_header::program_header32::{PT_LOAD, ProgramHeader}, program_header::program_header32::{PT_LOAD, ProgramHeader},
reloc::R_ARM_RELATIVE, reloc::R_ARM_RELATIVE,
section_header::{SHT_REL, SHT_SYMTAB}, section_header::{SHT_REL, SHT_SYMTAB},
}, },
elf32::{reloc::Rel, section_header::SectionHeader, sym::Sym}, elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym},
}; };
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use userlib_sys::{EntryFn, SyscallTable};
const ELF32_HDR_SIZE: usize = 52; const ELF32_HDR_SIZE: usize = 52;
#[derive(Debug)] pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> {
pub enum LoadError {
WrongMachine,
InvalidElf,
FailedToReadFile,
ElfIsNotPie,
UnknownRelocationType,
SyscallTableNotFound,
}
pub async unsafe fn load_binary(name: &ShortFileName) -> Result<(EntryFn, Bump), LoadError> {
let mut sd_lock = SDCARD.get().lock().await; let mut sd_lock = SDCARD.get().lock().await;
let sd = sd_lock.as_mut().expect("Sdcard locked"); let sd = sd_lock.as_mut().unwrap();
let mut header_buf = [0; ELF32_HDR_SIZE]; let mut header_buf = [0; ELF32_HDR_SIZE];
sd.read_file(name, |mut file| { let (entry, bump) = sd
file.read(&mut header_buf) .read_file(name, |mut file| {
.map_err(|_| LoadError::FailedToReadFile)?; file.read(&mut header_buf).unwrap();
let elf_header = Header::from_bytes(&header_buf); let elf_header = Header::from_bytes(&header_buf);
validate_header(&elf_header)?; // reject non-PIE
if elf_header.e_type != header::ET_DYN {
return None;
}
let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize]; let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize];
let (total_size, min_vaddr, _max_vaddr) = let (total_size, min_vaddr, _max_vaddr) =
total_loadable_size(&mut file, elf_header, &mut ph_buf)?; total_loadable_size(&mut file, &elf_header, &mut ph_buf);
let bump = Bump::with_capacity(total_size); let bump = Bump::with_capacity(total_size);
let base = bump.alloc_slice_fill_default::<u8>(total_size); let base = bump.alloc_slice_fill_default::<u8>(total_size);
@@ -54,54 +48,45 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result<(EntryFn, Bump),
// load each segment into bump, relative to base_ptr // load each segment into bump, relative to base_ptr
for i in 0..elf_header.e_phnum { for i in 0..elf_header.e_phnum {
file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32)
.map_err(|_| LoadError::FailedToReadFile)?; .unwrap();
file.read(&mut ph_buf) file.read(&mut ph_buf).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?;
let ph = cast_phdr(&ph_buf); let ph = cast_phdr(&ph_buf);
let seg_offset = (ph.p_vaddr - min_vaddr) as usize; let seg_offset = (ph.p_vaddr - min_vaddr) as usize;
let segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize]; let mut segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize];
if ph.p_type == PT_LOAD { if ph.p_type == PT_LOAD {
load_segment(&mut file, &ph, segment)?; load_segment(&mut file, &ph, &mut segment).unwrap();
} }
} }
for i in 0..elf_header.e_shnum { for i in 0..elf_header.e_shnum {
let sh = read_section(&mut file, elf_header, i.into())?; let sh = read_section(&mut file, elf_header, i.into());
if sh.sh_type == SHT_REL { match sh.sh_type {
apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file)?; SHT_REL => {
apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file).unwrap();
}
_ => {}
} }
} }
patch_syscalls(elf_header, base.as_mut_ptr(), min_vaddr, &mut file)?; patch_abi(&elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap();
// entry pointer is base_ptr + (entry - min_vaddr) // entry pointer is base_ptr + (entry - min_vaddr)
let entry_ptr: EntryFn = unsafe { let entry_ptr: EntryFn = unsafe {
core::mem::transmute(base.as_ptr().add((elf_header.e_entry - min_vaddr) as usize)) core::mem::transmute(base.as_ptr().add((elf_header.e_entry - min_vaddr) as usize))
}; };
Ok((entry_ptr, bump)) Some((entry_ptr, bump))
}) })
.await .await
.map_err(|_| LoadError::FailedToReadFile)? .expect("Failed to read file")?;
Some((entry, bump))
} }
fn validate_header(h: &Header) -> Result<(), LoadError> { fn load_segment(file: &mut File, ph: &ProgramHeader, segment: &mut [u8]) -> Result<(), ()> {
if h.e_type != ET_DYN {
return Err(LoadError::ElfIsNotPie);
}
if h.e_machine != EM_ARM {
return Err(LoadError::WrongMachine);
}
if h.e_version as u8 != EV_CURRENT {
return Err(LoadError::InvalidElf);
}
Ok(())
}
fn load_segment(file: &mut File, ph: &ProgramHeader, segment: &mut [u8]) -> Result<(), LoadError> {
let filesz = ph.p_filesz as usize; let filesz = ph.p_filesz as usize;
let memsz = ph.p_memsz as usize; let memsz = ph.p_memsz as usize;
@@ -113,10 +98,8 @@ fn load_segment(file: &mut File, ph: &ProgramHeader, segment: &mut [u8]) -> Resu
while remaining > 0 { while remaining > 0 {
let to_read = core::cmp::min(remaining, buf.len()); let to_read = core::cmp::min(remaining, buf.len());
file.seek_from_start(file_offset) file.seek_from_start(file_offset).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?; file.read(&mut buf[..to_read]).unwrap();
file.read(&mut buf[..to_read])
.map_err(|_| LoadError::FailedToReadFile)?;
segment[dst_offset..dst_offset + to_read].copy_from_slice(&buf[..to_read]); segment[dst_offset..dst_offset + to_read].copy_from_slice(&buf[..to_read]);
@@ -138,16 +121,14 @@ fn apply_relocations(
min_vaddr: u32, min_vaddr: u32,
base: *mut u8, base: *mut u8,
file: &mut File, file: &mut File,
) -> Result<(), LoadError> { ) -> Result<(), ()> {
let mut reloc = [0_u8; 8]; let mut reloc = [0_u8; 8];
let num_relocs = sh.sh_size as usize / sh.sh_entsize as usize; let num_relocs = sh.sh_size as usize / sh.sh_entsize as usize;
for i in 0..num_relocs { for i in 0..num_relocs {
file.seek_from_start(sh.sh_offset + (i as u32 * 8)) file.seek_from_start(sh.sh_offset + (i as u32 * 8)).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?; file.read(&mut reloc).unwrap();
file.read(&mut reloc)
.map_err(|_| LoadError::FailedToReadFile)?;
let rel = cast_rel(&reloc); let rel = cast_rel(&reloc);
@@ -163,29 +144,27 @@ fn apply_relocations(
} }
} }
_ => { _ => {
return Err(LoadError::UnknownRelocationType); return Err(());
} }
} }
} }
Ok(()) Ok(())
} }
fn patch_syscalls( fn patch_abi(
elf_header: &Header, elf_header: &Header,
base: *mut u8, base: *mut u8,
min_vaddr: u32, min_vaddr: u32,
file: &mut File, file: &mut File,
) -> Result<(), LoadError> { ) -> Result<(), ()> {
for i in 1..=elf_header.e_shnum { for i in 1..=elf_header.e_shnum {
let sh = read_section(file, elf_header, i.into())?; let sh = read_section(file, &elf_header, i.into());
// find the symbol table // find the symbol table
if sh.sh_type == SHT_SYMTAB { if sh.sh_type == SHT_SYMTAB {
let mut symtab_buf = vec![0u8; sh.sh_size as usize]; let mut symtab_buf = vec![0u8; sh.sh_size as usize];
file.seek_from_start(sh.sh_offset) file.seek_from_start(sh.sh_offset).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?; file.read(&mut symtab_buf).unwrap();
file.read(&mut symtab_buf)
.map_err(|_| LoadError::FailedToReadFile)?;
// Cast buffer into symbols // Cast buffer into symbols
let sym_count = sh.sh_size as usize / sh.sh_entsize as usize; let sym_count = sh.sh_size as usize / sh.sh_entsize as usize;
@@ -194,50 +173,44 @@ fn patch_syscalls(
&symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize]; &symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize];
let sym = cast_sym(sym_bytes); let sym = cast_sym(sym_bytes);
let str_sh = read_section(file, elf_header, sh.sh_link)?; let str_sh = read_section(file, &elf_header, sh.sh_link);
let mut name = Vec::new(); let mut name = Vec::new();
file.seek_from_start(str_sh.sh_offset + sym.st_name) file.seek_from_start(str_sh.sh_offset + sym.st_name)
.map_err(|_| LoadError::FailedToReadFile)?; .unwrap();
loop { loop {
let mut byte = [0u8; 1]; let mut byte = [0u8; 1];
file.read(&mut byte) file.read(&mut byte).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?;
if byte[0] == 0 { if byte[0] == 0 {
break; break;
} }
name.push(byte[0]); name.push(byte[0]);
} }
let symbol_name = core::str::from_utf8(&name).expect("symbol was not utf8"); let symbol_name = core::str::from_utf8(&name).unwrap();
if symbol_name == stringify!(SYS_CALL_TABLE) { if symbol_name == "CALL_ABI_TABLE" {
let table_base = let table_base =
unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) }
as *mut usize; as *mut usize;
for (idx, call) in SyscallTable::iter().enumerate() { for (idx, call) in CallTable::iter().enumerate() {
let ptr = match call { let ptr = match call {
SyscallTable::Alloc => syscalls::alloc as usize, CallTable::Alloc => abi::alloc as usize,
SyscallTable::Dealloc => syscalls::dealloc as usize, CallTable::Dealloc => abi::dealloc as usize,
SyscallTable::PrintString => syscalls::print as usize, CallTable::PrintString => abi::print as usize,
SyscallTable::SleepMs => syscalls::sleep as usize, CallTable::SleepMs => abi::sleep as usize,
SyscallTable::GetMs => syscalls::get_ms as usize, CallTable::GetMs => abi::get_ms as usize,
SyscallTable::DrawIter => syscalls::draw_iter as usize, CallTable::DrawIter => abi::draw_iter as usize,
SyscallTable::GetKey => syscalls::get_key as usize, CallTable::GetKey => abi::get_key as usize,
SyscallTable::GenRand => syscalls::gen_rand as usize, CallTable::GenRand => abi::gen_rand as usize,
SyscallTable::ListDir => syscalls::list_dir as usize, CallTable::ListDir => abi::list_dir as usize,
SyscallTable::ReadFile => syscalls::read_file as usize, CallTable::ReadFile => abi::read_file as usize,
SyscallTable::WriteFile => syscalls::write_file as usize, CallTable::WriteFile => abi::write_file as usize,
SyscallTable::FileLen => syscalls::file_len as usize, CallTable::FileLen => abi::file_len as usize,
SyscallTable::ReconfigureAudioSampleRate => {
syscalls::reconfigure_audio_sample_rate as usize
}
SyscallTable::AudioBufferReady => syscalls::audio_buffer_ready as usize,
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
}; };
unsafe { unsafe {
table_base.add(idx).write(ptr); table_base.add(idx as usize).write(ptr);
} }
} }
return Ok(()); return Ok(());
@@ -245,21 +218,21 @@ fn patch_syscalls(
} }
} }
} }
Err(LoadError::SyscallTableNotFound) Err(())
} }
fn total_loadable_size( fn total_loadable_size(
file: &mut File, file: &mut File,
elf_header: &Header, elf_header: &Header,
ph_buf: &mut [u8], ph_buf: &mut [u8],
) -> Result<(usize, u32, u32), LoadError> { ) -> (usize, u32, u32) {
let mut min_vaddr = u32::MAX; let mut min_vaddr = u32::MAX;
let mut max_vaddr = 0u32; let mut max_vaddr = 0u32;
for i in 0..elf_header.e_phnum { for i in 0..elf_header.e_phnum {
file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32)
.map_err(|_| LoadError::FailedToReadFile)?; .unwrap();
file.read(ph_buf).map_err(|_| LoadError::FailedToReadFile)?; file.read(ph_buf).unwrap();
let ph = cast_phdr(ph_buf); let ph = cast_phdr(&ph_buf);
if ph.p_type == PT_LOAD { if ph.p_type == PT_LOAD {
if ph.p_vaddr < min_vaddr { if ph.p_vaddr < min_vaddr {
@@ -272,22 +245,17 @@ fn total_loadable_size(
} }
let total_size = (max_vaddr - min_vaddr) as usize; let total_size = (max_vaddr - min_vaddr) as usize;
Ok((total_size, min_vaddr, max_vaddr)) (total_size, min_vaddr, max_vaddr)
} }
fn read_section( fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHeader {
file: &mut File,
elf_header: &Header,
section: u32,
) -> Result<SectionHeader, LoadError> {
let mut sh_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)) file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize as u32 * section))
.map_err(|_| LoadError::FailedToReadFile)?; .unwrap();
file.read(&mut sh_buf) file.read(&mut sh_buf).unwrap();
.map_err(|_| LoadError::FailedToReadFile)?;
Ok(cast_shdr(&sh_buf)) cast_shdr(&sh_buf)
} }
fn cast_phdr(buf: &[u8]) -> ProgramHeader { fn cast_phdr(buf: &[u8]) -> ProgramHeader {

View File

@@ -46,7 +46,7 @@ impl<'a> AtomicFrameBuffer<'a> {
} }
fn mark_tiles_dirty(&mut self, rect: Rectangle) { fn mark_tiles_dirty(&mut self, rect: Rectangle) {
let tiles_x = SCREEN_WIDTH.div_ceil(TILE_SIZE); let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
let start_tx = (rect.top_left.x as usize) / 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 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 start_ty = (rect.top_left.y as usize) / TILE_SIZE;
@@ -261,15 +261,14 @@ impl<'a> AtomicFrameBuffer<'a> {
// Check for dirty tile // Check for dirty tile
if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) { if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) {
let run_start = col; let run_start = col;
let mut scan_col = col;
let mut run_len = 1; let mut run_len = 1;
// Extend run while contiguous dirty tiles and within MAX_BATCH_TILES // Extend run while contiguous dirty tiles and within MAX_BATCH_TILES
while scan_col + 1 < NUM_TILE_COLS while col + 1 < NUM_TILE_COLS
&& self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire) && self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire)
&& run_len < MAX_BATCH_TILES && run_len < MAX_BATCH_TILES
{ {
scan_col += 1; col += 1;
run_len += 1; run_len += 1;
} }
@@ -294,8 +293,6 @@ impl<'a> AtomicFrameBuffer<'a> {
&self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE],
) )
.await?; .await?;
col = scan_col;
} }
col += 1; col += 1;
@@ -349,9 +346,11 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
} }
} }
if changed && let Some(rect) = dirty_rect { if changed {
if let Some(rect) = dirty_rect {
self.mark_tiles_dirty(rect); self.mark_tiles_dirty(rect);
} }
}
Ok(()) Ok(())
} }
@@ -403,7 +402,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
self.fill_contiguous( self.fill_contiguous(
area, area,
core::iter::repeat_n(color, (self.size().width * self.size().height) as usize), core::iter::repeat(color).take((self.size().width * self.size().height) as usize),
) )
} }
@@ -413,10 +412,8 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
0, 0,
self.size().width as u16 - 1, self.size().width as u16 - 1,
self.size().height as u16 - 1, self.size().height as u16 - 1,
core::iter::repeat_n( core::iter::repeat(RawU16::from(color).into_inner())
RawU16::from(color).into_inner(), .take((self.size().width * self.size().height) as usize),
(self.size().width * self.size().height) as usize,
),
)?; )?;
for tile in self.dirty_tiles.iter() { for tile in self.dirty_tiles.iter() {

View File

@@ -22,8 +22,8 @@ impl Region {
fn contains(&self, address: usize) -> bool { fn contains(&self, address: usize) -> bool {
let start = self.start.load(Ordering::Relaxed); let start = self.start.load(Ordering::Relaxed);
let end = start + self.size.load(Ordering::Relaxed); let end = self.start.load(Ordering::Relaxed);
(start..end).contains(&address) (start..start + end).contains(&address)
} }
fn new(start: usize, size: usize) -> Self { fn new(start: usize, size: usize) -> Self {

View File

@@ -5,25 +5,17 @@
#![allow(static_mut_refs)] #![allow(static_mut_refs)]
#![feature(allocator_api)] #![feature(allocator_api)]
#![feature(slice_ptr_get)] #![feature(slice_ptr_get)]
#![deny(warnings)]
#![deny(clippy::redundant_clone)]
#![deny(clippy::unwrap_used)]
extern crate alloc; extern crate alloc;
mod audio; mod abi;
mod display; mod display;
mod elf; mod elf;
mod framebuffer; mod framebuffer;
// TODO: NEED TO UPDATE MCU TO TEST BATTERY READS
#[allow(unused)]
mod peripherals; mod peripherals;
#[allow(unused)]
mod scsi; mod scsi;
mod storage; mod storage;
mod syscalls;
mod ui; mod ui;
#[allow(unused)]
mod usb; mod usb;
mod utils; mod utils;
@@ -35,17 +27,20 @@ mod heap;
mod psram; mod psram;
#[cfg(feature = "psram")] #[cfg(feature = "psram")]
use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi}; use crate::{heap::HEAP, heap::init_qmi_psram_heap, psram::init_psram, psram::init_psram_qmi};
use crate::{ use crate::{
audio::{AUDIO_BUFFER_WRITTEN, audio_handler, clear_audio_buffers}, abi::{KEY_CACHE, MS_SINCE_LAUNCH},
display::{FRAMEBUFFER, display_handler, init_display}, display::{FRAMEBUFFER, display_handler, init_display},
peripherals::{conf_peripherals, keyboard::read_keyboard_fifo}, peripherals::{
conf_peripherals,
keyboard::{KeyState, read_keyboard_fifo},
},
scsi::MSC_SHUTDOWN, scsi::MSC_SHUTDOWN,
storage::{SDCARD, SdCard}, storage::{SDCARD, SdCard},
syscalls::{KEY_CACHE, MS_SINCE_LAUNCH},
ui::{SELECTIONS, clear_selection, ui_handler}, ui::{SELECTIONS, clear_selection, ui_handler},
}; };
use abi_sys::EntryFn;
use bumpalo::Bump; use bumpalo::Bump;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use embassy_executor::{Executor, Spawner}; use embassy_executor::{Executor, Spawner};
@@ -60,9 +55,9 @@ use embassy_rp::{
peripherals::{ peripherals::{
DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, 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_20, PIN_21, PIN_22, PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22,
PIN_26, PIN_27, PIO0, SPI0, SPI1, USB, WATCHDOG, PIO0, SPI0, SPI1, USB, WATCHDOG,
}, },
pio::{self, Common, Pio, StateMachine}, pio,
spi::{self, Spi}, spi::{self, Spi},
usb as embassy_rp_usb, usb as embassy_rp_usb,
watchdog::{ResetReason, Watchdog}, watchdog::{ResetReason, Watchdog},
@@ -79,7 +74,6 @@ use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::SdCard as SdmmcSdCard; use embedded_sdmmc::SdCard as SdmmcSdCard;
use static_cell::StaticCell; use static_cell::StaticCell;
use talc::*; use talc::*;
use userlib_sys::EntryFn;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
embassy_rp::bind_interrupts!(struct Irqs { embassy_rp::bind_interrupts!(struct Irqs {
@@ -109,7 +103,7 @@ async fn watchdog_task(mut watchdog: Watchdog) {
ResetReason::Forced => "forced", ResetReason::Forced => "forced",
ResetReason::TimedOut => "timed out", ResetReason::TimedOut => "timed out",
}; };
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::error!("Watchdog reset reason: {}", _reason); defmt::error!("Watchdog reset reason: {}", _reason);
} }
@@ -125,12 +119,10 @@ async fn watchdog_task(mut watchdog: Watchdog) {
static ENABLE_UI: AtomicBool = AtomicBool::new(true); static ENABLE_UI: AtomicBool = AtomicBool::new(true);
static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new(); static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
const OVERCLOCK: u32 = 300_000_000;
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = if cfg!(feature = "overclock") { let p = if cfg!(feature = "overclock") {
let clocks = ClockConfig::system_freq(OVERCLOCK).unwrap(); let clocks = ClockConfig::system_freq(300_000_000).unwrap();
let config = Config::new(clocks); let config = Config::new(clocks);
embassy_rp::init(config) embassy_rp::init(config)
} else { } else {
@@ -157,19 +149,6 @@ async fn main(_spawner: Spawner) {
data: p.PIN_14, data: p.PIN_14,
reset: p.PIN_15, reset: p.PIN_15,
}; };
let Pio {
common, sm0, sm1, ..
} = Pio::new(p.PIO0, Irqs);
let audio = Audio {
pio: common,
sm0,
dma0: p.DMA_CH3,
left: p.PIN_26,
sm1,
dma1: p.DMA_CH4,
right: p.PIN_27,
};
let sd = Sd { let sd = Sd {
spi: p.SPI0, spi: p.SPI0,
clk: p.PIN_18, clk: p.PIN_18,
@@ -178,15 +157,15 @@ async fn main(_spawner: Spawner) {
cs: p.PIN_17, cs: p.PIN_17,
det: p.PIN_22, det: p.PIN_22,
}; };
// let psram = Psram { let psram = Psram {
// pio: p.PIO0, pio: p.PIO0,
// sclk: p.PIN_21, sclk: p.PIN_21,
// mosi: p.PIN_2, mosi: p.PIN_2,
// miso: p.PIN_3, miso: p.PIN_3,
// cs: p.PIN_20, cs: p.PIN_20,
// dma1: p.DMA_CH3, dma1: p.DMA_CH3,
// dma2: p.DMA_CH4, dma2: p.DMA_CH4,
// }; };
let mcu = Mcu { let mcu = Mcu {
i2c: p.I2C1, i2c: p.I2C1,
clk: p.PIN_7, clk: p.PIN_7,
@@ -196,7 +175,7 @@ async fn main(_spawner: Spawner) {
executor0.run(|spawner| { executor0.run(|spawner| {
spawner spawner
.spawn(kernel_task( .spawn(kernel_task(
spawner, p.WATCHDOG, display, audio, sd, mcu, p.USB, spawner, p.WATCHDOG, display, sd, psram, mcu, p.USB,
)) ))
.unwrap() .unwrap()
}); });
@@ -229,9 +208,6 @@ async fn userland_task() {
// enable kernel ui // enable kernel ui
{ {
AUDIO_BUFFER_WRITTEN.store(false, Ordering::Release);
clear_audio_buffers();
ENABLE_UI.store(true, Ordering::Release); ENABLE_UI.store(true, Ordering::Release);
UI_CHANGE.signal(()); UI_CHANGE.signal(());
unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() }; unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() };
@@ -253,15 +229,6 @@ struct Display {
data: Peri<'static, PIN_14>, data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>, reset: Peri<'static, PIN_15>,
} }
struct Audio {
pio: Common<'static, PIO0>,
dma0: Peri<'static, DMA_CH3>,
sm0: StateMachine<'static, PIO0, 0>,
left: Peri<'static, PIN_26>,
dma1: Peri<'static, DMA_CH4>,
sm1: StateMachine<'static, PIO0, 1>,
right: Peri<'static, PIN_27>,
}
struct Sd { struct Sd {
spi: Peri<'static, SPI0>, spi: Peri<'static, SPI0>,
clk: Peri<'static, PIN_18>, clk: Peri<'static, PIN_18>,
@@ -331,7 +298,7 @@ async fn setup_display(display: Display, spawner: Spawner) {
async fn setup_qmi_psram() { async fn setup_qmi_psram() {
Timer::after_millis(250).await; Timer::after_millis(250).await;
let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL);
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::info!("size: {}", psram_qmi_size); defmt::info!("size: {}", psram_qmi_size);
Timer::after_millis(100).await; Timer::after_millis(100).await;
@@ -363,9 +330,8 @@ async fn kernel_task(
spawner: Spawner, spawner: Spawner,
watchdog: Peri<'static, WATCHDOG>, watchdog: Peri<'static, WATCHDOG>,
display: Display, display: Display,
audio: Audio,
sd: Sd, sd: Sd,
// _psram: Psram, _psram: Psram,
mcu: Mcu, mcu: Mcu,
usb: Peri<'static, USB>, usb: Peri<'static, USB>,
) { ) {
@@ -373,7 +339,7 @@ async fn kernel_task(
.spawn(watchdog_task(Watchdog::new(watchdog))) .spawn(watchdog_task(Watchdog::new(watchdog)))
.unwrap(); .unwrap();
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq()); defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq());
setup_mcu(mcu).await; setup_mcu(mcu).await;
@@ -390,8 +356,6 @@ async fn kernel_task(
setup_display(display, spawner).await; setup_display(display, spawner).await;
setup_sd(sd).await; setup_sd(sd).await;
spawner.spawn(audio_handler(audio)).unwrap();
let _usb = embassy_rp_usb::Driver::new(usb, Irqs); let _usb = embassy_rp_usb::Driver::new(usb, Irqs);
// spawner.spawn(usb_handler(usb)).unwrap(); // spawner.spawn(usb_handler(usb)).unwrap();

View File

@@ -1,5 +1,5 @@
use crate::peripherals::PERIPHERAL_BUS; use crate::peripherals::PERIPHERAL_BUS;
pub use userlib_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; pub use abi_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
const REG_ID_KEY: u8 = 0x04; const REG_ID_KEY: u8 = 0x04;
const REG_ID_FIF: u8 = 0x09; const REG_ID_FIF: u8 = 0x09;

View File

@@ -526,7 +526,7 @@ pub fn init_psram_qmi(
let psram_size = detect_psram_qmi(qmi); let psram_size = detect_psram_qmi(qmi);
if psram_size == 0 { if psram_size == 0 {
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::error!("qmi psram size 0"); defmt::error!("qmi psram size 0");
return 0; return 0;
} }

View File

@@ -30,7 +30,7 @@ pub struct MassStorageClass<'d, D: Driver<'d>> {
bulk_in: D::EndpointIn, bulk_in: D::EndpointIn,
} }
impl<'d, D: Driver<'d>> MassStorageClass<'d, D> { impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
pub fn new(builder: &mut Builder<'d, D>, temp_sd: Option<SdCard>) -> Self { pub fn new(builder: &mut Builder<'d, D>, temp_sd: Option<SdCard>) -> Self {
let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class
let mut interface = function.interface(); let mut interface = function.interface();
@@ -72,10 +72,9 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
async fn handle_cbw(&mut self) { async fn handle_cbw(&mut self) {
let mut cbw_buf = [0u8; 31]; let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
&& n == 31 if n == 31 {
&& let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
{
// Take sdcard to increase speed // Take sdcard to increase speed
if self.temp_sd.is_none() { if self.temp_sd.is_none() {
let mut guard = SDCARD.get().lock().await; let mut guard = SDCARD.get().lock().await;
@@ -95,13 +94,15 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
self.send_csw_fail(cbw.dCBWTag).await self.send_csw_fail(cbw.dCBWTag).await
} }
if self.pending_eject if self.pending_eject {
&& let ScsiCommand::Write { lba: _, len: _ } = command if let ScsiCommand::Write { lba: _, len: _ } = command {
{
MSC_SHUTDOWN.signal(()); MSC_SHUTDOWN.signal(());
} }
} }
} }
}
}
}
async fn handle_command(&mut self, command: ScsiCommand) -> Result<(), ()> { async fn handle_command(&mut self, command: ScsiCommand) -> Result<(), ()> {
let mut response: Vec<u8, BULK_ENDPOINT_PACKET_SIZE> = Vec::new(); let mut response: Vec<u8, BULK_ENDPOINT_PACKET_SIZE> = Vec::new();
@@ -237,7 +238,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64; let block_size = SdCard::BLOCK_SIZE as u64;
let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size;
let last_lba = total_blocks.saturating_sub(1); 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(&(last_lba as u32).to_be_bytes())?;
response.extend_from_slice(&(block_size as u32).to_be_bytes())?; response.extend_from_slice(&(block_size as u32).to_be_bytes())?;
@@ -248,7 +249,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64; let block_size = SdCard::BLOCK_SIZE as u64;
let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size;
let last_lba = total_blocks.saturating_sub(1); 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(&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(&(block_size as u32).to_be_bytes())?; // 4 bytes block length
@@ -268,7 +269,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?; sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?;
for block in &mut *block_buf { for block in &mut *block_buf {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) {
self.bulk_in.write(chunk).await.map_err(|_| ())?; self.bulk_in.write(chunk).await.map_err(|_| ())?;
} }
} }
@@ -280,7 +281,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
.read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?; .read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?;
for block in &block_buf[..blocks as usize] { for block in &block_buf[..blocks as usize] {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) {
self.bulk_in.write(chunk).await.map_err(|_| ())?; self.bulk_in.write(chunk).await.map_err(|_| ())?;
} }
} }
@@ -300,7 +301,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
while blocks > 0 { while blocks > 0 {
if blocks >= block_buf.len() as u64 { if blocks >= block_buf.len() as u64 {
for block in block_buf.as_mut() { for block in block_buf.as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
self.bulk_out.read(chunk).await.map_err(|_| ())?; self.bulk_out.read(chunk).await.map_err(|_| ())?;
} }
} }
@@ -311,7 +313,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
idx += block_buf.len() as u64; idx += block_buf.len() as u64;
} else { } else {
for block in block_buf[..blocks as usize].as_mut() { for block in block_buf[..blocks as usize].as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
self.bulk_out.read(chunk).await.map_err(|_| ())?; self.bulk_out.read(chunk).await.map_err(|_| ())?;
} }
} }

View File

@@ -1,7 +1,7 @@
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
/// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs /// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs
///
/// SCSI device subclass code /// SCSI device subclass code
pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set
@@ -91,7 +91,6 @@ pub enum ScsiCommand {
}, },
} }
#[allow(clippy::enum_variant_names)]
#[repr(u8)] #[repr(u8)]
#[derive(Copy, Clone, Debug, TryFromPrimitive)] #[derive(Copy, Clone, Debug, TryFromPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]

View File

@@ -9,8 +9,8 @@ use embassy_sync::mutex::Mutex;
use embassy_time::Delay; use embassy_time::Delay;
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{ use embedded_sdmmc::{
Block, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp, Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource,
VolumeIdx, VolumeManager, sdcard::Error, Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error,
}; };
use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName}; use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName};
@@ -21,6 +21,7 @@ pub const MAX_VOLUMES: usize = 1;
type Device = ExclusiveDevice<Spi<'static, SPI0, Blocking>, Output<'static>, embassy_time::Delay>; type Device = ExclusiveDevice<Spi<'static, SPI0, Blocking>, Output<'static>, embassy_time::Delay>;
type SD = SdmmcSdCard<Device, Delay>; type SD = SdmmcSdCard<Device, Delay>;
type VolMgr = VolumeManager<SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; type VolMgr = VolumeManager<SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
type Vol<'a> = Volume<'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 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 type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
@@ -42,7 +43,7 @@ pub struct FileName {
impl PartialOrd for FileName { impl PartialOrd for FileName {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other)) Some(self.long_name.cmp(&other.long_name))
} }
} }
@@ -52,13 +53,6 @@ impl Ord for FileName {
} }
} }
#[derive(Debug)]
pub enum SdCardError {
Volume0Missing,
RootDirMissing,
FileOpenFailed,
}
pub struct SdCard { pub struct SdCard {
det: Input<'static>, det: Input<'static>,
volume_mgr: VolMgr, volume_mgr: VolMgr,
@@ -73,7 +67,10 @@ impl SdCard {
DummyTimeSource {}, DummyTimeSource {},
5000, 5000,
); );
Self { det, volume_mgr } Self {
det: det,
volume_mgr,
}
} }
/// Returns true if an SD card is inserted. /// Returns true if an SD card is inserted.
@@ -93,6 +90,17 @@ impl SdCard {
result 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<(), ()> { pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> {
let mut res: Result<(), Error> = Ok(()); let mut res: Result<(), Error> = Ok(());
self.volume_mgr.device(|sd| { self.volume_mgr.device(|sd| {
@@ -111,37 +119,30 @@ impl SdCard {
res.map_err(|_| ()) res.map_err(|_| ())
} }
pub fn access_root_dir<R>( pub fn access_root_dir(&mut self, mut access: impl FnMut(Dir)) {
&mut self, let volume0 = self.volume_mgr.open_volume(VolumeIdx(0)).unwrap();
mut access: impl FnMut(Dir) -> R, let root_dir = volume0.open_root_dir().unwrap();
) -> Result<R, SdCardError> {
let volume0 = self
.volume_mgr
.open_volume(VolumeIdx(0))
.map_err(|_| SdCardError::Volume0Missing)?;
let root_dir = volume0
.open_root_dir()
.map_err(|_| SdCardError::RootDirMissing)?;
Ok(access(root_dir)) access(root_dir);
} }
pub async fn read_file<R>( pub async fn read_file<T>(
&mut self, &mut self,
name: &ShortFileName, name: &ShortFileName,
mut access: impl FnMut(File) -> R, mut access: impl FnMut(File) -> T,
) -> Result<R, SdCardError> { ) -> Result<T, ()> {
let mut res = Err(());
self.access_root_dir(|root_dir| { self.access_root_dir(|root_dir| {
let file = root_dir if let Ok(file) = root_dir.open_file_in_dir(name, Mode::ReadOnly) {
.open_file_in_dir(name, Mode::ReadOnly) res = Ok(access(file));
.map_err(|_| SdCardError::FileOpenFailed)?; }
});
Ok(access(file)) res
})?
} }
/// Returns a Vec of file names (long format) that match the given extension (e.g., "BIN") /// 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<Vec<FileName>, SdCardError> { pub fn list_files_by_extension(&mut self, ext: &str) -> Result<Vec<FileName>, ()> {
let mut result = Vec::new(); let mut result = Vec::new();
// Only proceed if card is inserted // Only proceed if card is inserted
@@ -165,7 +166,7 @@ impl SdCard {
} }
}) })
.unwrap() .unwrap()
})?; });
Ok(result) Ok(result)
} }

View File

@@ -2,6 +2,7 @@ use crate::{
BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED, BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED,
peripherals::keyboard, storage::FileName, peripherals::keyboard, storage::FileName,
}; };
use abi_sys::keyboard::{KeyCode, KeyState};
use alloc::{str::FromStr, string::String, vec::Vec}; use alloc::{str::FromStr, string::String, vec::Vec};
use core::sync::atomic::Ordering; use core::sync::atomic::Ordering;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
@@ -19,16 +20,14 @@ use embedded_layout::{
prelude::*, prelude::*,
}; };
use embedded_text::TextBox; use embedded_text::TextBox;
use userlib_sys::keyboard::{KeyCode, KeyState};
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> = pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
Mutex::new(SelectionList::new()); Mutex::new(SelectionList::new());
pub async fn ui_handler() { pub async fn ui_handler() {
loop { loop {
if let Some(event) = keyboard::read_keyboard_fifo().await if let Some(event) = keyboard::read_keyboard_fifo().await {
&& let KeyState::Pressed = event.state if let KeyState::Pressed = event.state {
{
match event.key { match event.key {
KeyCode::Up => { KeyCode::Up => {
let mut selections = SELECTIONS.lock().await; let mut selections = SELECTIONS.lock().await;
@@ -44,16 +43,16 @@ pub async fn ui_handler() {
selections.selections[selections.current_selection as usize].clone(); selections.selections[selections.current_selection as usize].clone();
let entry = unsafe { let entry = unsafe {
match load_binary(&selection.short_name).await { load_binary(&selection.short_name)
Ok(entry) => entry, .await
Err(e) => panic!("unable to load binary: {:?}", e), .expect("unable to load binary")
}
}; };
BINARY_CH.send(entry).await; BINARY_CH.send(entry).await;
} }
_ => (), _ => (),
} }
} }
}
let changed = SELECTIONS.lock().await.changed; let changed = SELECTIONS.lock().await.changed;
if changed { if changed {

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../userlib" } abi = { path = "../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"
embedded-text = "0.7.3" embedded-text = "0.7.3"

View File

@@ -2,25 +2,25 @@
extern crate alloc; extern crate alloc;
use abi::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
};
use alloc::vec::Vec; use alloc::vec::Vec;
use embedded_graphics::{ use embedded_graphics::{
mono_font::{ascii::FONT_10X20, MonoTextStyle}, Drawable,
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565, pixelcolor::Rgb565,
prelude::{Dimensions, DrawTarget, Point, Primitive, RgbColor}, prelude::{Dimensions, DrawTarget, Point, Primitive, RgbColor},
primitives::{PrimitiveStyle, Rectangle}, primitives::{PrimitiveStyle, Rectangle},
text::{renderer::TextRenderer, Alignment, Text}, text::{Alignment, Text, renderer::TextRenderer},
Drawable,
}; };
use embedded_layout::{ use embedded_layout::{
align::{horizontal, vertical}, align::{horizontal, vertical},
layout::linear::{FixedMargin, LinearLayout}, layout::linear::{FixedMargin, LinearLayout},
prelude::*, prelude::*,
}; };
use userlib::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
};
#[derive(Debug)] #[derive(Debug)]
pub enum SelectionUiError<DisplayError> { pub enum SelectionUiError<DisplayError> {
@@ -51,15 +51,16 @@ impl<'a> SelectionUi<'a> {
let selection; let selection;
loop { loop {
let key = get_key(); let key = get_key();
if key.state == KeyState::Pressed if key.state == KeyState::Pressed {
&& let Some(s) = self.update(display, key.key)? { if let Some(s) = self.update(display, key.key)? {
selection = Some(s); selection = Some(s);
display display
.clear(Rgb565::BLACK) .clear(Rgb565::BLACK)
.map_err(SelectionUiError::DisplayError)?; .map_err(|e| SelectionUiError::DisplayError(e))?;
break; break;
} }
} }
}
Ok(selection) Ok(selection)
} }
@@ -99,7 +100,7 @@ impl<'a> SelectionUi<'a> {
Rectangle::new(bounds.top_left, bounds.size) Rectangle::new(bounds.top_left, bounds.size)
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(display) .draw(display)
.map_err(SelectionUiError::DisplayError)?; .map_err(|e| SelectionUiError::DisplayError(e))?;
} }
let mut views: Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new(); let mut views: Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new();
@@ -118,7 +119,7 @@ impl<'a> SelectionUi<'a> {
layout layout
.draw(display) .draw(display)
.map_err(SelectionUiError::DisplayError)?; .map_err(|e| SelectionUiError::DisplayError(e))?;
// draw selected box // draw selected box
if let Some(selected_bounds) = layout.inner().get(self.selection) { if let Some(selected_bounds) = layout.inner().get(self.selection) {
@@ -126,7 +127,7 @@ impl<'a> SelectionUi<'a> {
Rectangle::new(selected_bounds.top_left, selected_bounds.size) Rectangle::new(selected_bounds.top_left, selected_bounds.size)
.into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1))
.draw(display) .draw(display)
.map_err(SelectionUiError::DisplayError)?; .map_err(|e| SelectionUiError::DisplayError(e))?;
self.last_bounds = Some(selected_bounds); self.last_bounds = Some(selected_bounds);
} }
@@ -135,19 +136,13 @@ impl<'a> SelectionUi<'a> {
} }
} }
pub fn draw_text_center<S>( pub fn draw_text_center<'a, S>(
display: &mut Display, display: &mut Display,
text: &str, text: &'a str,
style: S, style: S,
) -> Result<Point, <Display as DrawTarget>::Error> ) -> Result<Point, <Display as DrawTarget>::Error>
where where
S: TextRenderer<Color = <Display as DrawTarget>::Color>, S: TextRenderer<Color = <Display as DrawTarget>::Color>,
{ {
Text::with_alignment( Text::with_alignment(text, Point::zero(), style, Alignment::Center).draw(display)
text,
display.bounding_box().center(),
style,
Alignment::Center,
)
.draw(display)
} }

View File

@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"

View File

@@ -2,6 +2,12 @@
#![no_main] #![no_main]
extern crate alloc; extern crate alloc;
use abi::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{format, string::String, vec, vec::Vec}; use alloc::{format, string::String, vec, vec::Vec};
use core::panic::PanicInfo; use core::panic::PanicInfo;
use embedded_graphics::{ use embedded_graphics::{
@@ -19,12 +25,6 @@ use embedded_layout::{
layout::linear::LinearLayout, layout::linear::LinearLayout,
prelude::Chain, prelude::Chain,
}; };
use userlib::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
@@ -83,7 +83,7 @@ pub fn main() {
.align_to(&display.bounding_box(), horizontal::Left, vertical::Center); .align_to(&display.bounding_box(), horizontal::Left, vertical::Center);
let result = if let Ok(result) = evaluate(&input[input_min..]) { let result = if let Ok(result) = evaluate(&input[input_min..]) {
&format!(" = {result}") &format!(" = {}", result)
} else { } else {
" = Error" " = Error"
}; };

View File

@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
tinybmp = "0.6.0" tinybmp = "0.6.0"

View File

@@ -3,6 +3,13 @@
#![allow(static_mut_refs)] #![allow(static_mut_refs)]
extern crate alloc; extern crate alloc;
use abi::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{format, vec}; use alloc::{format, vec};
use core::panic::PanicInfo; use core::panic::PanicInfo;
use embedded_graphics::{ use embedded_graphics::{
@@ -10,13 +17,6 @@ use embedded_graphics::{
pixelcolor::Rgb565, prelude::*, text::Text, pixelcolor::Rgb565, prelude::*, text::Text,
}; };
use tinybmp::Bmp; use tinybmp::Bmp;
use userlib::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
@@ -51,9 +51,9 @@ pub fn main() {
println!("file: {}", file); println!("file: {}", file);
if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" { if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" {
let file_path = format!("/images/{file}"); let file_path = format!("/images/{}", file);
let read = read_file(&file_path, 0, &mut bmp_buf[..]); let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]);
if read > 0 { if read > 0 {
let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp");
@@ -74,7 +74,7 @@ pub fn main() {
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let text_y = y + bmp_h + 2; // 2px gap under image let text_y = y + bmp_h + 2; // 2px gap under image
Text::new(file.base(), Point::new(cell_x + 2, text_y), text_style) Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style)
.draw(&mut display) .draw(&mut display)
.unwrap(); .unwrap();
@@ -85,8 +85,11 @@ pub fn main() {
loop { loop {
let event = get_key(); let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc { if event.state != KeyState::Idle {
return; match event.key {
KeyCode::Esc => return,
_ => (),
} }
};
} }
} }

13
user-apps/gboy/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "gboy"
version = "0.1.0"
edition = "2024"
[build-dependencies]
bindgen = "0.71.0"
cc = "1.2.44"
[dependencies]
abi = { path = "../../abi" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1"

View File

@@ -14,6 +14,8 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
fn main() { fn main() {
bindgen();
// Put `memory.x` in our output directory and ensure it's // Put `memory.x` in our output directory and ensure it's
// on the linker search path. // on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
@@ -26,3 +28,37 @@ fn main() {
println!("cargo:rerun-if-changed=memory.x"); println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=-Tmemory.x"); println!("cargo:rustc-link-arg-bins=-Tmemory.x");
} }
fn bindgen() {
let bindings = bindgen::Builder::default()
.header("Peanut-GB/peanut_gb.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.disable_nested_struct_naming()
.clang_arg("-I../../picolibc/newlib/libc/include/")
.clang_arg("-I../../picolibc/build/")
.use_core()
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
cc::Build::new()
.define("PEANUT_GB_IS_LITTLE_ENDIAN", None)
.define("ENABLE_LCD", None)
.file("peanut_gb_stub.c")
.include("Peanut-GB")
// optimization flags
.flag("-Ofast")
.flag("-fdata-sections")
.flag("-ffunction-sections")
.flag("-mcpu=cortex-m33")
.flag("-mthumb")
.flag("-g0")
.compile("peanut_gb");
println!("cargo:rustc-link-search=Peanut-GB");
println!("cargo:rustc-link-lib=peanut_gb");
}

View File

@@ -0,0 +1 @@
#include <peanut_gb.h>

206
user-apps/gboy/src/main.rs Normal file
View File

@@ -0,0 +1,206 @@
#![no_std]
#![no_main]
#![allow(static_mut_refs)]
extern crate alloc;
use abi::{
display::Display,
format,
fs::{Entries, file_len, list_dir, read_file, write_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{vec, vec::Vec};
use core::{cell::LazyCell, mem::MaybeUninit, panic::PanicInfo};
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::FONT_6X10},
pixelcolor::Rgb565,
prelude::RgbColor,
};
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
mod peanut;
use peanut::gb_run_frame;
use crate::peanut::{
JOYPAD_A, JOYPAD_B, JOYPAD_DOWN, JOYPAD_LEFT, JOYPAD_RIGHT, JOYPAD_SELECT, JOYPAD_START,
JOYPAD_UP, gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_get_rom_name, gb_get_save_size,
gb_init, gb_init_lcd, gb_reset, gb_rom_read, gb_s, lcd_draw_line,
};
static mut DISPLAY: LazyCell<Display> = LazyCell::new(|| Display::take().unwrap());
const RAM_SIZE: usize = 32 * 1024; // largest ram size is 32k
static mut RAM: [u8; RAM_SIZE] = [0; RAM_SIZE];
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
const GAME_PATH: &'static str = "/games/gameboy";
static mut GAME_ROM: Option<Vec<u8>> = None;
pub fn main() {
println!("Starting Gameboy app");
let mut entries = Entries::new();
list_dir(GAME_PATH, &mut entries);
let mut files = entries.entries();
files.retain(|e| {
let ext = e.extension().unwrap_or("");
ext == "gb" || ext == "GB"
});
let mut roms = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
roms.sort();
let selection = {
let display = unsafe { &mut *DISPLAY };
let mut selection_ui = SelectionUi::new(&roms);
match selection_ui.run_selection_ui(display) {
Ok(maybe_sel) => maybe_sel,
Err(e) => match e {
SelectionUiError::SelectionListEmpty => {
draw_text_center(
display,
&format!("No Roms were found in {}", GAME_PATH),
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
)
.expect("Display Error");
None
}
SelectionUiError::DisplayError(_) => panic!("Display Error"),
},
}
};
assert!(selection.is_some());
let file_name = format!("{}/{}", GAME_PATH, roms[selection.unwrap()]);
let size = file_len(&file_name);
unsafe { GAME_ROM = Some(vec![0_u8; size]) };
let read = read_file(&file_name, 0, unsafe { GAME_ROM.as_mut().unwrap() });
assert!(size == read);
println!("Rom size: {}", read);
let mut gb = MaybeUninit::<gb_s>::uninit();
let init_status = unsafe {
gb_init(
gb.as_mut_ptr(),
Some(gb_rom_read),
Some(gb_cart_ram_read),
Some(gb_cart_ram_write),
Some(gb_error),
core::ptr::null_mut(),
)
};
println!("gb init status: {}", init_status);
unsafe {
load_save(&mut gb.assume_init());
}
unsafe {
gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line));
// enable frame skip
// gb.assume_init().direct.set_frame_skip(!true); // active low
};
loop {
let event = get_key();
let button = match event.key {
KeyCode::Esc => {
unsafe { write_save(&mut gb.assume_init()) };
break;
}
KeyCode::Char('r') => {
unsafe { gb_reset(gb.as_mut_ptr()) };
continue;
}
KeyCode::Tab => JOYPAD_START as u8,
KeyCode::Del => JOYPAD_SELECT as u8,
KeyCode::Enter => JOYPAD_A as u8,
KeyCode::Backspace => JOYPAD_B as u8,
KeyCode::Up => JOYPAD_UP as u8,
KeyCode::Down => JOYPAD_DOWN as u8,
KeyCode::Left => JOYPAD_LEFT as u8,
KeyCode::Right => JOYPAD_RIGHT as u8,
_ => 0,
};
if button != 0 {
unsafe {
// bindgen incorrectly generates direct so manual manipulation is required :(
let direct_ptr = &mut gb.assume_init().direct as *mut _ as *mut u8;
let joypad_ptr = direct_ptr.add(2); // this is the joypad bitfield byte
if let KeyState::Pressed = event.state {
*joypad_ptr &= !button;
} else if let KeyState::Released = event.state {
*joypad_ptr |= button;
}
println!("joypad: {:b}", *joypad_ptr);
}
}
unsafe {
gb_run_frame(gb.as_mut_ptr());
}
}
}
unsafe fn load_save(gb: &mut gb_s) {
let mut buf = [0; 16];
unsafe {
gb_get_rom_name(gb, buf.as_mut_ptr());
let save_size = gb_get_save_size(gb);
if save_size > 0 {
read_file(
&format!(
"{}/saves/{}.sav",
GAME_PATH,
str::from_utf8(&buf).expect("bad rom name")
),
0,
&mut RAM,
);
}
}
}
unsafe fn write_save(gb: &mut gb_s) {
let mut buf = [0; 16];
unsafe {
gb_get_rom_name(gb, buf.as_mut_ptr());
let save_size = gb_get_save_size(gb);
if save_size > 0 {
write_file(
&format!(
"{}/saves/{}.sav",
GAME_PATH,
str::from_utf8(&buf).expect("bad rom name")
),
0,
&mut RAM,
);
}
}
}

View File

@@ -0,0 +1,102 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::{DISPLAY, GAME_ROM, RAM};
#[allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
use abi::{display::Pixel565, println};
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
pub const GBOY_WIDTH: usize = 160;
pub const GBOY_HEIGHT: usize = 144;
pub unsafe extern "C" fn gb_rom_read(_gb: *mut gb_s, addr: u32) -> u8 {
unsafe { GAME_ROM.as_ref().unwrap()[addr as usize] }
}
pub unsafe extern "C" fn gb_cart_ram_read(_gb: *mut gb_s, addr: u32) -> u8 {
unsafe { RAM[addr as usize] }
}
pub unsafe extern "C" fn gb_cart_ram_write(_gb: *mut gb_s, addr: u32, val: u8) {
unsafe { RAM[addr as usize] = val }
}
pub unsafe extern "C" fn gb_error(_gb: *mut gb_s, err: gb_error_e, addr: u16) {
let e = match err {
0 => "UNKNOWN ERROR",
1 => "INVALID OPCODE",
2 => "INVALID READ",
3 => "INVALID WRITE",
4 => "HALT FOREVER",
5 => "INVALID MAX",
_ => unreachable!(),
};
println!("PeanutGB error: {}, addr: {}", e, addr);
}
const NUM_PALETTES: usize = 3;
const SHADES_PER_PALETTE: usize = 4;
const PALETTES: [[Rgb565; SHADES_PER_PALETTE]; NUM_PALETTES] = [
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // BG
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // OBJ0
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // OBJ1
];
pub unsafe extern "C" fn lcd_draw_line(_gb: *mut gb_s, pixels: *const u8, line: u8) {
if line < GBOY_HEIGHT as u8 {
let pixels = unsafe { core::slice::from_raw_parts(pixels, GBOY_WIDTH) };
let y = line as u16;
for (x, &p) in pixels.iter().enumerate() {
let palette_idx = ((p & 0xF0) >> 4) as usize;
let shade_idx = (p & 0x03) as usize;
let color = PALETTES
.get(palette_idx)
.and_then(|pal| pal.get(shade_idx))
.copied()
.unwrap_or(Rgb565::new(0, 0, 0));
// let sx = (x as u16) * 2;
// let sy = y * 2;
// draw_color(color, sx, sy);
// draw_color(color, sx + 1, sy);
// draw_color(color, sx, sy + 1);
// draw_color(color, sx + 1, sy + 1);
//
draw_color(color, x as u16, y as u16);
}
}
}
fn draw_color(color: Rgb565, x: u16, y: u16) {
let mut pixel = Pixel565::default();
pixel.0 = Point::new(x.into(), y.into());
pixel.1 = color;
unsafe {
pixel.draw(&mut *DISPLAY).unwrap();
}
}

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
selection_ui = { path = "../../selection_ui" } selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
tinygif = { git = "https://github.com/LegitCamper/tinygif" } tinygif = { git = "https://github.com/LegitCamper/tinygif" }

View File

@@ -2,6 +2,13 @@
#![no_main] #![no_main]
extern crate alloc; extern crate alloc;
use abi::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, file_len, list_dir, read_file},
get_key, get_ms,
keyboard::{KeyCode, KeyState},
println, sleep,
};
use alloc::{format, vec, vec::Vec}; use alloc::{format, vec, vec::Vec};
use core::panic::PanicInfo; use core::panic::PanicInfo;
use embedded_graphics::{ use embedded_graphics::{
@@ -13,13 +20,6 @@ use embedded_graphics::{
}; };
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center}; use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
use tinygif::Gif; use tinygif::Gif;
use userlib::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, file_len, list_dir, read_file},
get_key, get_ms,
keyboard::{KeyCode, KeyState},
println, sleep,
};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
@@ -44,7 +44,7 @@ pub fn main() {
let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>(); let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
gifs.sort(); gifs.sort();
let mut selection_ui = SelectionUi::new(&gifs); let mut selection_ui = SelectionUi::new(&mut gifs);
let selection = match selection_ui.run_selection_ui(&mut display) { let selection = match selection_ui.run_selection_ui(&mut display) {
Ok(maybe_sel) => maybe_sel, Ok(maybe_sel) => maybe_sel,
Err(e) => match e { Err(e) => match e {
@@ -87,9 +87,14 @@ pub fn main() {
if frame_num % 5 == 0 { if frame_num % 5 == 0 {
let event = get_key(); let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc { if event.state != KeyState::Idle {
match event.key {
KeyCode::Esc => {
drop(buf); drop(buf);
return; return;
}
_ => (),
};
}; };
} }
sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); sleep(((frame.delay_centis as u64) * 10).saturating_sub(start));

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" } embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" }
rand = { version = "0.9.0", default-features = false } rand = { version = "0.9.0", default-features = false }

View File

@@ -2,16 +2,16 @@
#![no_main] #![no_main]
extern crate alloc; extern crate alloc;
use core::panic::PanicInfo; use abi::{
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
use userlib::{
Rng, Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
get_key, get_key,
keyboard::{KeyCode, KeyState}, keyboard::{KeyCode, KeyState},
println, sleep, println, sleep,
}; };
use core::panic::PanicInfo;
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {

View File

@@ -1,11 +0,0 @@
[package]
name = "wav_player"
version = "0.1.0"
edition = "2024"
[dependencies]
userlib = { path = "../../userlib" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1"
rand = { version = "0.9.0", default-features = false }
embedded-audio = { git = "https://github.com/LegitCamper/embedded-audio" }

View File

@@ -1,143 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::{string::String, vec::Vec};
use core::panic::PanicInfo;
use embedded_audio::{AudioFile, PlatformFile, PlatformFileError, wav::Wav};
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::FONT_6X10},
pixelcolor::Rgb565,
prelude::RgbColor,
};
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
use userlib::{
audio::{AUDIO_BUFFER_LEN, audio_buffer_ready, send_audio_buffer},
display::Display,
format,
fs::{Entries, file_len, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
pub fn main() {
println!("Starting Wav player app");
let mut display = Display::take().unwrap();
loop {
let mut entries = Entries::new();
list_dir("/music", &mut entries);
let mut files = entries.entries();
files.retain(|e| e.extension().unwrap_or("") == "wav");
let mut wavs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
wavs.sort();
let mut selection_ui = SelectionUi::new(&wavs);
let selection = match selection_ui.run_selection_ui(&mut display) {
Ok(maybe_sel) => maybe_sel,
Err(e) => match e {
SelectionUiError::SelectionListEmpty => {
draw_text_center(
&mut display,
"No Wavs were found in /music",
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
)
.expect("Display Error");
None
}
SelectionUiError::DisplayError(_) => panic!("Display Error"),
},
};
assert!(selection.is_some());
draw_text_center(
&mut display,
&format!("Now playing {}", wavs[selection.unwrap()]),
MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE),
)
.expect("Display Error");
let file_name = format!("/music/{}", wavs[selection.unwrap()]);
let file = File::new(file_name);
let mut wav = Wav::new(file).unwrap();
println!("sample rate: {}", wav.sample_rate());
println!("channels: {:?}", wav.channels() as u8);
let mut buf = [0_u8; AUDIO_BUFFER_LEN];
loop {
if audio_buffer_ready() {
if wav.is_eof() {
break;
}
let _read = wav.read(&mut buf).unwrap();
send_audio_buffer(&buf);
}
let event = get_key();
if event.state == KeyState::Released && event.key == KeyCode::Esc {
return;
}
}
}
}
struct File {
current_pos: usize,
file: String,
}
impl File {
fn new(file: String) -> Self {
Self {
current_pos: 0,
file,
}
}
}
impl PlatformFile for File {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, PlatformFileError> {
let read = read_file(&self.file, self.current_pos, buf);
self.current_pos += read;
Ok(read)
}
fn seek_from_current(&mut self, offset: i64) -> Result<(), PlatformFileError> {
if offset.is_positive() {
self.current_pos += offset as usize;
} else {
self.current_pos -= offset as usize;
}
Ok(())
}
fn seek_from_start(&mut self, offset: usize) -> Result<(), PlatformFileError> {
self.current_pos = offset;
Ok(())
}
fn seek_from_end(&mut self, offset: usize) -> Result<(), PlatformFileError> {
self.current_pos = self.length() - offset;
Ok(())
}
fn length(&mut self) -> usize {
file_len(&self.file)
}
}