WIP keyboard syscall

This commit is contained in:
2025-08-31 22:41:09 -06:00
parent 1bbd988ef7
commit 5d0a3608d1
8 changed files with 210 additions and 113 deletions

39
Cargo.lock generated
View File

@@ -17,8 +17,11 @@ name = "abi"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"abi_sys", "abi_sys",
"embassy-time 0.5.0",
"embedded-graphics", "embedded-graphics",
"shared", "shared",
"spin",
"talc",
] ]
[[package]] [[package]]
@@ -186,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f377753756ec12e76b52d2dd657437be0448cc9736402ffadd0b8b8b9602c8a1" checksum = "f377753756ec12e76b52d2dd657437be0448cc9736402ffadd0b8b8b9602c8a1"
dependencies = [ dependencies = [
"embassy-sync 0.6.2", "embassy-sync 0.6.2",
"embassy-time", "embassy-time 0.4.0",
"embedded-io", "embedded-io",
"embedded-io-async", "embedded-io-async",
"futures-intrusive", "futures-intrusive",
@@ -320,7 +323,7 @@ dependencies = [
"embassy-futures", "embassy-futures",
"embassy-net-driver-channel", "embassy-net-driver-channel",
"embassy-sync 0.6.2", "embassy-sync 0.6.2",
"embassy-time", "embassy-time 0.4.0",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-io-async", "embedded-io-async",
"futures", "futures",
@@ -494,7 +497,7 @@ dependencies = [
"embassy-futures", "embassy-futures",
"embassy-hal-internal 0.3.0", "embassy-hal-internal 0.3.0",
"embassy-sync 0.7.0", "embassy-sync 0.7.0",
"embassy-time", "embassy-time 0.4.0",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async", "embedded-hal-async",
@@ -588,7 +591,7 @@ dependencies = [
"embassy-futures", "embassy-futures",
"embassy-hal-internal 0.2.0", "embassy-hal-internal 0.2.0",
"embassy-sync 0.6.2", "embassy-sync 0.6.2",
"embassy-time", "embassy-time 0.4.0",
"embassy-usb-driver", "embassy-usb-driver",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
@@ -626,7 +629,7 @@ dependencies = [
"embassy-futures", "embassy-futures",
"embassy-hal-internal 0.2.0", "embassy-hal-internal 0.2.0",
"embassy-sync 0.6.2", "embassy-sync 0.6.2",
"embassy-time", "embassy-time 0.4.0",
"embassy-time-driver", "embassy-time-driver",
"embassy-time-queue-utils", "embassy-time-queue-utils",
"embassy-usb-driver", "embassy-usb-driver",
@@ -696,10 +699,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "embassy-time-driver" name = "embassy-time"
version = "0.2.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba" checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65"
dependencies = [
"cfg-if",
"critical-section",
"document-features",
"embassy-time-driver",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-async",
"futures-core",
]
[[package]]
name = "embassy-time-driver"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
dependencies = [ dependencies = [
"document-features", "document-features",
] ]
@@ -1187,7 +1206,7 @@ dependencies = [
"embassy-futures", "embassy-futures",
"embassy-rp 0.4.0", "embassy-rp 0.4.0",
"embassy-sync 0.7.0", "embassy-sync 0.7.0",
"embassy-time", "embassy-time 0.4.0",
"embassy-usb", "embassy-usb",
"embedded-graphics", "embedded-graphics",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
@@ -2086,7 +2105,7 @@ dependencies = [
"bt-hci", "bt-hci",
"embassy-futures", "embassy-futures",
"embassy-sync 0.6.2", "embassy-sync 0.6.2",
"embassy-time", "embassy-time 0.4.0",
"embedded-io", "embedded-io",
"futures", "futures",
"heapless", "heapless",

View File

@@ -7,3 +7,6 @@ edition = "2024"
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
shared = { path = "../shared" } shared = { path = "../shared" }
abi_sys = { path = "../abi_sys" } abi_sys = { path = "../abi_sys" }
talc = "4.4.3"
spin = "0.10.0"
embassy-time = "0.5.0"

View File

@@ -1,20 +1,20 @@
#![no_std] #![no_std]
use abi_sys::{Syscall, call_abi}; use abi_sys::draw_iter;
use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; pub use abi_sys::{get_key, print};
pub use embassy_time;
pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
use talc::*;
pub fn print(msg: &str) { static mut ARENA: [u8; 10000] = [0; 10000];
let syscall = Syscall::Print {
msg: msg.as_ptr(), #[global_allocator]
len: msg.len(), static ALLOCATOR: Talck<spin::Mutex<()>, ClaimOnOom> =
}; Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) })
unsafe { .lock();
call_abi(&syscall);
}
}
pub mod display { pub mod display {
use crate::{Syscall, call_abi}; use crate::draw_iter;
use embedded_graphics::{ use embedded_graphics::{
Pixel, Pixel,
geometry::{Dimensions, Point}, geometry::{Dimensions, Point},
@@ -30,18 +30,6 @@ pub mod display {
pub struct Display; pub struct Display;
impl Display {
fn syscall_draw(&self, pixels: &[Pixel565]) {
let syscall = Syscall::DrawIter {
pixels: pixels.as_ptr(),
len: pixels.len(),
};
unsafe {
call_abi(&syscall);
}
}
}
impl Dimensions for Display { impl Dimensions for Display {
fn bounding_box(&self) -> Rectangle { fn bounding_box(&self) -> Rectangle {
Rectangle { Rectangle {
@@ -71,13 +59,13 @@ pub mod display {
count += 1; count += 1;
if count == BUF_SIZE { if count == BUF_SIZE {
self.syscall_draw(&buf[..count]); draw_iter(&buf[..count]);
count = 0; count = 0;
} }
} }
if count > 0 { if count > 0 {
self.syscall_draw(&buf[..count]); draw_iter(&buf[..count]);
} }
Ok(()) Ok(())

View File

@@ -1,5 +1,9 @@
#![no_std] #![no_std]
extern crate alloc;
use alloc::boxed::Box;
use core::pin::Pin;
pub use embedded_graphics::{ pub use embedded_graphics::{
Pixel, Pixel,
geometry::Point, geometry::Point,
@@ -7,26 +11,50 @@ pub use embedded_graphics::{
}; };
use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
// Instead of extern, declare a static pointer in a dedicated section pub type EntryFn = fn() -> Pin<Box<dyn Future<Output = ()>>>;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
#[unsafe(link_section = ".user_reloc")] #[unsafe(link_section = ".userapp")]
#[allow(non_upper_case_globals)] pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT];
pub static mut call_abi_ptr: usize = 0;
// Helper to call it #[repr(usize)]
pub unsafe fn call_abi(call: *const Syscall) { #[derive(Clone, Copy)]
let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; pub enum CallAbiTable {
f(call); Print = 0,
DrawIter = 1,
GetKey = 2,
} }
#[repr(C)] impl CallAbiTable {
pub enum Syscall { pub const COUNT: usize = 3;
DrawIter { }
pixels: *const Pixel<Rgb565>,
len: usize, pub type PrintAbi = extern "Rust" fn(msg: &str);
},
Print { pub fn print(msg: &str) {
msg: *const u8, unsafe {
len: usize, let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize];
}, let f: PrintAbi = core::mem::transmute(ptr);
f(msg);
}
}
pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel<Rgb565>]);
pub fn draw_iter(pixels: &[Pixel<Rgb565>]) {
unsafe {
let ptr = CALL_ABI_TABLE[CallAbiTable::DrawIter as usize];
let f: DrawIterAbi = core::mem::transmute(ptr);
f(pixels);
}
}
pub type GetKeyAbi = extern "Rust" fn() -> Option<KeyEvent>;
pub fn get_key() -> Option<KeyEvent> {
unsafe {
let ptr = CALL_ABI_TABLE[CallAbiTable::GetKey as usize];
let f: GetKeyAbi = core::mem::transmute(ptr);
f()
}
} }

View File

@@ -1,4 +1,7 @@
use abi_sys::Syscall; use core::pin::Pin;
use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi};
use alloc::boxed::Box;
use defmt::info; use defmt::info;
use embassy_futures::block_on; use embassy_futures::block_on;
use embedded_graphics::{ use embedded_graphics::{
@@ -8,35 +11,30 @@ use embedded_graphics::{
prelude::{Point, RgbColor, Size}, prelude::{Point, RgbColor, Size},
primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, primitives::{PrimitiveStyle, Rectangle, StyledDrawable},
}; };
use shared::keyboard::KeyEvent;
use crate::display::FRAMEBUFFER; use crate::{KEY_CACHE, display::FRAMEBUFFER};
#[allow(unused)] // ensure the abi and the kernel fn signatures are the same
pub extern "C" fn call_abi(call: *const Syscall) { const _: PrintAbi = print;
info!("called abi"); const _: DrawIterAbi = draw_iter;
let call = unsafe { &*call }; const _: GetKeyAbi = get_key;
match call {
Syscall::DrawIter { pixels, len } => {
// SAFETY: we're trusting the user program here
let slice = unsafe { core::slice::from_raw_parts(*pixels, *len) };
pub extern "Rust" fn print(msg: &str) {
defmt::info!("{:?}", msg);
}
pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
let framebuffer = block_on(FRAMEBUFFER.lock()); let framebuffer = block_on(FRAMEBUFFER.lock());
framebuffer framebuffer
.borrow_mut() .borrow_mut()
.as_mut() .as_mut()
.unwrap() .unwrap()
.draw_iter(slice.iter().copied()) .draw_iter(pixels.iter().copied())
.unwrap(); .unwrap();
} }
Syscall::Print { msg, len } => {
// SAFETY: we're trusting the user program here pub extern "Rust" fn get_key() -> Option<KeyEvent> {
let slice = unsafe { core::slice::from_raw_parts(*msg, *len) }; defmt::info!("get key called");
unsafe { KEY_CACHE.dequeue() }
if let Ok(str) = str::from_utf8(slice) {
defmt::info!("{:?}", str);
} else {
defmt::error!("Failed to parse user print str")
}
}
}
} }

View File

@@ -1,5 +1,15 @@
#![allow(static_mut_refs)] #![allow(static_mut_refs)]
use core::{alloc::Layout, ffi::c_void, ptr::NonNull, slice::from_raw_parts_mut}; use crate::abi;
use abi_sys::{CallAbiTable, EntryFn};
use alloc::boxed::Box;
use core::{
alloc::Layout,
ffi::c_void,
pin::Pin,
ptr::NonNull,
slice::from_raw_parts_mut,
task::{Context, Poll},
};
use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym};
// userland ram region defined in memory.x // userland ram region defined in memory.x
@@ -8,8 +18,6 @@ unsafe extern "C" {
static __userapp_end__: u8; static __userapp_end__: u8;
} }
type EntryFn = extern "C" fn();
pub unsafe fn load_binary(bytes: &[u8]) -> Result<EntryFn, &str> { pub unsafe fn load_binary(bytes: &[u8]) -> Result<EntryFn, &str> {
let elf = Elf::parse(&bytes).expect("Failed to parse ELF"); let elf = Elf::parse(&bytes).expect("Failed to parse ELF");
@@ -55,16 +63,22 @@ pub unsafe fn load_binary(bytes: &[u8]) -> Result<EntryFn, &str> {
let call_abi_sym = elf let call_abi_sym = elf
.syms .syms
.iter() .iter()
.find(|s| elf.strtab.get_at(s.st_name).unwrap() == "call_abi_ptr") .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "CALL_ABI_TABLE")
.expect("call_abi_ptr not found"); .expect("syscall table not found");
// Virtual address inside user RAM let table_base = call_abi_sym.st_value as *mut usize;
let addr = call_abi_sym.st_value as *mut usize;
// Patch it let entries: &[(CallAbiTable, usize)] = &[
(CallAbiTable::Print, abi::print as usize),
(CallAbiTable::DrawIter, abi::draw_iter as usize),
(CallAbiTable::GetKey, abi::get_key as usize),
];
assert!(entries.len() == CallAbiTable::COUNT);
for &(abi_idx, func_ptr) in entries {
unsafe { unsafe {
core::ptr::write(addr, crate::abi::call_abi as usize); table_base.add(abi_idx as usize).write(func_ptr);
}
} }
Ok(unsafe { core::mem::transmute(elf.entry as u32) }) Ok(unsafe { core::mem::transmute(elf.entry as u32) })
} }

View File

@@ -1,7 +1,11 @@
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
#![feature(type_alias_impl_trait)]
#![feature(ascii_char)] #![feature(ascii_char)]
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)] #![cfg_attr(not(test), no_main)]
#![allow(static_mut_refs)]
extern crate alloc;
mod abi; mod abi;
mod display; mod display;
@@ -23,12 +27,13 @@ use crate::{
storage::{SDCARD, SdCard}, storage::{SDCARD, SdCard},
usb::{ENABLE_SCSI, usb_handler}, usb::{ENABLE_SCSI, usb_handler},
}; };
use alloc::vec::Vec;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
use defmt::unwrap; use defmt::unwrap;
use embassy_executor::{Executor, Spawner}; use embassy_executor::{Executor, Spawner};
use embassy_futures::join::join; use embassy_futures::join::{join, join3};
use embassy_rp::{ use embassy_rp::{
gpio::{Input, Level, Output, Pull}, gpio::{Input, Level, Output, Pull},
i2c::{self, I2c}, i2c::{self, I2c},
@@ -40,10 +45,15 @@ use embassy_rp::{
spi::{self, Spi}, spi::{self, Spi},
usb as embassy_rp_usb, usb as embassy_rp_usb,
}; };
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; use embassy_sync::{
blocking_mutex::{Mutex, raw::CriticalSectionRawMutex},
signal::Signal,
};
use embassy_time::{Delay, Timer}; use embassy_time::{Delay, Timer};
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::SdCard as SdmmcSdCard; use embedded_sdmmc::SdCard as SdmmcSdCard;
use heapless::spsc::Queue;
use shared::keyboard::KeyEvent;
use static_cell::StaticCell; use static_cell::StaticCell;
use talc::*; use talc::*;
@@ -118,7 +128,7 @@ async fn userland_task() {
defmt::info!("Running binary"); defmt::info!("Running binary");
let entry = unsafe { load_binary(binary_data).unwrap() }; let entry = unsafe { load_binary(binary_data).unwrap() };
entry(); entry().await;
} }
struct Display { struct Display {
@@ -194,5 +204,21 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: USB) {
let usb_fut = usb_handler(usb); let usb_fut = usb_handler(usb);
ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed); ENABLE_SCSI.store(true, core::sync::atomic::Ordering::Relaxed);
join(usb_fut, display_fut).await; join3(usb_fut, display_fut, async {
loop {
Timer::after_millis(100).await;
get_keys().await
}
})
.await;
}
static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
async fn get_keys() {
if let Some(event) = read_keyboard_fifo().await {
unsafe {
let _ = KEY_CACHE.enqueue(event);
}
}
} }

View File

@@ -1,8 +1,10 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use abi::{display::Display, print}; extern crate alloc;
use core::panic::PanicInfo; use abi::{KeyCode, display::Display, embassy_time, get_key, print};
use alloc::{boxed::Box, string::String, vec};
use core::{panic::PanicInfo, pin::Pin};
use embedded_graphics::{ use embedded_graphics::{
Drawable, Drawable,
geometry::{Dimensions, Point}, geometry::{Dimensions, Point},
@@ -17,21 +19,40 @@ fn panic(_info: &PanicInfo) -> ! {
loop {} loop {}
} }
#[unsafe(no_mangle)] pub async fn main() {
pub extern "C" fn _start() { print("Starting Async Calculator app");
print("Starting Calculator app");
let mut display = Display; let mut display = Display;
let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED);
// Draw centered text. let mut text = vec!['H', 'E', 'L', 'L', 'O'];
let text = "embedded-graphics";
loop {
Text::with_alignment( Text::with_alignment(
text, &text.iter().cloned().collect::<String>(),
display.bounding_box().center() + Point::new(0, 15), display.bounding_box().center() + Point::new(0, 15),
character_style, character_style,
Alignment::Center, Alignment::Center,
) )
.draw(&mut display) .draw(&mut display)
.unwrap(); .unwrap();
if let Some(event) = get_key() {
print("User got event");
match event.key {
KeyCode::Char(ch) => {
text.push(ch);
}
KeyCode::Backspace => {
text.pop();
}
_ => (),
}
}
}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() -> Pin<Box<dyn Future<Output = ()>>> {
Box::pin(async { main().await })
} }