From c4f2c6cffb0c386b3eb20eb4c2b4d75d812495b8 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 26 Aug 2025 18:44:46 -0600 Subject: [PATCH] user app can invoke kernel syscall!!!! --- Cargo.lock | 79 ++++++++++---------- abi/src/lib.rs | 10 ++- justfile | 4 ++ kernel/Cargo.toml | 12 ++-- kernel/memory.x | 28 ++++---- kernel/src/abi.rs | 2 +- kernel/src/elf.rs | 119 ++++++++++++++----------------- kernel/src/main.rs | 15 ++-- user-apps/calculator/build.rs | 28 ++++++++ user-apps/calculator/src/main.rs | 20 +++--- user-apps/link.x | 21 ------ user-apps/memory.x | 33 +++++++++ 12 files changed, 201 insertions(+), 170 deletions(-) create mode 100644 justfile create mode 100644 user-apps/calculator/build.rs delete mode 100644 user-apps/link.x create mode 100644 user-apps/memory.x diff --git a/Cargo.lock b/Cargo.lock index 8d737a4..130e238 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,45 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "abi", + "bitflags 2.9.1", + "bt-hci", + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt 0.3.100", + "defmt-rtt", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", + "embassy-rp 0.4.0", + "embassy-sync 0.7.0", + "embassy-time", + "embassy-usb", + "embedded-graphics", + "embedded-hal 0.2.7", + "embedded-hal-async", + "embedded-hal-bus", + "embedded-layout", + "embedded-sdmmc", + "goblin", + "heapless", + "num_enum 0.7.4", + "panic-probe", + "portable-atomic", + "shared", + "spin", + "st7365p-lcd", + "static_cell", + "talc", + "trouble-host", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -1428,46 +1467,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "picocalc-os-rs" -version = "0.1.0" -dependencies = [ - "abi", - "bitflags 2.9.1", - "bt-hci", - "bumpalo", - "cortex-m", - "cortex-m-rt", - "cyw43", - "cyw43-pio", - "defmt 0.3.100", - "defmt-rtt", - "embassy-embedded-hal", - "embassy-executor", - "embassy-futures", - "embassy-rp 0.4.0", - "embassy-sync 0.7.0", - "embassy-time", - "embassy-usb", - "embedded-graphics", - "embedded-hal 0.2.7", - "embedded-hal-async", - "embedded-hal-bus", - "embedded-layout", - "embedded-sdmmc", - "goblin", - "heapless", - "num_enum 0.7.4", - "panic-probe", - "portable-atomic", - "shared", - "spin", - "st7365p-lcd", - "static_cell", - "talc", - "trouble-host", -] - [[package]] name = "pin-project-lite" version = "0.2.16" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index b39cd25..f97455e 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,8 +3,16 @@ use core::ffi::c_void; use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +// Instead of extern, declare a static pointer in a dedicated section #[unsafe(no_mangle)] -pub unsafe fn call_abi(_call: *const Syscall) {} +#[unsafe(link_section = ".user_reloc")] +pub static mut call_abi_ptr: usize = 0; + +// Helper to call it +pub unsafe fn call_abi(call: *const Syscall) { + let f: extern "C" fn(*const Syscall) = unsafe { core::mem::transmute(call_abi_ptr) }; + f(call); +} #[repr(C)] pub enum Syscall { diff --git a/justfile b/justfile new file mode 100644 index 0000000..79061bc --- /dev/null +++ b/justfile @@ -0,0 +1,4 @@ +kernel: calculator + cargo run --bin kernel +calculator: + RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --release diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d6d0173..2891c66 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "picocalc-os-rs" +name = "kernel" version = "0.1.0" edition = "2024" [[bin]] -name = "picocalc-os-rs" +name = "kernel" path = "src/main.rs" test = false doctest = false @@ -78,11 +78,13 @@ static_cell = "2.1.1" bitflags = "2.9.1" heapless = "0.8.0" num_enum = { version = "0.7.4", default-features = false } -goblin = { version = "0.10.0", default-features = false, features = ["elf32", "elf64", "alloc", "endian_fd"] } -bumpalo = "3.19.0" +goblin = { version = "0.10.0", default-features = false, features = [ + "elf32", + "elf64", + "endian_fd", +] } talc = "4.4.3" spin = "0.10.0" shared = { path = "../shared" } abi = { path = "../abi" } - diff --git a/kernel/memory.x b/kernel/memory.x index 5bdbb38..fd22299 100644 --- a/kernel/memory.x +++ b/kernel/memory.x @@ -1,22 +1,10 @@ MEMORY { - /* - * The RP2350 has either external or internal flash. - * - * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. - */ FLASH : ORIGIN = 0x10000000, LENGTH = 4096K - /* - * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. - * This is usually good for performance, as it distributes load on - * those banks evenly. - */ RAM : ORIGIN = 0x20000000, LENGTH = 512K - /* - * RAM banks 8 and 9 use a direct mapping. They can be used to have - * memory areas dedicated for some specific job, improving predictability - * of access times. - * Example: Separate stacks for core0 and core1. - */ + + /* Reserve a block of RAM for the user app */ + USERAPP : ORIGIN = 0x20010000, LENGTH = 192K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } @@ -73,3 +61,11 @@ SECTIONS { PROVIDE(start_to_end = __end_block_addr - __start_block_addr); PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + +SECTIONS { + .userapp (NOLOAD) : + { + __userapp_start__ = ORIGIN(USERAPP); + __userapp_end__ = ORIGIN(USERAPP) + LENGTH(USERAPP); + } > USERAPP +} diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 5eba4df..1f8c3ba 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -11,7 +11,7 @@ use embedded_graphics::{ use crate::display::FRAMEBUFFER; #[allow(unused)] -pub fn call_abi(call: *const Syscall) { +pub extern "C" fn call_abi(call: *const Syscall) { info!("called abi"); let call = unsafe { &*call }; match call { diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 13943f2..a553aa4 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,85 +1,70 @@ #![allow(static_mut_refs)] -use abi::Syscall; -use bumpalo::Bump; use core::{alloc::Layout, ffi::c_void, ptr::NonNull, slice::from_raw_parts_mut}; -use goblin::{ - elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}, - elf32, -}; +use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym}; -use crate::abi::call_abi; +// userland ram region defined in memory.x +unsafe extern "C" { + static __userapp_start__: u8; + static __userapp_end__: u8; +} -pub fn load_elf(elf_bytes: &[u8], bump: &mut Bump) -> Result !, ()> { - let elf = Elf::parse(elf_bytes).map_err(|_| ())?; +type EntryFn = extern "C" fn(); - if elf.is_64 - || elf.is_lib - || elf.is_object_file() - || !elf.little_endian - || elf.header.e_type != ET_DYN - || elf.interpreter.is_some() - { - return Err(()); +pub unsafe fn load_binary(bytes: &[u8]) -> Result { + let elf = Elf::parse(&bytes).expect("Failed to parse ELF"); + + if elf.is_64 || elf.is_lib || !elf.little_endian { + return Err("Unsupported ELF type"); } - // Find base address (lowest virtual address of PT_LOAD segments) - let base_vaddr = elf - .program_headers - .iter() - .filter(|ph| ph.p_type == PT_LOAD) - .map(|ph| ph.p_vaddr) - .min() - .ok_or(())?; - - // Determine total memory needed for all PT_LOAD segments - let total_size = elf - .program_headers - .iter() - .filter(|ph| ph.p_type == PT_LOAD) - .map(|ph| { - let start = ph.p_vaddr; - let end = ph.p_vaddr + ph.p_memsz; - end - base_vaddr - }) - .max() - .unwrap_or(0) as usize; - - // Allocate one big block from the bump heap - let layout = Layout::from_size_align(total_size, 0x1000).map_err(|_| ())?; - let base_ptr = bump.alloc_layout(layout).as_ptr(); - for ph in &elf.program_headers { - if ph.p_type != PT_LOAD { - continue; - } + if ph.p_type == PT_LOAD { + let vaddr = ph.p_vaddr as usize; + let memsz = ph.p_memsz as usize; + let filesz = ph.p_filesz as usize; + let offset = ph.p_offset as usize; - let file_offset = ph.p_offset as usize; - let file_size = ph.p_filesz as usize; - let mem_size = ph.p_memsz as usize; - let virt_offset = (ph.p_vaddr - base_vaddr) as usize; + let seg_start = vaddr; + let seg_end = vaddr + memsz; - let src = &elf_bytes[file_offset..file_offset + file_size]; - let dst = unsafe { base_ptr.add(virt_offset) }; + // Bounds check: make sure segment fits inside payload region + let user_start = unsafe { &__userapp_start__ as *const u8 as usize }; + let user_end = unsafe { &__userapp_end__ as *const u8 as usize }; + if seg_start < user_start || seg_end > user_end { + panic!( + "Segment out of bounds: {:x}..{:x} not within {:x}..{:x}", + seg_start, seg_end, user_start, user_end + ); + } - unsafe { - core::ptr::copy_nonoverlapping(src.as_ptr(), dst, file_size); - if mem_size > file_size { - core::ptr::write_bytes(dst.add(file_size), 0, mem_size - file_size); + unsafe { + let dst = seg_start as *mut u8; + let src = bytes.as_ptr().add(offset); + + // Copy initialized part + core::ptr::copy_nonoverlapping(src, dst, filesz); + + // Zero BSS region (memsz - filesz) + if memsz > filesz { + core::ptr::write_bytes(dst.add(filesz), 0, memsz - filesz); + } } } } - // Patch `call_abi` symbol - for sym in elf.syms.iter() { - let name = elf.strtab.get_at(sym.st_name).ok_or(())?; - if name == "call_abi" && sym.st_bind() == sym::STB_GLOBAL { - let offset = (sym.st_value - base_vaddr) as usize; - let ptr = unsafe { base_ptr.add(offset) as *mut usize }; - unsafe { *ptr = call_abi as usize }; - } + let call_abi_sym = elf + .syms + .iter() + .find(|s| elf.strtab.get_at(s.st_name).unwrap() == "call_abi_ptr") + .expect("call_abi_ptr not found"); + + // Virtual address inside user RAM + let addr = call_abi_sym.st_value as *mut usize; + + // Patch it + unsafe { + core::ptr::write(addr, crate::abi::call_abi as usize); } - // Compute relocated entry point - let relocated_entry = unsafe { base_ptr.add((elf.entry - base_vaddr) as usize) }; - Ok(unsafe { core::mem::transmute(relocated_entry) }) + Ok(unsafe { core::mem::transmute(elf.entry as u32) }) } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index d0b34f5..715b489 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -15,7 +15,7 @@ mod utils; use crate::{ display::{display_handler, init_display}, - elf::load_elf, + elf::load_binary, peripherals::{ conf_peripherals, keyboard::{KeyCode, KeyState, read_keyboard_fifo}, @@ -26,7 +26,6 @@ use crate::{ use {defmt_rtt as _, panic_probe as _}; -use bumpalo::Bump; use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::join::join; @@ -56,7 +55,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 50_000] = [0; 50_000]; +static mut ARENA: [u8; 10000] = [0; 10000]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -107,14 +106,14 @@ async fn main(_spawner: Spawner) { // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { - let mut bump = Bump::with_capacity(25_000); - + defmt::info!("Loading binary"); let binary_data: &[u8] = - include_bytes!("../../target/thumbv8m.main-none-eabihf/debug/calculator"); - let entry = load_elf(binary_data, &mut bump).unwrap(); + include_bytes!("../../target/thumbv8m.main-none-eabihf/release/calculator"); + + defmt::info!("Running binary"); + let entry = unsafe { load_binary(binary_data).unwrap() }; entry(); - bump.reset(); // clear heap arena } struct Display { diff --git a/user-apps/calculator/build.rs b/user-apps/calculator/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/calculator/build.rs @@ -0,0 +1,28 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("../memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); +} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 3d219e5..3c26e28 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -10,17 +10,15 @@ fn panic(_info: &PanicInfo) -> ! { } #[unsafe(no_mangle)] -pub extern "C" fn _start() -> ! { - loop { - for i in 0..300 { - for o in 0..300 { - let call = Syscall::DrawPixel { - x: i, - y: o, - color: 0, - }; - unsafe { call_abi(&call) }; - } +pub extern "C" fn _start() { + for i in 0..300 { + for o in 0..300 { + let call = Syscall::DrawPixel { + x: i, + y: o, + color: 0, + }; + unsafe { call_abi(&call) }; } } } diff --git a/user-apps/link.x b/user-apps/link.x deleted file mode 100644 index cdfc9f7..0000000 --- a/user-apps/link.x +++ /dev/null @@ -1,21 +0,0 @@ -MEMORY -{ - RAM : ORIGIN = 0x00000000, LENGTH = 256K -} - -SECTIONS -{ - .text : { - *(.text .text.*); - *(.rodata .rodata.*); - } > RAM - - .data : { - *(.data .data.*); - } > RAM - - .bss : { - *(.bss .bss.*); - *(COMMON); - } > RAM -} diff --git a/user-apps/memory.x b/user-apps/memory.x new file mode 100644 index 0000000..19fa9a5 --- /dev/null +++ b/user-apps/memory.x @@ -0,0 +1,33 @@ +MEMORY +{ + /* Must match the USERAPP region in the kernel linker script */ + RAM : ORIGIN = 0x20010000, LENGTH = 192K +} + +SECTIONS +{ + /* Reserve first 1KB for patchable symbols */ + .user_reloc (NOLOAD) : ALIGN(4) + { + __user_reloc_start = .; + KEEP(*(.user_reloc*)); + __user_reloc_end = .; + } > RAM + + .text : ALIGN(4) + { + *(.text .text.*); + *(.rodata .rodata.*); + } > RAM + + .data : ALIGN(4) + { + *(.data .data.*); + } > RAM + + .bss : ALIGN(4) + { + *(.bss .bss.*); + *(COMMON); + } > RAM +}