diff --git a/Cargo.toml b/Cargo.toml index 7977fb7..5d6cacd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/adapter-riscv", + "crates/apps/hello", "crates/apps/snake", "crates/domain", "crates/elf", diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index 97e31e8..7252a1f 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -10,6 +10,14 @@ mod trap; use core::arch::global_asm; use hw::uart::Uart; +use core::ptr::addr_of_mut; + +static mut KERNEL: Option> = None; + +pub fn kernel_mut() -> &'static mut kernel::Kernel { + unsafe { (*addr_of_mut!(KERNEL)).as_mut() }.unwrap_or_else(|| unreachable!()) +} + global_asm!( r#" .section .text.entry @@ -61,18 +69,16 @@ extern "C" fn kmain() -> ! { types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE), board::USER_POOL_PAGES, ); - let mut kern = kernel::Kernel::new(riscv_platform, pool); - let (pid, tid) = kern + let kern = kernel::Kernel::new(riscv_platform, pool); + unsafe { *addr_of_mut!(KERNEL) = Some(kern); } + let kern = kernel_mut(); + + let (_pid, _tid) = kern .boot(types::bounded::Priority::DEFAULT, types::time::TimeSlice::DEFAULT) .unwrap_or_else(|_| loop { unsafe { core::arch::asm!("wfi") }; }); + uart.write_str("k-os: kernel started\n"); - uart.write_str("k-os: kernel started (pid="); - uart.write_dec(pid.raw() as usize); - uart.write_str(", tid="); - uart.write_dec(tid.raw() as usize); - uart.write_str(")\n"); - - ipc_demo(&mut kern, &uart, pid); + userspace_demo(kern, &uart, kernel_pt); uart.write_str("k-os: demo complete\n"); loop { @@ -80,100 +86,70 @@ extern "C" fn kmain() -> ! { } } -fn ipc_demo( - kern: &mut kernel::Kernel, +// Tiny userspace program: li a7, SYS_EXIT; ecall; j -4 +// SYS_EXIT = 16 = 0x10 +// RISC-V instructions (little-endian): +// li a7, 16 → addi a7, zero, 16 → 0x01000893 +// ecall → 0x00000073 +// j -4 → 0xffdff06f +static USER_EXIT_PROGRAM: [u8; 12] = [ + 0x93, 0x08, 0x00, 0x01, // addi a7, zero, 16 + 0x73, 0x00, 0x00, 0x00, // ecall + 0x6f, 0xf0, 0xdf, 0xff, // j -4 +]; + +const USER_VA: usize = 0x10000; + +fn userspace_demo( + _kern: &mut kernel::Kernel, uart: &Uart, - init_pid: types::id::ProcessId, + kernel_pt: &mmu::PageTable, ) { - use domain::message::Message; - use domain::resource::Resource; - use domain::syscall::Syscall; - use kernel::{SyscallOutcome, SyscallResult}; - use types::address::DataRef; - use types::bounded::CapSlot; - use types::id::ThreadId; - use types::permission::{Permission, PermissionSet}; + uart.write_str("k-os: --- userspace demo ---\n"); - uart.write_str("k-os: --- IPC demo ---\n"); - - let _proc_b_slot = match kern.handle_syscall(Syscall::CreateProcess { - create_cap: CapSlot::new(0).unwrap_or_else(|| unreachable!()), - image: DataRef::empty(), - }) { - SyscallOutcome::Complete(SyscallResult::Slot(s)) => s, - _ => { - uart.write_str("k-os: FAIL creating process B\n"); - return; - } - }; - let pid_b = types::id::ProcessId::from_raw(1); - let tid_b = ThreadId::from_raw(1); - uart.write_str("k-os: process B created\n"); - - let ep_id = kern.create_endpoint().unwrap_or_else(|_| unreachable!()); - - let ep_cap_a = kern.grant_capability( - init_pid, - Resource::Endpoint(ep_id), - PermissionSet::empty() - .with(Permission::Execute) - .with(Permission::Read), - ).unwrap_or_else(|_| unreachable!()); - - let ep_cap_b = kern.grant_capability( - pid_b, - Resource::Endpoint(ep_id), - PermissionSet::empty() - .with(Permission::Execute) - .with(Permission::Read), - ).unwrap_or_else(|_| unreachable!()); - - uart.write_str("k-os: endpoint created, caps granted\n"); - - kern.switch_to_thread(tid_b); - match kern.handle_syscall(Syscall::Receive { endpoint: ep_cap_b }) { - SyscallOutcome::BlockCaller(_) => { - uart.write_str("k-os: process B blocked on Receive\n"); - } - _ => { - uart.write_str("k-os: UNEXPECTED outcome for Receive\n"); - return; - } + // Allocate a page for the user program and copy code into it + let program_page = mmu::allocator::alloc_zeroed_page(); + unsafe { + core::ptr::copy_nonoverlapping( + USER_EXIT_PROGRAM.as_ptr(), + program_page as *mut u8, + USER_EXIT_PROGRAM.len(), + ); } - let tid_a = ThreadId::from_raw(0); - kern.switch_to_thread(tid_a); - let msg = Message::from_words([42, 7, 0, 0]); - match kern.handle_syscall(Syscall::Send { - endpoint: ep_cap_a, - message: msg, - }) { - SyscallOutcome::SwitchTo(target) => { - uart.write_str("k-os: Send delivered, switching to thread "); - uart.write_dec(target.raw() as usize); - uart.put_char(b'\n'); - } - _ => { - uart.write_str("k-os: UNEXPECTED outcome for Send\n"); - return; - } - } + // Create user page table with program mapped at USER_VA + let user_pt = mmu::create_user_page_table( + kernel_pt, + program_page, + 1, + USER_VA, + ); - match kern.pending_result(tid_b) { - Some(SyscallResult::Message(m)) => { - uart.write_str("k-os: IPC complete — received: ["); - uart.write_dec(m.word(0)); - uart.write_str(", "); - uart.write_dec(m.word(1)); - uart.write_str(", "); - uart.write_dec(m.word(2)); - uart.write_str(", "); - uart.write_dec(m.word(3)); - uart.write_str("]\n"); - } - _ => { - uart.write_str("k-os: FAIL — no message in pending result\n"); - } + uart.write_str("k-os: user page table created, entering U-mode\n"); + + // Activate user page table + mmu::activate(user_pt); + + // Set sscratch to kernel stack so trap vector can switch stacks + let kernel_sp: usize; + unsafe { core::arch::asm!("mv {0}, sp", out(reg) kernel_sp) }; + trap::set_kernel_stack(kernel_sp); + + // Enter user mode: set sstatus.SPP=0 (U-mode), sepc=USER_VA, sret + let user_stack = USER_VA - 8; // stack below program + unsafe { + core::arch::asm!( + "csrc sstatus, {spp}", // clear SPP → return to U-mode + "csrs sstatus, {spie}", // set SPIE → enable interrupts after sret + "csrw sepc, {entry}", // return address = user entry + "mv sp, {stack}", // user stack pointer (will be saved to sscratch on swap) + "sret", + spp = in(reg) 1usize << 8, + spie = in(reg) 1usize << 5, + entry = in(reg) USER_VA, + stack = in(reg) user_stack, + options(noreturn), + ); } } diff --git a/crates/adapter-riscv/src/mmu/allocator.rs b/crates/adapter-riscv/src/mmu/allocator.rs index e34b128..c71b7e1 100644 --- a/crates/adapter-riscv/src/mmu/allocator.rs +++ b/crates/adapter-riscv/src/mmu/allocator.rs @@ -8,12 +8,19 @@ pub fn init_allocator(start: usize) { unsafe { NEXT_FREE = start; } } -pub fn alloc_page_table() -> &'static mut PageTable { +pub fn alloc_zeroed_page() -> usize { unsafe { let addr = NEXT_FREE; NEXT_FREE += PAGE_SIZE; + core::ptr::write_bytes(addr as *mut u8, 0, PAGE_SIZE); + addr + } +} + +pub fn alloc_page_table() -> &'static mut PageTable { + unsafe { + let addr = alloc_zeroed_page(); let table = &mut *(addr as *mut PageTable); - table.zero(); table } } diff --git a/crates/adapter-riscv/src/trap/mod.rs b/crates/adapter-riscv/src/trap/mod.rs index eee9fcf..b1bc3d3 100644 --- a/crates/adapter-riscv/src/trap/mod.rs +++ b/crates/adapter-riscv/src/trap/mod.rs @@ -17,8 +17,6 @@ impl TrapFrame { } } -// sscratch = 0 when in S-mode, = kernel_stack_top when in U-mode. -// On trap: swap sp/sscratch. If sp==0 after swap, we came from S-mode (undo swap). global_asm!(r#" .align 4 .globl trap_vector @@ -149,6 +147,68 @@ pub fn set_kernel_stack(kernel_sp: usize) { } } +fn decode_syscall(frame: &TrapFrame) -> Option { + use domain::message::Message; + use domain::syscall::Syscall; + use types::syscall_abi::*; + + let nr = frame.regs[17]; // a7 + let a0 = frame.regs[10]; // a0 + let a1 = frame.regs[11]; // a1 + let a2 = frame.regs[12]; // a2 + let a3 = frame.regs[13]; // a3 + let a4 = frame.regs[14]; // a4 + + match nr { + SYS_EXIT => Some(Syscall::Exit), + SYS_EXIT_THREAD => Some(Syscall::ExitThread), + SYS_YIELD => Some(Syscall::Yield), + SYS_GET_TIME => Some(Syscall::GetTime), + SYS_GET_PROCESS_ID => Some(Syscall::GetProcessId), + SYS_SEND => { + let slot = types::bounded::CapSlot::new(a0 as u8)?; + let msg = Message::from_words([a1, a2, a3, a4]); + Some(Syscall::Send { endpoint: slot, message: msg }) + } + SYS_RECEIVE => { + let slot = types::bounded::CapSlot::new(a0 as u8)?; + Some(Syscall::Receive { endpoint: slot }) + } + SYS_SPAWN_THREAD => Some(Syscall::SpawnThread { + entry: types::address::EntryPoint::from_raw(a0), + stack: types::address::StackPointer::from_raw(a1), + }), + SYS_REQUEST_PAGES => { + let count = types::bounded::PageCount::new(a0 as u16)?; + Some(Syscall::RequestPages { count }) + } + _ => None, + } +} + +fn write_result(frame: &mut TrapFrame, outcome: &kernel::SyscallOutcome) { + use kernel::{SyscallOutcome, SyscallResult}; + + match outcome { + SyscallOutcome::Complete(result) => match result { + SyscallResult::Ok => frame.regs[10] = 0, + SyscallResult::Value(v) => frame.regs[10] = *v, + SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize, + SyscallResult::Message(m) => { + frame.regs[10] = m.word(0); + frame.regs[11] = m.word(1); + frame.regs[12] = m.word(2); + frame.regs[13] = m.word(3); + } + SyscallResult::Error(_) => frame.regs[10] = usize::MAX, + SyscallResult::WakeReason(_) => frame.regs[10] = 0, + }, + SyscallOutcome::BlockCaller(_) => {} + SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0, + SyscallOutcome::TerminateCaller => {} + } +} + #[unsafe(no_mangle)] extern "C" fn trap_handler_rust(frame: &mut TrapFrame) { let scause: usize; @@ -171,7 +231,19 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) { match scause { CAUSE_ECALL_U => { frame.sepc += 4; - uart.write_str("[ecall]\n"); + + if let Some(syscall) = decode_syscall(frame) { + let kern = crate::kernel_mut(); + let outcome = kern.handle_syscall(syscall); + write_result(frame, &outcome); + + match &outcome { + kernel::SyscallOutcome::TerminateCaller => { + uart.write_str("k-os: process exited\n"); + } + _ => {} + } + } } _ => { uart.write_str("FAULT: scause="); diff --git a/crates/apps/hello/Cargo.toml b/crates/apps/hello/Cargo.toml new file mode 100644 index 0000000..1be785e --- /dev/null +++ b/crates/apps/hello/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hello" +version = "0.1.0" +edition = "2024" + +[dependencies] +userspace = { workspace = true } + +[profile.release] +panic = "abort" +opt-level = "s" + +[profile.dev] +panic = "abort" diff --git a/crates/apps/hello/src/main.rs b/crates/apps/hello/src/main.rs new file mode 100644 index 0000000..41cbbbd --- /dev/null +++ b/crates/apps/hello/src/main.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use core::arch::global_asm; + +global_asm!( + r#" +.section .text.entry +.globl _start +_start: + call main + j _start +"# +); + +#[unsafe(no_mangle)] +extern "C" fn main() -> ! { + userspace::syscall::exit(); +} + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index cbfc06b..ca07831 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -8,4 +8,5 @@ pub mod macros; pub mod permission; #[cfg(kani)] pub mod proofs; +pub mod syscall_abi; pub mod time; diff --git a/crates/types/src/syscall_abi.rs b/crates/types/src/syscall_abi.rs new file mode 100644 index 0000000..70f0079 --- /dev/null +++ b/crates/types/src/syscall_abi.rs @@ -0,0 +1,17 @@ +pub const SYS_SEND: usize = 0; +pub const SYS_RECEIVE: usize = 1; +pub const SYS_DELEGATE: usize = 2; +pub const SYS_WAIT_FOR_INTERRUPT: usize = 3; +pub const SYS_ACKNOWLEDGE_INTERRUPT: usize = 4; +pub const SYS_WAIT_ANY: usize = 5; +pub const SYS_CREATE_PROCESS: usize = 6; +pub const SYS_KILL: usize = 7; +pub const SYS_REVOKE: usize = 8; +pub const SYS_SPAWN_THREAD: usize = 9; +pub const SYS_EXIT_THREAD: usize = 10; +pub const SYS_REQUEST_PAGES: usize = 11; +pub const SYS_RELEASE_PAGES: usize = 12; +pub const SYS_GET_TIME: usize = 13; +pub const SYS_GET_PROCESS_ID: usize = 14; +pub const SYS_YIELD: usize = 15; +pub const SYS_EXIT: usize = 16; diff --git a/crates/userspace/Cargo.toml b/crates/userspace/Cargo.toml index e620937..6329ed9 100644 --- a/crates/userspace/Cargo.toml +++ b/crates/userspace/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } diff --git a/crates/userspace/src/lib.rs b/crates/userspace/src/lib.rs index b93cf3f..7b3b5ed 100644 --- a/crates/userspace/src/lib.rs +++ b/crates/userspace/src/lib.rs @@ -1,14 +1,3 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![no_std] -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod syscall; diff --git a/crates/userspace/src/syscall.rs b/crates/userspace/src/syscall.rs new file mode 100644 index 0000000..e135e96 --- /dev/null +++ b/crates/userspace/src/syscall.rs @@ -0,0 +1,105 @@ +use types::syscall_abi::*; + +#[inline(always)] +fn ecall0(nr: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + lateout("a0") ret, + ); + } + ret +} + +#[inline(always)] +fn ecall1(nr: usize, a0: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + ); + } + ret +} + +#[inline(always)] +fn ecall2(nr: usize, a0: usize, a1: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + ); + } + ret +} + +pub fn exit() -> ! { + ecall0(SYS_EXIT); + loop {} +} + +pub fn exit_thread() -> ! { + ecall0(SYS_EXIT_THREAD); + loop {} +} + +pub fn yield_now() { + ecall0(SYS_YIELD); +} + +pub fn get_time() -> usize { + ecall0(SYS_GET_TIME) +} + +pub fn get_process_id() -> usize { + ecall0(SYS_GET_PROCESS_ID) +} + +pub fn spawn_thread(entry: usize, stack: usize) -> usize { + ecall2(SYS_SPAWN_THREAD, entry, stack) +} + +pub fn request_pages(count: usize) -> usize { + ecall1(SYS_REQUEST_PAGES, count) +} + +pub fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") SYS_SEND, + inlateout("a0") endpoint => ret, + in("a1") w0, + in("a2") w1, + in("a3") w2, + in("a4") w3, + ); + } + ret +} + +pub fn receive(endpoint: usize) -> (usize, usize, usize, usize) { + let r0: usize; + let r1: usize; + let r2: usize; + let r3: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") SYS_RECEIVE, + inlateout("a0") endpoint => r0, + lateout("a1") r1, + lateout("a2") r2, + lateout("a3") r3, + ); + } + (r0, r1, r2, r3) +}