feat(kernel): SystemControl cap for halt, board device table + boot caps to init, RegionAddress syscall, idle loop services irqs

This commit is contained in:
2026-10-26 23:59:57 +01:00
parent 130a82f50d
commit bafc18a5f8
21 changed files with 626 additions and 53 deletions

View File

@@ -8,3 +8,29 @@ pub const CLINT_BASE: usize = 0x02000000;
pub const USER_POOL_BASE: usize = 0x84000000;
pub const USER_POOL_PAGES: usize = 4096;
pub const TICK_INTERVAL: u64 = 10_000_000;
/// QEMU virt's PLIC lines.
const UART_IRQ: u16 = 10;
const VIRTIO_IRQ_BASE: u16 = 1;
const VIRTIO_SLOT_SIZE: usize = 0x1000;
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
/// Every device userspace may drive: the UART and each VirtIO MMIO slot.
/// The PLIC, CLINT and RAM stay the kernel's.
pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> {
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
let one = PageCount::new(1)?;
let device = |base: usize, irq: u16| kernel::BoardDevice {
base: PhysicalAddress::from_raw(base),
pages: one,
irq: Some(IrqId::from_raw(irq)),
};
let mut all = [device(UART_BASE, UART_IRQ); 1 + VIRTIO_SLOTS];
for (i, d) in all.iter_mut().skip(1).enumerate() {
*d = device(
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,
VIRTIO_IRQ_BASE + i as u16,
);
}
Some(all)
}

View File

@@ -10,7 +10,9 @@ use types::mapping::PagePerms;
// Boot-time device wiring. Everything here goes through the kernel's
// mapping API so the kernel knows about every page a driver can touch.
// P5 replaces these hardcoded grants with capabilities handed to init.
// Device regions come from the board table and belong to init, which
// also holds capabilities to them; moving drivers onto those caps is
// userspace work.
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() }
@@ -23,11 +25,16 @@ fn pages(uart: &Uart, n: usize) -> PageCount {
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
}
/// Maps device MMIO at `pa` into `pid` at `va`.
/// Maps the board device region at `pa` into `pid` at `va`. The region
/// belongs to init (see `kernel::Kernel::register_board`); `n` must match
/// its size.
pub(crate) fn map_mmio(uart: &Uart, pid: ProcessId, va: usize, pa: usize, n: usize) {
let id = kern()
.create_device_region(pid, PhysicalAddress::from_raw(pa), pages(uart, n))
.unwrap_or_else(|_| crate::halt(uart, "device region failed"));
.device_region_at(PhysicalAddress::from_raw(pa))
.unwrap_or_else(|| crate::halt(uart, "no such board device"));
if kern().region_pages(id).map(|p| p.value() as usize) != Some(n) {
crate::halt(uart, "device size mismatch");
}
kern()
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));

View File

@@ -17,6 +17,12 @@ use kernel_cell::KernelCell;
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
// SAFETY: single core; boot code runs before any trap can touch the
// kernel.
unsafe { &mut *kernel_ptr() }
}
pub fn kernel_ptr() -> *mut kernel::Kernel<platform::RiscVPlatform> {
KERNEL.as_ptr()
}
@@ -51,10 +57,18 @@ extern "C" fn kmain() -> ! {
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
let virtio_base = blk.base();
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
kern_mut()
.register_board(init.pid, &board)
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
devices::gpu_setup::configure(&uart, display.pid);
devices::input_setup::configure(&uart, input.pid);
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
// After grant_all, so the slots init already relies on stay put.
kern_mut()
.grant_boot_authority(init.pid, &board)
.unwrap_or_else(|_| halt(&uart, "boot authority failed"));
uart.write_str("k-os: entering userspace\n");
boot::loader::enter_userspace(init.pid, init.entry, SHARED_BUF_VA);

View File

@@ -218,32 +218,66 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
save_frame_to_context(frame, ctx);
}
if let Some(next_tid) = kern.pick_next_thread() {
let next_pid = kern.thread_process(next_tid);
let current_pid = kern.thread_process(current_tid);
let next_tid = next_runnable();
let kern = unsafe { &mut *crate::kernel_ptr() };
let next_pid = kern.thread_process(next_tid);
let current_pid = kern.thread_process(current_tid);
let core = types::id::CoreId::from_raw(0);
let _ = kern.set_thread_running(next_tid, core);
let core = types::id::CoreId::from_raw(0);
let _ = kern.set_thread_running(next_tid, core);
if let Some(ctx) = kern.thread_context(next_tid) {
load_context_to_frame(ctx, frame);
if let Some(ctx) = kern.thread_context(next_tid) {
load_context_to_frame(ctx, frame);
}
apply_pending_result(frame, next_tid);
if next_pid != current_pid
&& let Some(pid) = next_pid
{
let pt_addr = kern.platform_mut().page_table_for(pid);
if pt_addr != 0 {
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
crate::mmu::activate(pt);
}
}
}
apply_pending_result(frame, next_tid);
/// The next thread to run, idling until an interrupt makes one ready.
///
/// We are inside a trap, so SIE is clear and interrupts are not taken.
/// `wfi` still wakes once one is pending, and it is then serviced here by
/// polling `sip`, which is how a driver blocked on its IRQ notification
/// gets woken when nothing else can run.
fn next_runnable() -> types::id::ThreadId {
loop {
let kern = unsafe { &mut *crate::kernel_ptr() };
if let Some(tid) = kern.pick_next_thread() {
return tid;
}
unsafe { core::arch::asm!("wfi") };
let sip: usize;
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
if sip & (1 << CAUSE_S_TIMER) != 0 {
// No thread is running, so there is no time slice to charge.
crate::hw::timer::handle_interrupt();
}
if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
dispatch_external();
}
}
}
if next_pid != current_pid
&& let Some(pid) = next_pid
{
let pt_addr = kern.platform_mut().page_table_for(pid);
if pt_addr != 0 {
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
crate::mmu::activate(pt);
}
}
} else {
loop {
unsafe { core::arch::asm!("wfi") };
/// Claims every pending PLIC interrupt and hands each to the kernel.
fn dispatch_external() {
loop {
let irq_num = crate::hw::plic::Plic::claim();
if irq_num == 0 {
return;
}
let kern = unsafe { &mut *crate::kernel_ptr() };
kern.handle_interrupt(types::id::IrqId::from_raw(irq_num as u16));
crate::hw::plic::Plic::complete(irq_num);
}
}
@@ -272,15 +306,7 @@ fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
}
}
}
CAUSE_S_EXTERNAL => {
let irq_num = crate::hw::plic::Plic::claim();
if irq_num != 0 {
let kern = unsafe { &mut *crate::kernel_ptr() };
let irq = types::id::IrqId::from_raw(irq_num as u16);
kern.handle_interrupt(irq);
crate::hw::plic::Plic::complete(irq_num);
}
}
CAUSE_S_EXTERNAL => dispatch_external(),
_ => {}
}
}