From dbec3842facdb7cfd5aba31bd243370b992ca7fa Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 19 Oct 2026 00:53:31 +0200 Subject: [PATCH] feat: create libkos crate with core modules (syscall, alloc, rt) --- Cargo.toml | 4 + crates/libkos/Cargo.toml | 7 ++ crates/libkos/src/alloc_impl.rs | 141 ++++++++++++++++++++++++++++++++ crates/libkos/src/arch/mock.rs | 27 ++++++ crates/libkos/src/arch/mod.rs | 9 ++ crates/libkos/src/arch/riscv.rs | 106 ++++++++++++++++++++++++ crates/libkos/src/lib.rs | 12 +++ crates/libkos/src/rt.rs | 21 +++++ crates/libkos/src/syscall.rs | 85 +++++++++++++++++++ 9 files changed, 412 insertions(+) create mode 100644 crates/libkos/Cargo.toml create mode 100644 crates/libkos/src/alloc_impl.rs create mode 100644 crates/libkos/src/arch/mock.rs create mode 100644 crates/libkos/src/arch/mod.rs create mode 100644 crates/libkos/src/arch/riscv.rs create mode 100644 crates/libkos/src/lib.rs create mode 100644 crates/libkos/src/rt.rs create mode 100644 crates/libkos/src/syscall.rs diff --git a/Cargo.toml b/Cargo.toml index b32345e..8bbea9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "crates/drivers/input/uart", "crates/drivers/input/virtio", "crates/hw-virtio", + "crates/libkos", ] resolver = "2" @@ -99,6 +100,9 @@ user-manager = { path = "crates/servers/user-manager" } # hw hw-virtio = { path = "crates/hw-virtio" } +# libkos +libkos = { path = "crates/libkos" } + # ports port-framebuffer = { path = "crates/ports/framebuffer" } port-block-device = { path = "crates/ports/block-device" } diff --git a/crates/libkos/Cargo.toml b/crates/libkos/Cargo.toml new file mode 100644 index 0000000..b112228 --- /dev/null +++ b/crates/libkos/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "libkos" +version = "0.1.0" +edition = "2024" + +[dependencies] +types = { workspace = true } diff --git a/crates/libkos/src/alloc_impl.rs b/crates/libkos/src/alloc_impl.rs new file mode 100644 index 0000000..db98bac --- /dev/null +++ b/crates/libkos/src/alloc_impl.rs @@ -0,0 +1,141 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::cell::UnsafeCell; +use core::ptr; + +const PAGE_SIZE: usize = 4096; +const MAX_PAGES: usize = 64; + +struct FreeBlock { + size: usize, + next: *mut FreeBlock, +} + +pub struct KosAllocator { + inner: UnsafeCell, +} + +struct AllocatorInner { + free_list: *mut FreeBlock, + pages: [usize; MAX_PAGES], + page_count: usize, +} + +unsafe impl Sync for KosAllocator {} + +impl KosAllocator { + pub const fn new() -> Self { + Self { + inner: UnsafeCell::new(AllocatorInner { + free_list: ptr::null_mut(), + pages: [0; MAX_PAGES], + page_count: 0, + }), + } + } + + fn inner_mut(&self) -> &mut AllocatorInner { + unsafe { &mut *self.inner.get() } + } +} + +impl AllocatorInner { + fn request_page(&mut self) -> *mut u8 { + if self.page_count >= MAX_PAGES { + return ptr::null_mut(); + } + + let count = match types::bounded::PageCount::new(1) { + Some(c) => c, + None => return ptr::null_mut(), + }; + let va = match crate::syscall::request_pages(count) { + Some(va) => va, + None => return ptr::null_mut(), + }; + + self.pages[self.page_count] = va.raw(); + self.page_count += 1; + va.raw() as *mut u8 + } + + fn alloc_from_free_list(&mut self, size: usize, align: usize) -> *mut u8 { + let mut prev: *mut FreeBlock = ptr::null_mut(); + let mut current = self.free_list; + + while !current.is_null() { + let block = unsafe { &mut *current }; + let addr = current as usize; + let aligned = (addr + align - 1) & !(align - 1); + let padding = aligned - addr; + + if block.size >= size + padding { + let remaining = block.size - size - padding; + + if remaining >= core::mem::size_of::() { + let new_block = (aligned + size) as *mut FreeBlock; + unsafe { + (*new_block).size = remaining; + (*new_block).next = block.next; + } + if prev.is_null() { + self.free_list = new_block; + } else { + unsafe { (*prev).next = new_block; } + } + } else { + if prev.is_null() { + self.free_list = block.next; + } else { + unsafe { (*prev).next = block.next; } + } + } + + return aligned as *mut u8; + } + + prev = current; + current = block.next; + } + + ptr::null_mut() + } + + fn add_to_free_list(&mut self, ptr: *mut u8, size: usize) { + let block = ptr as *mut FreeBlock; + unsafe { + (*block).size = size; + (*block).next = self.free_list; + } + self.free_list = block; + } +} + +unsafe impl GlobalAlloc for KosAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let inner = self.inner_mut(); + let size = layout.size().max(core::mem::size_of::()); + let align = layout.align(); + + let ptr = inner.alloc_from_free_list(size, align); + if !ptr.is_null() { + return ptr; + } + + let pages_needed = (size + PAGE_SIZE - 1) / PAGE_SIZE; + for _ in 0..pages_needed { + let page = inner.request_page(); + if page.is_null() { + return ptr::null_mut(); + } + inner.add_to_free_list(page, PAGE_SIZE); + } + + inner.alloc_from_free_list(size, align) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let inner = self.inner_mut(); + let size = layout.size().max(core::mem::size_of::()); + inner.add_to_free_list(ptr, size); + } +} diff --git a/crates/libkos/src/arch/mock.rs b/crates/libkos/src/arch/mock.rs new file mode 100644 index 0000000..4aef986 --- /dev/null +++ b/crates/libkos/src/arch/mock.rs @@ -0,0 +1,27 @@ +pub fn syscall0(_nr: usize) -> usize { + 0 +} + +pub fn syscall1(_nr: usize, _a0: usize) -> usize { + 0 +} + +pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize { + 0 +} + +pub fn syscall3(_nr: usize, _a0: usize, _a1: usize, _a2: usize) -> usize { + 0 +} + +pub fn syscall4(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize) -> usize { + 0 +} + +pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize { + 0 +} + +pub fn syscall_receive(_nr: usize, _a0: usize) -> (usize, usize, usize, usize) { + (0, 0, 0, 0) +} diff --git a/crates/libkos/src/arch/mod.rs b/crates/libkos/src/arch/mod.rs new file mode 100644 index 0000000..35687a5 --- /dev/null +++ b/crates/libkos/src/arch/mod.rs @@ -0,0 +1,9 @@ +#[cfg(all(target_arch = "riscv64", target_os = "none"))] +mod riscv; +#[cfg(all(target_arch = "riscv64", target_os = "none"))] +pub use riscv::*; + +#[cfg(not(target_os = "none"))] +mod mock; +#[cfg(not(target_os = "none"))] +pub use mock::*; diff --git a/crates/libkos/src/arch/riscv.rs b/crates/libkos/src/arch/riscv.rs new file mode 100644 index 0000000..60ba1b2 --- /dev/null +++ b/crates/libkos/src/arch/riscv.rs @@ -0,0 +1,106 @@ +#[inline(always)] +pub fn syscall0(nr: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + lateout("a0") ret, + ); + } + ret +} + +#[inline(always)] +pub fn syscall1(nr: usize, a0: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + ); + } + ret +} + +#[inline(always)] +pub fn syscall2(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 +} + +#[inline(always)] +pub fn syscall3(nr: usize, a0: usize, a1: usize, a2: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + in("a2") a2, + ); + } + ret +} + +#[inline(always)] +pub fn syscall4(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + in("a2") a2, + in("a3") a3, + ); + } + ret +} + +#[inline(always)] +pub fn syscall5(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + in("a2") a2, + in("a3") a3, + in("a4") a4, + ); + } + ret +} + +#[inline(always)] +pub fn syscall_receive(nr: usize, a0: usize) -> (usize, usize, usize, usize) { + let r0: usize; + let r1: usize; + let r2: usize; + let r3: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => r0, + lateout("a1") r1, + lateout("a2") r2, + lateout("a3") r3, + ); + } + (r0, r1, r2, r3) +} diff --git a/crates/libkos/src/lib.rs b/crates/libkos/src/lib.rs new file mode 100644 index 0000000..06663cc --- /dev/null +++ b/crates/libkos/src/lib.rs @@ -0,0 +1,12 @@ +#![no_std] + +extern crate alloc; + +mod arch; +pub mod syscall; +pub mod alloc_impl; +mod rt; + +#[cfg(target_os = "none")] +#[global_allocator] +static ALLOCATOR: alloc_impl::KosAllocator = alloc_impl::KosAllocator::new(); diff --git a/crates/libkos/src/rt.rs b/crates/libkos/src/rt.rs new file mode 100644 index 0000000..b57ef14 --- /dev/null +++ b/crates/libkos/src/rt.rs @@ -0,0 +1,21 @@ +// Architecture-specific entry points. +// To add a new architecture: add a cfg block with the appropriate global_asm. + +#[cfg(all(target_arch = "riscv64", target_os = "none"))] +core::arch::global_asm!( + r#" +.section .text.entry +.globl _start +_start: + call main + li a7, 16 + ecall + j _start +"# +); + +#[cfg(target_os = "none")] +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + crate::syscall::exit(); +} diff --git a/crates/libkos/src/syscall.rs b/crates/libkos/src/syscall.rs new file mode 100644 index 0000000..1299eb6 --- /dev/null +++ b/crates/libkos/src/syscall.rs @@ -0,0 +1,85 @@ +use crate::arch; +use types::syscall_abi::*; +use types::address::{EntryPoint, StackPointer, VirtualAddress}; +use types::bounded::{CapSlot, PageCount}; +use types::id::ProcessId; +use types::permission::PermissionSet; +use types::time::Ticks; + +pub fn exit() -> ! { + arch::syscall0(SYS_EXIT); + loop {} +} + +pub fn exit_thread() -> ! { + arch::syscall0(SYS_EXIT_THREAD); + loop {} +} + +pub fn yield_now() { + arch::syscall0(SYS_YIELD); +} + +pub fn halt() -> ! { + arch::syscall0(SYS_HALT); + loop {} +} + +pub fn get_time() -> Ticks { + Ticks::from_raw(arch::syscall0(SYS_GET_TIME) as u64) +} + +pub fn get_process_id() -> ProcessId { + ProcessId::from_raw(arch::syscall0(SYS_GET_PROCESS_ID) as u16) +} + +pub fn create_process(create_cap: CapSlot) -> Option { + let r = arch::syscall1(SYS_CREATE_PROCESS, create_cap.value() as usize); + if r == usize::MAX { None } else { CapSlot::new(r as u8) } +} + +pub fn create_endpoint() -> Option { + let r = arch::syscall0(SYS_CREATE_ENDPOINT); + if r == usize::MAX { None } else { CapSlot::new(r as u8) } +} + +pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { + arch::syscall2(SYS_SPAWN_THREAD, entry.raw(), stack.raw()) +} + +pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize { + arch::syscall3(SYS_SPAWN_THREAD_IN, process_cap.value() as usize, entry.raw(), stack.raw()) +} + +pub fn request_pages(count: PageCount) -> Option { + let r = arch::syscall1(SYS_REQUEST_PAGES, count.value() as usize); + if r == usize::MAX { None } else { Some(VirtualAddress::from_raw(r)) } +} + +pub fn map_page(process_cap: CapSlot, src_va: VirtualAddress, dst_va: VirtualAddress, executable: bool) -> bool { + arch::syscall4(SYS_MAP_PAGE, process_cap.value() as usize, src_va.raw(), dst_va.raw(), executable as usize) == 0 +} + +pub fn send(endpoint: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> usize { + arch::syscall5(SYS_SEND, endpoint.value() as usize, w0, w1, w2, w3) +} + +pub fn receive(endpoint: CapSlot) -> (usize, usize, usize, usize) { + arch::syscall_receive(SYS_RECEIVE, endpoint.value() as usize) +} + +pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool { + arch::syscall3(SYS_DELEGATE, slot.value() as usize, to.value() as usize, permissions.bits() as usize) == 0 +} + +pub fn kill(process_cap: CapSlot) -> bool { + arch::syscall1(SYS_KILL, process_cap.value() as usize) == 0 +} + +pub fn revoke(slot: CapSlot) -> bool { + arch::syscall1(SYS_REVOKE, slot.value() as usize) == 0 +} + +pub fn debug_putchar(byte: u8) { + arch::syscall1(SYS_DEBUG_PUTCHAR, byte as usize); +}