user app can invoke kernel syscall!!!!

This commit is contained in:
2025-08-26 18:44:46 -06:00
parent 6dcdd88a0f
commit c4f2c6cffb
12 changed files with 201 additions and 170 deletions

View File

@@ -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" }

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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<extern "C" fn() -> !, ()> {
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<EntryFn, &str> {
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) })
}

View File

@@ -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<Executor> = StaticCell::new();
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
static mut ARENA: [u8; 50_000] = [0; 50_000];
static mut ARENA: [u8; 10000] = [0; 10000];
#[global_allocator]
static ALLOCATOR: Talck<spin::Mutex<()>, 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 {