diff --git a/.gdbinit b/.gdbinit index d4d99b1..b70b1fd 100644 --- a/.gdbinit +++ b/.gdbinit @@ -1,4 +1,6 @@ file target/riscv64/debug/kernel-rust target remote localhost:1234 break machine_mode_entry +# break *0x800dd1d8 +# add-symbol-file target/riscv64/debug/test_pic 0x800dd1d8 c diff --git a/.gitignore b/.gitignore index e470ada..55983a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -.helix +.helix/ +.zed/ **/target **/Cargo.lock diff --git a/crates/os-std/src/lib.rs b/crates/os-std/src/lib.rs index 2776b04..c821822 100644 --- a/crates/os-std/src/lib.rs +++ b/crates/os-std/src/lib.rs @@ -1,12 +1,34 @@ #![no_std] -mod prelude; +extern crate alloc; + +pub mod prelude; pub use shared::syscall; #[macro_export] macro_rules! custom_std_setup { () => { + use $crate::prelude::*; + + extern crate alloc; + + struct GlobalAllocator; + + #[global_allocator] + static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator; + + unsafe impl core::alloc::GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 { + syscall::write_string_temp("Alloc user called"); + $crate::syscall::alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) { + $crate::syscall::dealloc(ptr, layout) + } + } + #[panic_handler] fn panic(_panic_info: &core::panic::PanicInfo) -> ! { // TODO print @@ -19,3 +41,21 @@ macro_rules! custom_std_setup { } }; } + +#[macro_export] +macro_rules! print { + ($($args:expr),*) => { + $crate::syscall::write_string_temp(&format!($($args),*)) + }; +} +#[macro_export] +macro_rules! println { + () => { + // $crate::print!(""); + $crate::print!("\n\r"); + }; + ($($args:expr),*) => { + $crate::print!($($args),*); + $crate::println!(); + }; +} diff --git a/crates/os-std/src/prelude.rs b/crates/os-std/src/prelude.rs index 8b13789..48ffcc0 100644 --- a/crates/os-std/src/prelude.rs +++ b/crates/os-std/src/prelude.rs @@ -1 +1,5 @@ - +pub use crate::print; +pub use crate::println; +pub use alloc::format; +pub use alloc::string::String; +pub use alloc::vec; diff --git a/crates/shared/src/syscall.rs b/crates/shared/src/syscall.rs index f8b2e98..ccf37e4 100644 --- a/crates/shared/src/syscall.rs +++ b/crates/shared/src/syscall.rs @@ -1,7 +1,9 @@ -use core::time::Duration; +use core::{alloc::Layout, time::Duration}; #[repr(u64)] pub enum SysCall { + Alloc = 40, + Dealloc = 41, Exit = 60, NanoSleep = 101, WriteIntTemp = 998, @@ -12,6 +14,8 @@ pub enum SysCall { impl From for SysCall { fn from(value: u64) -> Self { match value { + 40 => SysCall::Alloc, + 41 => SysCall::Dealloc, 60 => SysCall::Exit, 101 => SysCall::NanoSleep, 998 => SysCall::WriteIntTemp, @@ -90,7 +94,7 @@ pub fn sleep(duration: Duration) { } } -pub fn write_string_temp(content: &'static str) { +pub fn write_string_temp(content: &str) { unsafe { syscall!( SysCall::WriteTemp, @@ -104,3 +108,19 @@ pub fn write_int_temp(content: u64) { syscall!(SysCall::WriteIntTemp, content); } } + +pub fn alloc(layout: Layout) -> *mut u8 { + unsafe { + let size = layout.size(); + let align = layout.align(); + let (ptr, ..) = syscall!(SysCall::Alloc, size as u64, align as u64); + ptr as *mut u8 + } +} +pub fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) { + unsafe { + let size = layout.size(); + let align = layout.align(); + syscall!(SysCall::Dealloc, ptr as u64, size as u64, align as u64); + } +} diff --git a/justfile b/justfile index a758907..ea5ec06 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,7 @@ sync_filesystem: sync build_user_prog prog: - RUSTFLAGS="-C relocation-model=pic -C link-arg=-Tilm.ld" cargo b {{ cargo_flags }} --package {{ prog }} + RUSTFLAGS="-C relocation-model=pic -C link-arg=-Tuser.ld" cargo b {{ cargo_flags }} --package {{ prog }} riscv64-elf-objcopy -O binary {{ "target/riscv64/debug" / prog }} {{ "mnt/usr/bin" / prog }} build: mount_filesystem (map_dir "user" "build_user_prog") diff --git a/src/boot.rs b/src/boot.rs index 344cb67..5fb3487 100644 --- a/src/boot.rs +++ b/src/boot.rs @@ -1,3 +1,7 @@ +//! Early boot and mode transition helpers. +//! +//! Contains the machine-mode startup code that sets up trap handlers, delegates +//! interrupts, and transitions into supervisor mode. use core::arch::naked_asm; use crate::{ diff --git a/src/boot/sbi.rs b/src/boot/sbi.rs index 3d4eab1..db019f7 100644 --- a/src/boot/sbi.rs +++ b/src/boot/sbi.rs @@ -1,3 +1,7 @@ +//! Supervisor Binary Interface (SBI) identifiers. +//! +//! Simple definitions for SBI extension and function identifiers used by the +//! kernel when interacting with machine-mode services. #[non_exhaustive] #[repr(usize)] pub enum EextensionID { diff --git a/src/critical_section.rs b/src/critical_section.rs index 8948b18..6a8ce1e 100644 --- a/src/critical_section.rs +++ b/src/critical_section.rs @@ -1,3 +1,7 @@ +//! Critical section implementation for supervisor mode. +//! +//! Provides a small critical-section implementation that disables and restores +//! supervisor interrupts for short atomic regions. use critical_section::RawRestoreState; use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt}; diff --git a/src/fs.rs b/src/fs.rs index 25eb4bf..b64a26e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,3 +1,7 @@ +//! Simple wrapper around a FAT32 image exposed to the kernel. +//! +//! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by +//! the kernel to load user binaries. use core::{cell::UnsafeCell, ops::Deref}; use bffs::{ @@ -7,10 +11,18 @@ use bffs::{ const DISK_ADDR: *const u8 = 0x9000_0000 as *const _; +/// Lazy holder for the kernel's filesystem instance. +/// +/// The inner `UnsafeCell` allows one-time initialization at early boot while +/// exposing a shared `&'static` reference through `Deref` once initialized. pub struct FSTemp(UnsafeCell>>); unsafe impl Sync for FSTemp {} impl FSTemp { + /// Initialize the global filesystem from the in-memory disk image. + /// + /// Safety: must be called exactly once during early kernel initialization + /// before any other filesystem operations occur. pub unsafe fn init(&self) { unsafe { *self.0.get() = Some(Fat32FileSystem::new(Disk::new(1024 * 1024 * 16)).unwrap()); @@ -29,12 +41,17 @@ impl Deref for FSTemp { pub static FILE_SYSTEM: FSTemp = FSTemp(UnsafeCell::new(None)); #[derive(Debug)] +/// Simple disk backend that reads from a fixed memory region. +/// +/// The `Disk` struct provides `Read` and `Seek` implementations over a +/// contiguous in-memory image exposed at DISK_ADDR. pub struct Disk { pos: u64, size: u64, } impl Disk { + /// Create a new `Disk` representing an in-memory image of `size` bytes. pub fn new(size: u64) -> Self { Self { pos: 0, size } } @@ -56,12 +73,15 @@ impl Seek for Disk { } impl Read for Disk { + /// Read bytes from the in-memory disk image into `buf`. fn read(&mut self, buf: &mut [u8]) -> Result> { if self.pos >= self.size { return Ok(0); } let size = usize::min(buf.len(), (self.size - self.pos) as usize); - (0..size).for_each(|i| buf[i] = unsafe { *DISK_ADDR.byte_add(i + self.pos as usize) }); + for i in 0..size { + buf[i] = unsafe { *DISK_ADDR.byte_add(i + self.pos as usize) }; + } self.pos += size as u64; Ok(size) } diff --git a/src/interrupt.rs b/src/interrupt.rs index 104b057..7326be1 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,3 +1,8 @@ +//! +//! Trap handling and syscall dispatch. +//! +//! This module contains the low-level trap handlers for machine and supervisor +//! modes and the syscall dispatch implementation used by user processes. use alloc::str; use log::info; use shared::syscall::SysCall; @@ -13,11 +18,16 @@ use crate::{ time::{setup_next_timer_interrupt, IRQ_M_TIMER}, write_csr, }; -use core::{arch::naked_asm, time::Duration}; +use core::{alloc::Layout, arch::naked_asm, time::Duration}; use crate::time::{setup_timer_interrupt, timer_interrupt}; #[unsafe(no_mangle)] +/// Machine-mode trap handler. +/// +/// Handles synchronous exceptions and SBI calls that occur while running in +/// machine mode. This function decodes `mcause` and either handles the +/// condition or forwards it to a panic. unsafe extern "C" fn machine_trap_handler( mcause: u64, mie: u64, @@ -86,6 +96,10 @@ unsafe extern "C" fn machine_trap_handler( } #[unsafe(no_mangle)] +/// Supervisor-mode trap handler and syscall dispatcher. +/// +/// Handles exceptions and interrupts coming from supervisor mode, performs +/// syscall decoding, and invokes the scheduler when needed. unsafe extern "C" fn supervisor_trap_handler( mut interrupt_state: *mut ExecutionContext, scause: u64, @@ -105,8 +119,20 @@ unsafe extern "C" fn supervisor_trap_handler( let syscall_u64: u64 = unsafe { (*interrupt_state).a[0] }; let a1: u64 = unsafe { (*interrupt_state).a[1] }; let a2: u64 = unsafe { (*interrupt_state).a[2] }; + let a3: u64 = unsafe { (*interrupt_state).a[3] }; let syscall: SysCall = syscall_u64.into(); match syscall { + SysCall::Alloc => { + let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap(); + // Allocate memory and put the pointer in a0 + unsafe { (*interrupt_state).a[0] = alloc::alloc::alloc(layout) as u64 }; + } + SysCall::Dealloc => { + let ptr = a1 as *mut u8; + let layout = Layout::from_size_align(a2 as usize, a3 as usize).unwrap(); + // Free memory + unsafe { alloc::alloc::dealloc(ptr, layout) }; + } SysCall::Exit => exit_process(&mut interrupt_state), SysCall::NanoSleep => sleep(Duration::new(a1, a2 as u32), &mut interrupt_state), SysCall::WriteTemp => { @@ -146,10 +172,13 @@ unsafe extern "C" fn supervisor_trap_handler( interrupt_state } +/// Install the machine-mode trap entry point and enable timer interrupts. pub unsafe fn setup_machine_trap_handler() { write_csr!(mtvec, _machine_mode_trap); set_csr!(mie, IRQ_M_TIMER); } + +/// Install the supervisor-mode trap entry point and configure periodic timer. pub unsafe fn setup_supervisor_trap_handler() { write_csr!(stvec, _supervisor_mode_trap); setup_timer_interrupt(); @@ -157,6 +186,11 @@ pub unsafe fn setup_supervisor_trap_handler() { #[unsafe(naked)] #[unsafe(no_mangle)] +/// Low-level machine-mode trap entry (assembly stub). +/// +/// Saves the machine-mode callee-saved context, constructs a stack frame and +/// calls `machine_trap_handler` in Rust. Implemented as a naked function +/// with inline assembly. unsafe extern "C" fn _machine_mode_trap() { naked_asm!( " @@ -210,6 +244,10 @@ unsafe extern "C" fn _machine_mode_trap() { } #[unsafe(naked)] #[unsafe(no_mangle)] +/// Low-level supervisor-mode trap entry (assembly stub). +/// +/// This stub saves the full register state and forwards control to +/// `supervisor_trap_handler` implemented in Rust. unsafe extern "C" fn _supervisor_mode_trap() { naked_asm!(concat!( " @@ -267,6 +305,7 @@ unsafe extern "C" fn _supervisor_mode_trap() { #[unsafe(naked)] #[unsafe(no_mangle)] +/// Restore a saved execution context and perform `sret` to return to user code. pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! { naked_asm!(concat!( " diff --git a/src/io.rs b/src/io.rs index 5c9dcce..16db2ae 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,16 +1,25 @@ +//! Kernel I/O helpers and logging frontend. +//! +//! Provides a lightweight logger implementation routing to UART and helper +//! macros for printing from kernel code. use crate::println; use alloc::format; -use alloc::string::String; + use log::{Level, Metadata, Record}; use log::{LevelFilter, SetLoggerError}; use crate::uart::write_uart; -pub(crate) fn print(content: String) { +/// Print a string to the kernel console (via UART). +/// +/// Accepts any type that implements `AsRef` to avoid unnecessary +/// allocations at call sites. +pub(crate) fn print>(content: T) { write_uart(content); } +/// Logger implementation that routes kernel log records to the UART-based console. struct Logger; impl log::Log for Logger { @@ -39,6 +48,9 @@ impl log::Log for Logger { static LOGGER: Logger = Logger; +/// Initialize the kernel logger and set the default level. +/// +/// Returns an error if a global logger has already been set. pub fn init_log() -> Result<(), SetLoggerError> { log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info)) } diff --git a/src/main.rs b/src/main.rs index 12b66cb..d225bb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +//! Kernel initialization and supervisor entry point. +//! +//! This module sets up the global heap, initializes core subsystems (VGA, filesystem, +//! scheduler, and logging), and starts initial processes. #![no_std] #![no_main] #![allow(static_mut_refs)] @@ -43,7 +47,7 @@ pub const HEAP_SIZE: usize = 1024 * 1024; // 1Mo RAM static HEAP: Heap = Heap::empty(); // Usize is assumed to be an u64 in the whole kernel -const _: () = assert!(size_of::() == size_of::()); +const _: () = assert!(core::mem::size_of::() == core::mem::size_of::()); #[unsafe(no_mangle)] pub extern "C" fn supervisor_mode_entry() { diff --git a/src/panic_handler.rs b/src/panic_handler.rs index 3233194..9b868ca 100644 --- a/src/panic_handler.rs +++ b/src/panic_handler.rs @@ -1,3 +1,7 @@ +//! Panic handler and diagnostic display. +//! +//! Prints panic information to the kernel log and displays the message on the +//! framebuffer before halting the CPU. use core::arch::riscv64::wfi; use alloc::{format, string::ToString}; @@ -6,6 +10,7 @@ use log::error; use crate::vga::{Color, Vga, FONT_HEIGHT}; #[panic_handler] +/// Kernel panic handler that displays the panic message on the framebuffer and halts. fn panic(panic_info: &core::panic::PanicInfo) -> ! { error!("PANIC !"); let mut panic_message = panic_info.message().to_string(); diff --git a/src/process.rs b/src/process.rs index 2db9167..6d3f7e5 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,3 +1,12 @@ +//! Process management module for the operating system. +//! +//! This module provides the structures and functions necessary to create, +//! manage and schedule processes in the kernel. It defines the `Process` and +//! `ExecutionContext` types and helper functions to create processes from +//! in-memory functions or binaries on the filesystem. The module intentionally +//! keeps unsafe usage localized and documented where raw pointers or transmute +//! are required. + use core::time::Duration; use alloc::{boxed::Box, format, string::String, vec::Vec}; @@ -6,41 +15,78 @@ use shared::syscall::exit; use crate::{ fs::FILE_SYSTEM, - scheduler::{scheduler_without_ret, ACTIVE_PID, PROCESS_COUNT, PROCESS_TABLE}, + println, + scheduler::{ACTIVE_PID, PROCESS_COUNT, PROCESS_TABLE, scheduler_without_ret}, time::elapsed_time_since_startup, }; +/// Size of the stack allocated to each process (in 64-bit words). const STACK_SIZE: usize = 4096; +/// MSTATUS bit to enable supervisor mode interrupts. +const MSTATUS_SPIE: u64 = 1 << 5; + +/// MSTATUS bit to set previous privilege mode to supervisor. +const MSTATUS_SPP: u64 = 1 << 1; + +/// Represents the state of a process in the system. #[derive(Debug, PartialEq, Eq)] pub enum ProcessState { + /// The process is currently executing. Active, + /// The process is ready to execute and waiting to be scheduled. Activable, + /// The process has terminated and its slot can be reused. Dead, + /// The process is sleeping until a specific wake time. Asleep, } +/// Execution context saved during a context switch. +/// +/// This structure contains all RISC-V registers that must be +/// preserved during an interrupt or process switch. #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct ExecutionContext { + /// Return address register. pub ra: *const u64, + /// Stack pointer register. pub sp: *const u64, + /// Global pointer register. pub gp: u64, + /// Thread pointer register. pub tp: u64, + /// Argument/return value registers (a0-a7). pub a: [u64; 8], + /// Temporary registers (t0-t6). pub t: [u64; 7], + /// Saved registers (s0-s11). pub s: [u64; 12], + /// Machine exception program counter. pub mepc: *const u64, + /// Machine status register. pub mstatus: u64, } +/// Represents a process in the system. +/// +/// Each process has its own execution context, stack, +/// and metadata for scheduling. pub struct Process { + /// Unique process identifier. pub pid: i64, + /// Descriptive name of the process. pub name: String, + /// Current state of the process. pub state: ProcessState, + /// Optional entry point for the process code. pub entry: Option<&'static dyn Fn()>, + /// Wake time for sleeping processes. pub wake_time: Duration, + /// Saved execution context. pub ctx: ExecutionContext, + /// Process stack. pub stack: [u64; STACK_SIZE], } @@ -57,64 +103,190 @@ impl core::fmt::Debug for Process { } } +/// Creates a process from a binary file. +/// +/// # Arguments +/// +/// * `path` - Path to the executable binary file. +/// +/// # Returns +/// +/// Returns the PID of the created process, or -1 on failure. +/// +/// # Safety +/// +/// This function uses `unsafe` to transmute the file content into an +/// executable function. The binary must be in the correct format and +/// conform to the expected ABI. +/// Create a process from an executable binary located on the filesystem. +/// +/// Attempts to open `path`, load its contents into memory and create a new +/// kernel process that will execute the loaded binary. Returns the PID of the +/// created process, or -1 on failure. pub fn create_process_from_file<'a, T: Into>>(path: T) -> i64 { let path = path.into(); let name = path.as_str(); + + // Open and read the binary file let mut bin = FILE_SYSTEM.open_file(path).unwrap(); let mut content: Vec = Vec::new(); bin.read_to_end(&mut content).unwrap(); - let test = + + println!("Loading binary at address: {:x?}", content.as_ptr()); + + // SAFETY: Convert raw bytes into an executable function. + // The binary must be valid RISC-V machine code. + let entry_point = unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(Vec::leak(content).as_ptr()) }; - let test = Box::leak(Box::new(move || { - test(); + // Create a wrapper for the entry point function + let wrapper = Box::leak(Box::new(move || { + entry_point(); })); - create_process(test, name) + + create_process(wrapper, name) } +/// Creates a new process with the specified code and name. +/// +/// # Arguments +/// +/// * `code` - Static reference to the function to execute. +/// * `name` - Name of the process (for identification). +/// +/// # Returns +/// +/// Returns the PID of the created process, or -1 if the process table is full. +/// +/// # Safety +/// +/// This function manipulates the global process table and initializes +/// the execution context using unsafe operations. +/// Create a new process from a function pointer. +/// +/// The provided `code` function will be executed when the process is first +/// scheduled. Returns the new PID, or -1 if the process table is full. pub fn create_process, F: Fn()>(code: &'static F, name: T) -> i64 { + // Search for a free slot in the process table let mut next_pid = 0; while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead } { next_pid += 1; } + // Check if a slot is available if next_pid >= PROCESS_COUNT { - return -1; + return -1; // Process table is full } + // SAFETY: Initializing process in the global table. + // Access is safe because we verified bounds and found a Dead slot. unsafe { - PROCESS_TABLE[next_pid].pid = next_pid as i64; - PROCESS_TABLE[next_pid].name = name.into(); - PROCESS_TABLE[next_pid].state = ProcessState::Activable; - PROCESS_TABLE[next_pid].entry = Some(code); - PROCESS_TABLE[next_pid].ctx.a[0] = - PROCESS_TABLE[next_pid].entry.as_ref().unwrap_unchecked() as *const &dyn Fn() as u64; - PROCESS_TABLE[next_pid].ctx.mepc = process_launcher as *const _; - PROCESS_TABLE[next_pid].ctx.mstatus = 1 << 1 | 1 << 5; - PROCESS_TABLE[next_pid].ctx.sp = &raw const PROCESS_TABLE[next_pid].stack[STACK_SIZE - 1]; + let process = &mut PROCESS_TABLE[next_pid]; + + // Configure process metadata + process.pid = next_pid as i64; + process.name = name.into(); + process.state = ProcessState::Activable; + process.entry = Some(code); + + // Configure execution context + // a0 contains the pointer to the function to execute + process.ctx.a[0] = process.entry.as_ref().unwrap_unchecked() as *const &dyn Fn() as u64; + + // mepc points to process_launcher which will call the function + process.ctx.mepc = process_launcher as *const _; + + // Configure mstatus for supervisor mode with interrupts enabled + process.ctx.mstatus = MSTATUS_SPP | MSTATUS_SPIE; + + // Initialize stack pointer at the top of the stack + process.ctx.sp = &raw const process.stack[STACK_SIZE - 1]; } next_pid as i64 } +/// Entry point to launch a new process. +/// +/// This function is automatically called during the first scheduling +/// of a process. It executes the process code and calls `exit()` +/// if the code doesn't terminate explicitly. +/// +/// # Arguments +/// +/// * `code` - Pointer to the function to execute. +/// +/// # Safety +/// +/// This function must be called with a valid pointer to a function. +/// Internal launcher used as the initial program counter for new processes. +/// +/// This function is installed into the process `mepc` so that when the new +/// process is scheduled it will run this launcher which calls the user +/// function and ensures the process exits cleanly. extern "C" fn process_launcher(code: *const &dyn Fn()) { + // SAFETY: The code pointer was initialized in create_process + // and points to a valid function. unsafe { (*code)() }; - // User code didn't exit before the end of its execution, so we call the exit syscall ourselves + + // If user code didn't exit explicitly, call exit() to clean up the process exit(); } +/// Terminates the currently active process. +/// +/// This function marks the active process as dead and triggers +/// the scheduler to switch to another process. +/// +/// # Arguments +/// +/// * `interrupt_context` - Interrupt context for state saving. +/// +/// # Note +/// +/// This function never returns as it transfers control to +/// another process via the scheduler. +/// Terminate the currently active process and switch to the scheduler. +/// +/// Marks the active process as dead and transfers control to the scheduler +/// to select the next runnable process. This function does not return. pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) { + // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. unsafe { PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead; } + + // Transfer control to the scheduler (does not return) scheduler_without_ret(interrupt_context) } +/// Puts the active process to sleep for a specified duration. +/// +/// The process will be automatically woken up by the scheduler when +/// the wake time is reached. +/// +/// # Arguments +/// +/// * `duration` - Duration of the sleep. +/// * `interrupt_context` - Interrupt context for state saving. +/// +/// # Note +/// +/// This function never returns as it transfers control to +/// another process via the scheduler. +/// Put the active process to sleep for `duration` and schedule the next runnable process. +/// +/// The wake time is computed from the current uptime; the scheduler will +/// reactivate the process when the wake time is reached. pub fn sleep(duration: Duration, interrupt_context: &mut *mut ExecutionContext) { + // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. unsafe { - PROCESS_TABLE[ACTIVE_PID].wake_time = elapsed_time_since_startup() + duration; - PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Asleep; + let process = &mut PROCESS_TABLE[ACTIVE_PID]; + process.wake_time = elapsed_time_since_startup() + duration; + process.state = ProcessState::Asleep; } + + // Transfer control to the scheduler (does not return) scheduler_without_ret(interrupt_context) } diff --git a/src/riscv.rs b/src/riscv.rs index 21fdd5d..791464f 100644 --- a/src/riscv.rs +++ b/src/riscv.rs @@ -1,3 +1,7 @@ +//! RISC-V CSR helpers and interrupt utilities. +//! +//! Small helpers to read/modify control and status registers and manage +//! interrupt enable/disable states. #![allow(unused)] use core::arch::naked_asm; @@ -18,24 +22,34 @@ impl SStatus { pub const SPIE: usize = 1 << 5; } +/// Return the current machine interrupt enable state. pub fn get_interrupt_state() -> bool { (read_csr!(mstatus) & MStatus::MIE as u64) != 0 } +/// Return whether supervisor interrupts are currently enabled. pub fn get_supervisor_interrupt_state() -> bool { (read_csr!(sstatus) & SStatus::SIE as u64) != 0 } +/// Enable machine-level interrupts. pub fn enable_interrupt() { set_csr!(mstatus, MStatus::MIE); } +/// Enable supervisor-level interrupts. pub fn enable_supervisor_interrupt() { set_csr!(sstatus, SStatus::SIE); } + +/// Disable machine-level interrupts. pub fn disable_interrupt() { clear_csr!(mstatus, MStatus::MIE); } + +/// Disable supervisor-level interrupts. pub fn disable_supervisor_interrupt() { clear_csr!(sstatus, SStatus::SIE); } + +/// Restore machine interrupt state from `previous_state`. pub fn restore_interrupt(previous_state: bool) { if previous_state { enable_interrupt(); @@ -43,6 +57,8 @@ pub fn restore_interrupt(previous_state: bool) { disable_interrupt(); } } + +/// Restore supervisor interrupt state from `previous_state`. pub fn restore_supervisor_interrupt(previous_state: bool) { if previous_state { enable_supervisor_interrupt(); diff --git a/src/scheduler.rs b/src/scheduler.rs index 6290cd7..bab27d6 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -1,3 +1,8 @@ +//! +//! Scheduler and idle loop utilities. +//! +//! This module exposes the global process table, the scheduler initialization +//! and a simple round-robin scheduler used by the kernel. use core::{arch::riscv64::wfi, array, cell::LazyCell, time::Duration}; use alloc::string::String; @@ -8,9 +13,17 @@ use crate::{ time, }; +/// Maximum number of simultaneous processes supported by the kernel. pub const PROCESS_COUNT: usize = 16; +/// Currently active PID. +/// +/// Updated by the scheduler when switching contexts. pub static mut ACTIVE_PID: usize = 0; +/// Global process table stored in a lazily-initialized container. +/// +/// Each entry represents a process slot which may be `Dead`, `Activable`, +/// `Active` or `Asleep`. pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_COUNT]> = LazyCell::new(|| { array::from_fn(|_| Process { pid: -1, @@ -33,6 +46,9 @@ pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_COUNT]> = LazyCell::new }) }); +/// Idle loop executed when there is no runnable process. +/// +/// Uses the `wfi` instruction to yield the CPU while waiting for interrupts. pub fn idle() { loop { // write_string_temp("idle"); @@ -43,6 +59,10 @@ pub fn idle() { } } +/// Initialize the scheduler and create the idle process. +/// +/// Marks all process slots as `Dead` then creates the idle process and sets +/// it as the active process. pub fn scheduler_init() { info!("scheduler init"); for pid in 0..PROCESS_COUNT { @@ -57,6 +77,12 @@ pub fn scheduler_init() { } } +/// Round-robin scheduler used to select the next runnable process. +/// +/// Saves the provided interrupt context into the previous process slot and +/// updates `ACTIVE_PID` to point to the chosen process. This function does +/// not return but instead updates `interrupt_state` to the context of the +/// next process to run. pub fn scheduler_without_ret(interrupt_state: &mut *mut ExecutionContext) { // info!("scheduler"); unsafe { diff --git a/src/time.rs b/src/time.rs index d9125d7..c6396ea 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,5 +1,6 @@ -use core::time::Duration; +//! Time and timer interrupt management for the kernel. +use core::time::Duration; use alloc::format; use crate::{ @@ -7,60 +8,86 @@ use crate::{ vga::{Color, Vga, FONT_WIDTH, WIDTH}, }; -pub const IRQ_M_TIMER: u8 = 1 << 7; +/// Supervisor timer interrupt enable bit for the SIE CSR. pub const IRQ_S_TIMER: u8 = 1 << 5; -const CLINT_TIMER_CMP: *mut u64 = 0x02004000 as *mut u64; -const CLINT_TIMER: *const u64 = 0x0200bff8 as *const u64; -const TIMER_FREQUENCY: u64 = 10000000; // 10MHz -const INTERRUPT_FREQUENCY: u64 = 20; // 20Hz +/// Machine timer interrupt enable bit for the MIE CSR (not used here, but provided for completeness). +pub const IRQ_M_TIMER: u8 = 1 << 7; +/// Memory-mapped address for the CLINT timer compare register. +const CLINT_TIMER_CMP: *mut u64 = 0x0200_4000 as *mut u64; +/// Memory-mapped address for the CLINT timer value register. +const CLINT_TIMER: *const u64 = 0x0200_bff8 as *const u64; + +/// The hardware timer frequency (Hz). +const TIMER_FREQUENCY: u64 = 10_000_000; // 10 MHz +/// The frequency at which timer interrupts should occur (Hz). +const INTERRUPT_FREQUENCY: u64 = 20; // 20 Hz + +/// Stores the instant when the kernel started. static mut START_TIME: Instant = Instant(0); +/// Initializes the timer interrupt system and records the kernel start time. +/// +/// This should be called once during kernel initialization. pub fn setup_timer_interrupt() { - unsafe { START_TIME = Instant::now() }; + unsafe { START_TIME = Instant::now(); } set_csr!(sie, IRQ_S_TIMER); setup_next_timer_interrupt(); } +/// Programs the next timer interrupt to occur after the configured interval. +/// +/// This should be called after each timer interrupt to schedule the next one. pub fn setup_next_timer_interrupt() { unsafe { - core::ptr::write_volatile( - CLINT_TIMER_CMP, - Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY, + let next = Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY; + core::ptr::write_volatile(CLINT_TIMER_CMP, next); + } +} + +/// Handles a timer interrupt: updates the on-screen clock and schedules the next interrupt. +pub fn timer_interrupt() { + let current_time = elapsed_time_since_startup(); + let total_seconds = current_time.as_secs(); + + let hours = (total_seconds / 3600) % 60; + let minutes = (total_seconds / 60) % 60; + let seconds = total_seconds % 60; + + let formatted_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds); + + unsafe { + Vga::draw_string( + (WIDTH - formatted_time.len() * FONT_WIDTH) as u16, + 0, + formatted_time, + Color::WHITE, + Color::BLACK, ); } } -pub fn timer_interrupt() { - let current_time = elapsed_time_since_startup(); - let seconds = current_time.as_secs(); - let minutes = seconds / 60 % 60; - let hours = seconds / 3600 % 60; - let seconds = seconds % 60; - let formated_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds); - unsafe { - Vga::draw_string( - (WIDTH - formated_time.len() * FONT_WIDTH) as u16, - 0, - formated_time, - Color::WHITE, - Color::BLACK, - ) - }; -} +/// Returns the duration since the kernel was started. pub fn elapsed_time_since_startup() -> Duration { unsafe { START_TIME.elapsed() } } +/// Represents a point in time, based on the hardware timer. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Instant(u64); +pub struct Instant(pub u64); impl Instant { + /// Returns the current value of the hardware timer as an `Instant`. pub fn now() -> Self { Instant(unsafe { core::ptr::read_volatile(CLINT_TIMER) }) } + + /// Returns the duration elapsed since this instant. pub fn elapsed(&self) -> Duration { let now = Self::now(); - Duration::from_nanos((now.0 - self.0) * (1_000_000_000 / TIMER_FREQUENCY)) + // Calculate elapsed ticks and convert to nanoseconds. + let ticks = now.0.saturating_sub(self.0); + let nanos = ticks.saturating_mul(1_000_000_000 / TIMER_FREQUENCY); + Duration::from_nanos(nanos) } } diff --git a/src/uart.rs b/src/uart.rs index 8312c7a..574eeb9 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,15 +1,27 @@ +//! UART low-level driver. +//! +//! Minimal polling driver used by the kernel for early console output. const UART_BASE: *mut u8 = 0x10000000 as *mut _; +/// Write a single character to the UART using a simple polling loop. +/// +/// This is a very small, platform-specific driver used for early boot +/// console output; it busy-waits until the UART indicates it can accept +/// a new byte. pub fn write_char_uart(c: char) { - while unsafe { core::ptr::read_volatile(UART_BASE.byte_add(0x5)) } >> 5 & 1 == 0 {} + while unsafe { (core::ptr::read_volatile(UART_BASE.byte_add(0x5)) >> 5) & 1 == 0 } {} unsafe { core::ptr::write_volatile(UART_BASE, c as u8) }; } +/// Write a UTF-8 string to the UART. +/// +/// Automatically injects a carriage-return after newline to support terminals +/// that expect CRLF pairs. pub fn write_uart>(print: T) { - print.as_ref().chars().for_each(|a| { + for a in print.as_ref().chars() { // Add \r if needed write_char_uart(a); if a == '\n' { write_char_uart('\r'); } - }); + } } diff --git a/src/user.rs b/src/user.rs index 1a58b67..b5f4e98 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,3 +1,7 @@ +//! Example user processes used for testing and demonstrations. +//! +//! Provides a couple of simple user-space loops used to exercise syscalls +//! and the scheduler. use core::time::Duration; use shared::syscall::{sleep, write_int_temp, write_string_temp}; diff --git a/src/vga.rs b/src/vga.rs index 516fe17..02790b0 100644 --- a/src/vga.rs +++ b/src/vga.rs @@ -1,3 +1,7 @@ +//! Basic VGA/Bochs frame-buffer driver and text rendering helpers. +//! +//! Provides primitives to initialize the Bochs-compatible frame buffer and +//! draw text using an embedded font plate. use kernel_macros::include_font_plate; use log::info; @@ -8,6 +12,7 @@ pub const VGA_ADDRESS: *mut Color = BOCHS_DISPLAY_BASE_ADDRESS as *mut Color; pub const WIDTH: usize = 1600; pub const HEIGHT: usize = 900; +/// 24-bit RGB color used by the framebuffer. #[repr(transparent)] #[derive(Clone, Copy)] pub struct Color(u32); @@ -27,9 +32,14 @@ impl Color { pub const BLUE: Color = Color::from_rgb(0, 0, 255); } +/// Framebuffer driver type providing text rendering helpers. pub struct Vga {} impl Vga { + /// Initialize the Bochs framebuffer and configure VGA parameters. + /// + /// This performs PCI enumeration to find a Bochs-compatible device and + /// programs the Bochs config registers accordingly. pub unsafe fn init() { for i in 0..32 { let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11); @@ -66,12 +76,18 @@ impl Vga { /// # Safety /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` + /// Write a single pixel into the framebuffer (unsafe). + /// + /// Caller must ensure `x < WIDTH` and `y < HEIGHT`. pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) { let pixel_index = x as usize + y as usize * WIDTH; unsafe { *VGA_ADDRESS.add(pixel_index) = color } } + /// Draw a single character with a background color at (x,y). + /// + /// Uses the embedded font plate to render glyphs into the framebuffer. pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) { let c = if (c as u8 > b'~') || ((c as u8) < b' ') { b'/' - b' ' @@ -101,6 +117,10 @@ impl Vga { /// # Safety /// The text must have a length that can fit within a `u16` + /// Draw a UTF-8 string at the given position. + /// + /// Newlines (`\n`) advance `y` by `FONT_HEIGHT` and carriage return (`\r`) + /// resets to the starting `x` position. pub unsafe fn draw_string>( x: u16, mut y: u16, @@ -126,12 +146,14 @@ impl Vga { }); } + /// Fill the entire framebuffer with a single color. pub fn clear_screen(color: Color) { for i in 0..WIDTH * HEIGHT { unsafe { *VGA_ADDRESS.add(i) = color } } } + /// Return whether a pixel inside the embedded font plate is set. unsafe fn font_plate_index(x: u16, y: u16) -> bool { let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize); let byte_index = pixel_index / 8; diff --git a/user.ld b/user.ld new file mode 100644 index 0000000..8f59c13 --- /dev/null +++ b/user.ld @@ -0,0 +1,35 @@ +/* + * ld directives the for barmetal RISCV + */ +OUTPUT_ARCH(riscv) +ENTRY(_start) + +MEMORY { + RAM (wxa) : ORIGIN = 0x800dd1d8, LENGTH = 128M +} + +SECTIONS { + . = 0x800dd1d8; + .text : { + KEEP(*(.text._start)) + + *(.text .text.*) + } > RAM + + .rodata : { + *(.rodata .rodata.*) + } > RAM + + .data : { + *(.data .data.*) + } > RAM + + .bss : ALIGN(8) { + __bss_start = .; + *(.bss .bss.*) + __bss_end = .; + } > RAM + + _heap_start = ALIGN(8); + _heap_end = ORIGIN(RAM) + LENGTH(RAM); +} diff --git a/user/test_pic/src/main.rs b/user/test_pic/src/main.rs index f777b13..420ae5b 100644 --- a/user/test_pic/src/main.rs +++ b/user/test_pic/src/main.rs @@ -1,11 +1,26 @@ #![no_std] #![no_main] +#![feature(fmt_internals)] + +use core::fmt::{write, Arguments, Write}; + +use os_std::syscall; os_std::custom_std_setup! {} -use os_std::syscall::write_string_temp; - fn main() { - write_string_temp( - "Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls !", - ); + let mut test = String::new(); + test.push('A'); + test.push('B'); + for _ in 0..50 { + test.push('C'); + } + let mut b = String::from("test"); + // (&mut b as &mut dyn Write).write_str("string: uaeuieuei"); + syscall::write_string_temp(&b); + // write(&mut b, Arguments::from_str_nonconst("string: uaeuieuei")); + // write(&mut b, format_args!("string: uaeuie{}", "uei")); + // syscall::write_int_temp(b.capacity() as u64); + // syscall::write_string_temp(&b); + // println!("{}", test); + // println!("Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls !"); }