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
*.uf2
userlib_sys.h
abi_sys.h
assets/gif/bad_apple.gif
picolibc
user-apps/gboy/Peanut-GB

204
Cargo.lock generated
View File

@@ -12,6 +12,27 @@ dependencies = [
"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]]
name = "ahash"
version = "0.8.12"
@@ -110,6 +131,26 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bit-set"
version = "0.5.3"
@@ -222,9 +263,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
name = "calculator"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"embedded-layout",
"userlib",
]
[[package]]
@@ -246,12 +287,42 @@ dependencies = [
"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]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "clap"
version = "3.2.25"
@@ -356,9 +427,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
@@ -877,14 +948,6 @@ dependencies = [
"rlsf",
]
[[package]]
name = "embedded-audio"
version = "0.1.0"
source = "git+https://github.com/LegitCamper/embedded-audio#087784644d810b94dd659a03dbed4795dfb0bd24"
dependencies = [
"heapless",
]
[[package]]
name = "embedded-graphics"
version = "0.8.1"
@@ -1077,6 +1140,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "fixed"
version = "1.29.0"
@@ -1209,16 +1278,27 @@ dependencies = [
name = "gallery"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"tinybmp",
"userlib",
]
[[package]]
name = "gboy"
version = "0.1.0"
dependencies = [
"abi",
"bindgen",
"cc",
"embedded-graphics",
"selection_ui",
]
[[package]]
name = "generic-array"
version = "0.14.7"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
@@ -1251,12 +1331,18 @@ dependencies = [
name = "gif"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"selection_ui",
"tinygif",
"userlib",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "goblin"
version = "0.10.3"
@@ -1429,6 +1515,7 @@ dependencies = [
name = "kernel"
version = "0.0.1"
dependencies = [
"abi_sys",
"assign-resources",
"bitflags 2.10.0",
"bt-hci",
@@ -1456,10 +1543,8 @@ dependencies = [
"embedded-layout",
"embedded-sdmmc",
"embedded-text",
"fixed",
"goblin",
"heapless",
"micromath",
"num_enum 0.7.5",
"once_cell",
"panic-probe",
@@ -1471,7 +1556,6 @@ dependencies = [
"strum",
"talc",
"trouble-host",
"userlib_sys",
]
[[package]]
@@ -1549,6 +1633,16 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "libredox"
version = "0.1.10"
@@ -1604,6 +1698,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nb"
version = "0.1.3"
@@ -1625,6 +1725,16 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "num-traits"
version = "0.2.19"
@@ -1888,6 +1998,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-error"
version = "1.0.4"
@@ -2077,6 +2197,12 @@ dependencies = [
"crc-any",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.2.3"
@@ -2136,10 +2262,10 @@ checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
name = "selection_ui"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"embedded-layout",
"embedded-text",
"userlib",
]
[[package]]
@@ -2216,6 +2342,12 @@ dependencies = [
"keccak",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.1"
@@ -2250,10 +2382,10 @@ dependencies = [
name = "snake"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"embedded-snake",
"rand",
"userlib",
]
[[package]]
@@ -2626,27 +2758,6 @@ dependencies = [
"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]]
name = "uuid"
version = "1.18.1"
@@ -2754,17 +2865,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wav_player"
version = "0.1.0"
dependencies = [
"embedded-audio",
"embedded-graphics",
"rand",
"selection_ui",
"userlib",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

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

View File

@@ -1,27 +1,26 @@
# PicoCalc OS (Rust)
A simple kernel and applications 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.
A simple operating system for the **Clockwork PicoCalc**, written in Rust.
This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware.
## Status
Basic synchronous applications are working great.
Current focus is on 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
- **`kernel/`** The core OS kernel
- **`userlib_sys/`** C FFI bindings for kernel syscall
- **`userlib/`** Rust wrapper on top of `userlib_sys`
- **`picolib/`** Built with ```just newlib```, and provides libc symbols when linking with C libraries
- **`user_apps/`** Collection of userspace programs (gif player, wav player, calculator, snake, etc.)
- **`kernel/`** The core OS kernel (task scheduling, drivers, memory, etc.)
- **`abi_sys/`** Shared application binary interface definitions for kernel ↔ userspace (Repr "C")
- **`abi/`** Rust focused ABI helpers and abstractions for easier development
- **`user-apps/`** Collection of userspace programs (calculator, snake, etc.)
## Features
- 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
- Hardware drivers tailored for the PicoCalc( Audio, Display, Keyboard, ans Storage )
- Hardware drivers tailored for the PicoCalc
## Getting Started

View File

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

View File

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

View File

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

View File

@@ -12,12 +12,12 @@ use strum::{EnumCount, EnumIter};
pub type EntryFn = fn();
pub const SYS_CALL_TABLE_COUNT: usize = 15;
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT);
pub const ABI_CALL_TABLE_COUNT: usize = 12;
const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
#[derive(Clone, Copy, EnumIter, EnumCount)]
#[repr(u8)]
pub enum SyscallTable {
pub enum CallTable {
Alloc = 0,
Dealloc = 1,
PrintString = 2,
@@ -30,14 +30,11 @@ pub enum SyscallTable {
ReadFile = 9,
WriteFile = 10,
FileLen = 11,
ReconfigureAudioSampleRate = 12,
AudioBufferReady = 13,
SendAudioBuffer = 14,
}
#[unsafe(no_mangle)]
#[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")]
#[repr(C)]
@@ -47,9 +44,9 @@ pub struct CLayout {
}
#[cfg(feature = "alloc")]
impl From<CLayout> for Layout {
fn from(val: CLayout) -> Self {
unsafe { Layout::from_size_align_unchecked(val.size, val.alignment) }
impl Into<Layout> for CLayout {
fn into(self) -> Layout {
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)]
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)
}
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)]
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
let f: Dealloc =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Dealloc as usize]) };
let f: DeallocAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) };
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)]
pub extern "C" fn print(ptr: *const u8, len: usize) {
let f: Print =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::PrintString as usize]) };
let f: PrintAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) };
f(ptr, len);
}
pub type SleepMs = extern "C" fn(ms: u64);
pub type SleepMsAbi = extern "C" fn(ms: u64);
#[unsafe(no_mangle)]
pub extern "C" fn sleep(ms: u64) {
let f: SleepMs =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::SleepMs as usize]) };
let f: SleepMsAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) };
f(ms);
}
pub type GetMs = extern "C" fn() -> u64;
pub type GetMsAbi = extern "C" fn() -> u64;
#[unsafe(no_mangle)]
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()
}
#[repr(C)]
#[derive(Default, Copy, Clone)]
#[derive(Copy, Clone)]
pub struct CPixel {
pub x: i32,
pub y: i32,
@@ -124,36 +121,33 @@ impl CPixel {
}
}
impl From<CPixel> for Pixel<Rgb565> {
fn from(value: CPixel) -> Self {
Pixel(
Point::new(value.x, value.y),
RawU16::new(value.color).into(),
)
}
}
impl From<Pixel<Rgb565>> for CPixel {
fn from(value: Pixel<Rgb565>) -> Self {
impl Into<CPixel> for Pixel<Rgb565> {
fn into(self) -> CPixel {
CPixel {
x: value.0.x,
y: value.0.y,
color: value.1.into_storage(),
x: self.0.x,
y: self.0.y,
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)]
pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) {
let f: DrawIter =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::DrawIter as usize]) };
let f: DrawIterAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) };
f(ptr, len);
}
pub mod keyboard {
use crate::{SYS_CALL_TABLE, SyscallTable};
use crate::{CALL_ABI_TABLE, CallTable};
bitflags::bitflags! {
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -175,12 +169,12 @@ pub mod keyboard {
pub mods: Modifiers,
}
impl From<KeyEventC> for KeyEvent {
fn from(val: KeyEventC) -> Self {
impl Into<KeyEvent> for KeyEventC {
fn into(self) -> KeyEvent {
KeyEvent {
key: val.key.into(),
state: val.state,
mods: val.mods,
key: self.key.into(),
state: self.state,
mods: self.mods,
}
}
}
@@ -192,12 +186,12 @@ pub mod keyboard {
pub mods: Modifiers,
}
impl From<KeyEvent> for KeyEventC {
fn from(val: KeyEvent) -> Self {
impl Into<KeyEventC> for KeyEvent {
fn into(self) -> KeyEventC {
KeyEventC {
key: val.key.into(),
state: val.state,
mods: val.mods,
key: self.key.into(),
state: self.state,
mods: self.mods,
}
}
}
@@ -218,7 +212,7 @@ pub mod keyboard {
1 => KeyState::Pressed,
2 => KeyState::Hold,
3 => KeyState::Released,
_ => KeyState::Idle,
0 | _ => KeyState::Idle,
}
}
}
@@ -271,9 +265,9 @@ pub mod keyboard {
Unknown(u8),
}
impl From<KeyCode> for u8 {
fn from(val: KeyCode) -> Self {
match val {
impl Into<u8> for KeyCode {
fn into(self) -> u8 {
match self {
KeyCode::JoyUp => 0x01,
KeyCode::JoyDown => 0x02,
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)]
pub extern "C" fn get_key() -> KeyEventC {
let f: GetKey =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetKey as usize]) };
let f: GetKeyAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) };
f()
}
}
@@ -393,7 +387,7 @@ pub type GenRand = extern "C" fn(req: &mut RngRequest);
#[unsafe(no_mangle)]
pub extern "C" fn gen_rand(req: &mut RngRequest) {
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);
f(req)
}
@@ -416,7 +410,7 @@ pub extern "C" fn list_dir(
max_entry_str_len: usize,
) -> usize {
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);
f(str, len, entries, entry_count, max_entry_str_len)
}
@@ -439,7 +433,7 @@ pub extern "C" fn read_file(
buf_len: usize,
) -> usize {
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);
f(str, len, read_from, buf, buf_len)
}
@@ -457,7 +451,7 @@ pub extern "C" fn write_file(
buf_len: usize,
) {
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);
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)]
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
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);
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:
cargo run --bin kernel --features {{board}} --features fps --features defmt
cargo run --bin kernel --features {{board}} --features fps
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:
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\""
cbindgen:
cbindgen userlib_sys --output userlib_sys.h -q
cbindgen abi_sys --output abi_sys.h -q
newlib:
#!/bin/bash
@@ -41,10 +39,10 @@ userapps: cbindgen
just userapp snake
just userapp gallery
just userapp gif
just userapp wav_player
just userapp gboy
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:
#!/bin/bash
@@ -53,7 +51,6 @@ copy-userapps:
just copy-userapp snake
just copy-userapp gallery
just copy-userapp gif
just copy-userapp wav_player
DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}')
udisksctl unmount -b "$DEV"

View File

@@ -11,7 +11,7 @@ doctest = false
bench = false
[features]
default = ["rp235x"]
default = ["rp235x", "defmt"]
pimoroni2w = ["rp235x", "psram"]
# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb
rp235x = ["embassy-rp/rp235xb"]
@@ -82,8 +82,6 @@ embedded-graphics = { version = "0.8.1" }
embedded-text = "0.7.2"
embedded-layout = "0.4.2"
micromath = "2.1.0"
fixed = "1.29.0"
strum = { version = "0.27.2", default-features = false }
rand = { version = "0.9.0", default-features = false }
once_cell = { version = "1.21.3", default-features = false }
@@ -99,4 +97,4 @@ embedded-alloc = { version = "0.6.0", features = [
], optional = true }
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;
#[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")]
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 core::{ffi::c_char, ptr, sync::atomic::Ordering};
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
@@ -5,11 +9,6 @@ use embassy_time::Instant;
use embedded_graphics::draw_target::DrawTarget;
use embedded_sdmmc::LfnBuffer;
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")]
use crate::heap::HEAP;
@@ -18,13 +17,12 @@ use crate::heap::HEAP;
use core::alloc::GlobalAlloc;
use crate::{
audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_SAMPLE_RATE, AUDIO_BUFFER_WRITTEN},
display::FRAMEBUFFER,
framebuffer::FB_PAUSED,
storage::{Dir, File, SDCARD},
};
const _: Alloc = alloc;
const _: AllocAbi = alloc;
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
// SAFETY: caller guarantees layout is valid
unsafe {
@@ -35,12 +33,12 @@ pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
#[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) {
// SAFETY: caller guarantees ptr and layout are valid
#[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) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
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) {
let cycles_per_ms = clk_sys_freq() / 1000;
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;
const _: GetMs = get_ms;
const _: GetMsAbi = get_ms;
pub extern "C" fn get_ms() -> u64 {
Instant::now()
.duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() })
.as_millis()
}
const _: DrawIter = draw_iter;
const _: DrawIterAbi = draw_iter;
pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
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();
const _: GetKey = get_key;
const _: GetKeyAbi = get_key;
pub extern "C" fn get_key() -> KeyEventC {
if let Some(event) = unsafe { KEY_CACHE.dequeue() } {
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() {
let len = name.len().min(max_str_len - 1);
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
}
}
@@ -190,8 +188,8 @@ pub extern "C" fn list_dir(
let sd = guard.as_mut().unwrap();
let mut wrote = 0;
let _ = sd.access_root_dir(|root| {
if dirs[0].is_empty() && dirs.len() >= 2 {
sd.access_root_dir(|root| {
if dirs[0] == "" && dirs.len() >= 2 {
unsafe {
if dir == "/" {
wrote = get_dir_entries(&root, files, max_entry_str_len);
@@ -209,15 +207,16 @@ fn recurse_file<T>(
dirs: &[&str],
mut access: impl FnMut(&mut File) -> T,
) -> Result<T, ()> {
defmt::info!("dir: {}, dirs: {}", dir, dirs);
if dirs.len() == 1 {
let mut b = [0_u8; 50];
let mut buf = LfnBuffer::new(&mut b);
let mut short_name = None;
dir.iterate_dir_lfn(&mut buf, |entry, name| {
if let Some(name) = name
&& (name == dirs[0] || entry.name.to_string().as_str() == dirs[0])
{
short_name = Some(entry.name.clone());
if let Some(name) = name {
if name == dirs[0] || entry.name.to_string().as_str() == dirs[0] {
short_name = Some(entry.name.clone());
}
}
})
.expect("Failed to iterate dir");
@@ -257,17 +256,17 @@ pub extern "C" fn read_file(
}
// 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 guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
let sd = guard.as_mut().unwrap();
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| {
file.seek_from_start(start_from as u32).unwrap_or(());
file.read(buf).unwrap()
file.read(&mut buf).unwrap()
}) {
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 sd = guard.as_mut().unwrap();
if !file.is_empty() {
let _ = sd.access_root_dir(|root| {
sd.access_root_dir(|root| {
recurse_file(&root, &components[1..count], |file| {
file.seek_from_start(start_from as u32).unwrap();
file.write(buf).unwrap()
file.write(&buf).unwrap()
})
.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 sd = guard.as_mut().unwrap();
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()) {
len = result
}
@@ -332,36 +331,3 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> 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 core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::Ordering;
use embassy_futures::yield_now;
use embassy_rp::{
Peri,
gpio::{Level, Output},
peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
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 st7365p_lcd::ST7365P;
#[cfg(feature = "psram")]
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")]
pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER};
type Display = ST7365P<
type DISPLAY = ST7365P<
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
Output<'static>,
Output<'static>,
@@ -58,7 +58,7 @@ pub async fn init_display(
cs: Peri<'static, PIN_13>,
data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>,
) -> Display {
) -> DISPLAY {
init_fb();
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]
pub async fn display_handler(mut display: Display) {
pub async fn display_handler(mut display: DISPLAY) {
use embassy_time::{Instant, Timer};
// 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 {
Timer::after_millis(FRAME_TIME_MS - elapsed).await;
} else {
yield_now().await;
Timer::after_millis(1).await;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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_FIF: u8 = 0x09;

View File

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

View File

@@ -30,7 +30,7 @@ pub struct MassStorageClass<'d, D: Driver<'d>> {
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 {
let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class
let mut interface = function.interface();
@@ -72,34 +72,35 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
async fn handle_cbw(&mut self) {
let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await
&& n == 31
&& let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n])
{
// Take sdcard to increase speed
if self.temp_sd.is_none() {
let mut guard = SDCARD.get().lock().await;
if let Some(sd) = guard.take() {
self.temp_sd = Some(sd);
} else {
#[cfg(feature = "defmt")]
defmt::warn!("Tried to take SDCARD but it was already taken");
return;
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
// Take sdcard to increase speed
if self.temp_sd.is_none() {
let mut guard = SDCARD.get().lock().await;
if let Some(sd) = guard.take() {
self.temp_sd = Some(sd);
} else {
#[cfg(feature = "defmt")]
defmt::warn!("Tried to take SDCARD but it was already taken");
return;
}
}
let command = parse_cb(&cbw.CBWCB);
if self.handle_command(command).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
if self.pending_eject {
if let ScsiCommand::Write { lba: _, len: _ } = command {
MSC_SHUTDOWN.signal(());
}
}
}
}
let command = parse_cb(&cbw.CBWCB);
if self.handle_command(command).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
if self.pending_eject
&& let ScsiCommand::Write { lba: _, len: _ } = command
{
MSC_SHUTDOWN.signal(());
}
}
}
@@ -237,7 +238,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64;
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(&(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 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(&(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))?;
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(|_| ())?;
}
}
@@ -280,7 +281,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
.read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?;
for block in &block_buf[..blocks as usize] {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE) {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) {
self.bulk_in.write(chunk).await.map_err(|_| ())?;
}
}
@@ -300,7 +301,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
while blocks > 0 {
if blocks >= block_buf.len() as u64 {
for block in block_buf.as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE) {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
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;
} else {
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(|_| ())?;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,13 @@
#![allow(static_mut_refs)]
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 core::panic::PanicInfo;
use embedded_graphics::{
@@ -10,13 +17,6 @@ use embedded_graphics::{
pixelcolor::Rgb565, prelude::*, text::Text,
};
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]
fn panic(info: &PanicInfo) -> ! {
@@ -51,9 +51,9 @@ pub fn main() {
println!("file: {}", file);
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 {
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_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)
.unwrap();
@@ -85,8 +85,11 @@ pub fn main() {
loop {
let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc {
return;
}
if event.state != KeyState::Idle {
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;
fn main() {
bindgen();
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
@@ -26,3 +28,37 @@ fn main() {
println!("cargo:rerun-if-changed=memory.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"
[dependencies]
userlib = { path = "../../userlib" }
abi = { path = "../../abi" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1"
tinygif = { git = "https://github.com/LegitCamper/tinygif" }

View File

@@ -2,6 +2,13 @@
#![no_main]
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 core::panic::PanicInfo;
use embedded_graphics::{
@@ -13,13 +20,6 @@ use embedded_graphics::{
};
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
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]
fn panic(info: &PanicInfo) -> ! {
@@ -44,7 +44,7 @@ pub fn main() {
let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
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) {
Ok(maybe_sel) => maybe_sel,
Err(e) => match e {
@@ -87,9 +87,14 @@ pub fn main() {
if frame_num % 5 == 0 {
let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc {
drop(buf);
return;
if event.state != KeyState::Idle {
match event.key {
KeyCode::Esc => {
drop(buf);
return;
}
_ => (),
};
};
}
sleep(((frame.delay_centis as u64) * 10).saturating_sub(start));

View File

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

View File

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