feat: userspace round-trip — syscall stubs, ecall dispatch, U-mode entry and exit on QEMU
This commit is contained in:
@@ -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<kernel::Kernel<platform::RiscVPlatform>> = None;
|
||||
|
||||
pub fn kernel_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
|
||||
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<platform::RiscVPlatform>,
|
||||
// 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<platform::RiscVPlatform>,
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user