Refactor
This commit is contained in:
2
.gdbinit
2
.gdbinit
@@ -1,4 +1,6 @@
|
|||||||
file target/riscv64/debug/kernel-rust
|
file target/riscv64/debug/kernel-rust
|
||||||
target remote localhost:1234
|
target remote localhost:1234
|
||||||
break machine_mode_entry
|
break machine_mode_entry
|
||||||
|
# break *0x800dd1d8
|
||||||
|
# add-symbol-file target/riscv64/debug/test_pic 0x800dd1d8
|
||||||
c
|
c
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.helix
|
.helix/
|
||||||
|
.zed/
|
||||||
|
|
||||||
**/target
|
**/target
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
mod prelude;
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
pub use shared::syscall;
|
pub use shared::syscall;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! custom_std_setup {
|
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]
|
#[panic_handler]
|
||||||
fn panic(_panic_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_panic_info: &core::panic::PanicInfo) -> ! {
|
||||||
// TODO print
|
// 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!();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
pub use crate::print;
|
||||||
|
pub use crate::println;
|
||||||
|
pub use alloc::format;
|
||||||
|
pub use alloc::string::String;
|
||||||
|
pub use alloc::vec;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use core::time::Duration;
|
use core::{alloc::Layout, time::Duration};
|
||||||
|
|
||||||
#[repr(u64)]
|
#[repr(u64)]
|
||||||
pub enum SysCall {
|
pub enum SysCall {
|
||||||
|
Alloc = 40,
|
||||||
|
Dealloc = 41,
|
||||||
Exit = 60,
|
Exit = 60,
|
||||||
NanoSleep = 101,
|
NanoSleep = 101,
|
||||||
WriteIntTemp = 998,
|
WriteIntTemp = 998,
|
||||||
@@ -12,6 +14,8 @@ pub enum SysCall {
|
|||||||
impl From<u64> for SysCall {
|
impl From<u64> for SysCall {
|
||||||
fn from(value: u64) -> Self {
|
fn from(value: u64) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
40 => SysCall::Alloc,
|
||||||
|
41 => SysCall::Dealloc,
|
||||||
60 => SysCall::Exit,
|
60 => SysCall::Exit,
|
||||||
101 => SysCall::NanoSleep,
|
101 => SysCall::NanoSleep,
|
||||||
998 => SysCall::WriteIntTemp,
|
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 {
|
unsafe {
|
||||||
syscall!(
|
syscall!(
|
||||||
SysCall::WriteTemp,
|
SysCall::WriteTemp,
|
||||||
@@ -104,3 +108,19 @@ pub fn write_int_temp(content: u64) {
|
|||||||
syscall!(SysCall::WriteIntTemp, content);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
justfile
2
justfile
@@ -11,7 +11,7 @@ sync_filesystem:
|
|||||||
sync
|
sync
|
||||||
|
|
||||||
build_user_prog prog:
|
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 }}
|
riscv64-elf-objcopy -O binary {{ "target/riscv64/debug" / prog }} {{ "mnt/usr/bin" / prog }}
|
||||||
|
|
||||||
build: mount_filesystem (map_dir "user" "build_user_prog")
|
build: mount_filesystem (map_dir "user" "build_user_prog")
|
||||||
|
|||||||
@@ -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 core::arch::naked_asm;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|||||||
@@ -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]
|
#[non_exhaustive]
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
pub enum EextensionID {
|
pub enum EextensionID {
|
||||||
|
|||||||
@@ -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 critical_section::RawRestoreState;
|
||||||
|
|
||||||
use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt};
|
use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt};
|
||||||
|
|||||||
22
src/fs.rs
22
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 core::{cell::UnsafeCell, ops::Deref};
|
||||||
|
|
||||||
use bffs::{
|
use bffs::{
|
||||||
@@ -7,10 +11,18 @@ use bffs::{
|
|||||||
|
|
||||||
const DISK_ADDR: *const u8 = 0x9000_0000 as *const _;
|
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<Option<Fat32FileSystem<Disk>>>);
|
pub struct FSTemp(UnsafeCell<Option<Fat32FileSystem<Disk>>>);
|
||||||
unsafe impl Sync for FSTemp {}
|
unsafe impl Sync for FSTemp {}
|
||||||
|
|
||||||
impl 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) {
|
pub unsafe fn init(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
*self.0.get() = Some(Fat32FileSystem::new(Disk::new(1024 * 1024 * 16)).unwrap());
|
*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));
|
pub static FILE_SYSTEM: FSTemp = FSTemp(UnsafeCell::new(None));
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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 {
|
pub struct Disk {
|
||||||
pos: u64,
|
pos: u64,
|
||||||
size: u64,
|
size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Disk {
|
impl Disk {
|
||||||
|
/// Create a new `Disk` representing an in-memory image of `size` bytes.
|
||||||
pub fn new(size: u64) -> Self {
|
pub fn new(size: u64) -> Self {
|
||||||
Self { pos: 0, size }
|
Self { pos: 0, size }
|
||||||
}
|
}
|
||||||
@@ -56,12 +73,15 @@ impl Seek for Disk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Read for Disk {
|
impl Read for Disk {
|
||||||
|
/// Read bytes from the in-memory disk image into `buf`.
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, bffs::error::Error<Self::Error>> {
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, bffs::error::Error<Self::Error>> {
|
||||||
if self.pos >= self.size {
|
if self.pos >= self.size {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let size = usize::min(buf.len(), (self.size - self.pos) as usize);
|
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;
|
self.pos += size as u64;
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 alloc::str;
|
||||||
use log::info;
|
use log::info;
|
||||||
use shared::syscall::SysCall;
|
use shared::syscall::SysCall;
|
||||||
@@ -13,11 +18,16 @@ use crate::{
|
|||||||
time::{setup_next_timer_interrupt, IRQ_M_TIMER},
|
time::{setup_next_timer_interrupt, IRQ_M_TIMER},
|
||||||
write_csr,
|
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};
|
use crate::time::{setup_timer_interrupt, timer_interrupt};
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[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(
|
unsafe extern "C" fn machine_trap_handler(
|
||||||
mcause: u64,
|
mcause: u64,
|
||||||
mie: u64,
|
mie: u64,
|
||||||
@@ -86,6 +96,10 @@ unsafe extern "C" fn machine_trap_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[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(
|
unsafe extern "C" fn supervisor_trap_handler(
|
||||||
mut interrupt_state: *mut ExecutionContext,
|
mut interrupt_state: *mut ExecutionContext,
|
||||||
scause: u64,
|
scause: u64,
|
||||||
@@ -105,8 +119,20 @@ unsafe extern "C" fn supervisor_trap_handler(
|
|||||||
let syscall_u64: u64 = unsafe { (*interrupt_state).a[0] };
|
let syscall_u64: u64 = unsafe { (*interrupt_state).a[0] };
|
||||||
let a1: u64 = unsafe { (*interrupt_state).a[1] };
|
let a1: u64 = unsafe { (*interrupt_state).a[1] };
|
||||||
let a2: u64 = unsafe { (*interrupt_state).a[2] };
|
let a2: u64 = unsafe { (*interrupt_state).a[2] };
|
||||||
|
let a3: u64 = unsafe { (*interrupt_state).a[3] };
|
||||||
let syscall: SysCall = syscall_u64.into();
|
let syscall: SysCall = syscall_u64.into();
|
||||||
match syscall {
|
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::Exit => exit_process(&mut interrupt_state),
|
||||||
SysCall::NanoSleep => sleep(Duration::new(a1, a2 as u32), &mut interrupt_state),
|
SysCall::NanoSleep => sleep(Duration::new(a1, a2 as u32), &mut interrupt_state),
|
||||||
SysCall::WriteTemp => {
|
SysCall::WriteTemp => {
|
||||||
@@ -146,10 +172,13 @@ unsafe extern "C" fn supervisor_trap_handler(
|
|||||||
interrupt_state
|
interrupt_state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install the machine-mode trap entry point and enable timer interrupts.
|
||||||
pub unsafe fn setup_machine_trap_handler() {
|
pub unsafe fn setup_machine_trap_handler() {
|
||||||
write_csr!(mtvec, _machine_mode_trap);
|
write_csr!(mtvec, _machine_mode_trap);
|
||||||
set_csr!(mie, IRQ_M_TIMER);
|
set_csr!(mie, IRQ_M_TIMER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install the supervisor-mode trap entry point and configure periodic timer.
|
||||||
pub unsafe fn setup_supervisor_trap_handler() {
|
pub unsafe fn setup_supervisor_trap_handler() {
|
||||||
write_csr!(stvec, _supervisor_mode_trap);
|
write_csr!(stvec, _supervisor_mode_trap);
|
||||||
setup_timer_interrupt();
|
setup_timer_interrupt();
|
||||||
@@ -157,6 +186,11 @@ pub unsafe fn setup_supervisor_trap_handler() {
|
|||||||
|
|
||||||
#[unsafe(naked)]
|
#[unsafe(naked)]
|
||||||
#[unsafe(no_mangle)]
|
#[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() {
|
unsafe extern "C" fn _machine_mode_trap() {
|
||||||
naked_asm!(
|
naked_asm!(
|
||||||
"
|
"
|
||||||
@@ -210,6 +244,10 @@ unsafe extern "C" fn _machine_mode_trap() {
|
|||||||
}
|
}
|
||||||
#[unsafe(naked)]
|
#[unsafe(naked)]
|
||||||
#[unsafe(no_mangle)]
|
#[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() {
|
unsafe extern "C" fn _supervisor_mode_trap() {
|
||||||
naked_asm!(concat!(
|
naked_asm!(concat!(
|
||||||
"
|
"
|
||||||
@@ -267,6 +305,7 @@ unsafe extern "C" fn _supervisor_mode_trap() {
|
|||||||
|
|
||||||
#[unsafe(naked)]
|
#[unsafe(naked)]
|
||||||
#[unsafe(no_mangle)]
|
#[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) -> ! {
|
pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! {
|
||||||
naked_asm!(concat!(
|
naked_asm!(concat!(
|
||||||
"
|
"
|
||||||
|
|||||||
16
src/io.rs
16
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 crate::println;
|
||||||
|
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::String;
|
|
||||||
use log::{Level, Metadata, Record};
|
use log::{Level, Metadata, Record};
|
||||||
use log::{LevelFilter, SetLoggerError};
|
use log::{LevelFilter, SetLoggerError};
|
||||||
|
|
||||||
use crate::uart::write_uart;
|
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<str>` to avoid unnecessary
|
||||||
|
/// allocations at call sites.
|
||||||
|
pub(crate) fn print<T: AsRef<str>>(content: T) {
|
||||||
write_uart(content);
|
write_uart(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Logger implementation that routes kernel log records to the UART-based console.
|
||||||
struct Logger;
|
struct Logger;
|
||||||
|
|
||||||
impl log::Log for Logger {
|
impl log::Log for Logger {
|
||||||
@@ -39,6 +48,9 @@ impl log::Log for Logger {
|
|||||||
|
|
||||||
static LOGGER: Logger = 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> {
|
pub fn init_log() -> Result<(), SetLoggerError> {
|
||||||
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info))
|
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![allow(static_mut_refs)]
|
#![allow(static_mut_refs)]
|
||||||
@@ -43,7 +47,7 @@ pub const HEAP_SIZE: usize = 1024 * 1024; // 1Mo RAM
|
|||||||
static HEAP: Heap = Heap::empty();
|
static HEAP: Heap = Heap::empty();
|
||||||
|
|
||||||
// Usize is assumed to be an u64 in the whole kernel
|
// Usize is assumed to be an u64 in the whole kernel
|
||||||
const _: () = assert!(size_of::<usize>() == size_of::<u64>());
|
const _: () = assert!(core::mem::size_of::<usize>() == core::mem::size_of::<u64>());
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn supervisor_mode_entry() {
|
pub extern "C" fn supervisor_mode_entry() {
|
||||||
|
|||||||
@@ -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 core::arch::riscv64::wfi;
|
||||||
|
|
||||||
use alloc::{format, string::ToString};
|
use alloc::{format, string::ToString};
|
||||||
@@ -6,6 +10,7 @@ use log::error;
|
|||||||
use crate::vga::{Color, Vga, FONT_HEIGHT};
|
use crate::vga::{Color, Vga, FONT_HEIGHT};
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
|
/// Kernel panic handler that displays the panic message on the framebuffer and halts.
|
||||||
fn panic(panic_info: &core::panic::PanicInfo) -> ! {
|
fn panic(panic_info: &core::panic::PanicInfo) -> ! {
|
||||||
error!("PANIC !");
|
error!("PANIC !");
|
||||||
let mut panic_message = panic_info.message().to_string();
|
let mut panic_message = panic_info.message().to_string();
|
||||||
|
|||||||
208
src/process.rs
208
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 core::time::Duration;
|
||||||
|
|
||||||
use alloc::{boxed::Box, format, string::String, vec::Vec};
|
use alloc::{boxed::Box, format, string::String, vec::Vec};
|
||||||
@@ -6,41 +15,78 @@ use shared::syscall::exit;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fs::FILE_SYSTEM,
|
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,
|
time::elapsed_time_since_startup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Size of the stack allocated to each process (in 64-bit words).
|
||||||
const STACK_SIZE: usize = 4096;
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum ProcessState {
|
pub enum ProcessState {
|
||||||
|
/// The process is currently executing.
|
||||||
Active,
|
Active,
|
||||||
|
/// The process is ready to execute and waiting to be scheduled.
|
||||||
Activable,
|
Activable,
|
||||||
|
/// The process has terminated and its slot can be reused.
|
||||||
Dead,
|
Dead,
|
||||||
|
/// The process is sleeping until a specific wake time.
|
||||||
Asleep,
|
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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ExecutionContext {
|
pub struct ExecutionContext {
|
||||||
|
/// Return address register.
|
||||||
pub ra: *const u64,
|
pub ra: *const u64,
|
||||||
|
/// Stack pointer register.
|
||||||
pub sp: *const u64,
|
pub sp: *const u64,
|
||||||
|
/// Global pointer register.
|
||||||
pub gp: u64,
|
pub gp: u64,
|
||||||
|
/// Thread pointer register.
|
||||||
pub tp: u64,
|
pub tp: u64,
|
||||||
|
/// Argument/return value registers (a0-a7).
|
||||||
pub a: [u64; 8],
|
pub a: [u64; 8],
|
||||||
|
/// Temporary registers (t0-t6).
|
||||||
pub t: [u64; 7],
|
pub t: [u64; 7],
|
||||||
|
/// Saved registers (s0-s11).
|
||||||
pub s: [u64; 12],
|
pub s: [u64; 12],
|
||||||
|
/// Machine exception program counter.
|
||||||
pub mepc: *const u64,
|
pub mepc: *const u64,
|
||||||
|
/// Machine status register.
|
||||||
pub mstatus: u64,
|
pub mstatus: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a process in the system.
|
||||||
|
///
|
||||||
|
/// Each process has its own execution context, stack,
|
||||||
|
/// and metadata for scheduling.
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
|
/// Unique process identifier.
|
||||||
pub pid: i64,
|
pub pid: i64,
|
||||||
|
/// Descriptive name of the process.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Current state of the process.
|
||||||
pub state: ProcessState,
|
pub state: ProcessState,
|
||||||
|
/// Optional entry point for the process code.
|
||||||
pub entry: Option<&'static dyn Fn()>,
|
pub entry: Option<&'static dyn Fn()>,
|
||||||
|
/// Wake time for sleeping processes.
|
||||||
pub wake_time: Duration,
|
pub wake_time: Duration,
|
||||||
|
/// Saved execution context.
|
||||||
pub ctx: ExecutionContext,
|
pub ctx: ExecutionContext,
|
||||||
|
/// Process stack.
|
||||||
pub stack: [u64; STACK_SIZE],
|
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<'a>>>(path: T) -> i64 {
|
pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
let name = path.as_str();
|
let name = path.as_str();
|
||||||
|
|
||||||
|
// Open and read the binary file
|
||||||
let mut bin = FILE_SYSTEM.open_file(path).unwrap();
|
let mut bin = FILE_SYSTEM.open_file(path).unwrap();
|
||||||
let mut content: Vec<u8> = Vec::new();
|
let mut content: Vec<u8> = Vec::new();
|
||||||
bin.read_to_end(&mut content).unwrap();
|
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()) };
|
unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(Vec::leak(content).as_ptr()) };
|
||||||
|
|
||||||
let test = Box::leak(Box::new(move || {
|
// Create a wrapper for the entry point function
|
||||||
test();
|
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<T: Into<String>, F: Fn()>(code: &'static F, name: T) -> i64 {
|
pub fn create_process<T: Into<String>, F: Fn()>(code: &'static F, name: T) -> i64 {
|
||||||
|
// Search for a free slot in the process table
|
||||||
let mut next_pid = 0;
|
let mut next_pid = 0;
|
||||||
while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead }
|
while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead }
|
||||||
{
|
{
|
||||||
next_pid += 1;
|
next_pid += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a slot is available
|
||||||
if next_pid >= PROCESS_COUNT {
|
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 {
|
unsafe {
|
||||||
PROCESS_TABLE[next_pid].pid = next_pid as i64;
|
let process = &mut PROCESS_TABLE[next_pid];
|
||||||
PROCESS_TABLE[next_pid].name = name.into();
|
|
||||||
PROCESS_TABLE[next_pid].state = ProcessState::Activable;
|
// Configure process metadata
|
||||||
PROCESS_TABLE[next_pid].entry = Some(code);
|
process.pid = next_pid as i64;
|
||||||
PROCESS_TABLE[next_pid].ctx.a[0] =
|
process.name = name.into();
|
||||||
PROCESS_TABLE[next_pid].entry.as_ref().unwrap_unchecked() as *const &dyn Fn() as u64;
|
process.state = ProcessState::Activable;
|
||||||
PROCESS_TABLE[next_pid].ctx.mepc = process_launcher as *const _;
|
process.entry = Some(code);
|
||||||
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];
|
// 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
|
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()) {
|
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)() };
|
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();
|
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) {
|
pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) {
|
||||||
|
// SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid.
|
||||||
unsafe {
|
unsafe {
|
||||||
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead;
|
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transfer control to the scheduler (does not return)
|
||||||
scheduler_without_ret(interrupt_context)
|
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) {
|
pub fn sleep(duration: Duration, interrupt_context: &mut *mut ExecutionContext) {
|
||||||
|
// SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid.
|
||||||
unsafe {
|
unsafe {
|
||||||
PROCESS_TABLE[ACTIVE_PID].wake_time = elapsed_time_since_startup() + duration;
|
let process = &mut PROCESS_TABLE[ACTIVE_PID];
|
||||||
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Asleep;
|
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)
|
scheduler_without_ret(interrupt_context)
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/riscv.rs
16
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)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use core::arch::naked_asm;
|
use core::arch::naked_asm;
|
||||||
@@ -18,24 +22,34 @@ impl SStatus {
|
|||||||
pub const SPIE: usize = 1 << 5;
|
pub const SPIE: usize = 1 << 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the current machine interrupt enable state.
|
||||||
pub fn get_interrupt_state() -> bool {
|
pub fn get_interrupt_state() -> bool {
|
||||||
(read_csr!(mstatus) & MStatus::MIE as u64) != 0
|
(read_csr!(mstatus) & MStatus::MIE as u64) != 0
|
||||||
}
|
}
|
||||||
|
/// Return whether supervisor interrupts are currently enabled.
|
||||||
pub fn get_supervisor_interrupt_state() -> bool {
|
pub fn get_supervisor_interrupt_state() -> bool {
|
||||||
(read_csr!(sstatus) & SStatus::SIE as u64) != 0
|
(read_csr!(sstatus) & SStatus::SIE as u64) != 0
|
||||||
}
|
}
|
||||||
|
/// Enable machine-level interrupts.
|
||||||
pub fn enable_interrupt() {
|
pub fn enable_interrupt() {
|
||||||
set_csr!(mstatus, MStatus::MIE);
|
set_csr!(mstatus, MStatus::MIE);
|
||||||
}
|
}
|
||||||
|
/// Enable supervisor-level interrupts.
|
||||||
pub fn enable_supervisor_interrupt() {
|
pub fn enable_supervisor_interrupt() {
|
||||||
set_csr!(sstatus, SStatus::SIE);
|
set_csr!(sstatus, SStatus::SIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable machine-level interrupts.
|
||||||
pub fn disable_interrupt() {
|
pub fn disable_interrupt() {
|
||||||
clear_csr!(mstatus, MStatus::MIE);
|
clear_csr!(mstatus, MStatus::MIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable supervisor-level interrupts.
|
||||||
pub fn disable_supervisor_interrupt() {
|
pub fn disable_supervisor_interrupt() {
|
||||||
clear_csr!(sstatus, SStatus::SIE);
|
clear_csr!(sstatus, SStatus::SIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore machine interrupt state from `previous_state`.
|
||||||
pub fn restore_interrupt(previous_state: bool) {
|
pub fn restore_interrupt(previous_state: bool) {
|
||||||
if previous_state {
|
if previous_state {
|
||||||
enable_interrupt();
|
enable_interrupt();
|
||||||
@@ -43,6 +57,8 @@ pub fn restore_interrupt(previous_state: bool) {
|
|||||||
disable_interrupt();
|
disable_interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore supervisor interrupt state from `previous_state`.
|
||||||
pub fn restore_supervisor_interrupt(previous_state: bool) {
|
pub fn restore_supervisor_interrupt(previous_state: bool) {
|
||||||
if previous_state {
|
if previous_state {
|
||||||
enable_supervisor_interrupt();
|
enable_supervisor_interrupt();
|
||||||
|
|||||||
@@ -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 core::{arch::riscv64::wfi, array, cell::LazyCell, time::Duration};
|
||||||
|
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
@@ -8,9 +13,17 @@ use crate::{
|
|||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Maximum number of simultaneous processes supported by the kernel.
|
||||||
pub const PROCESS_COUNT: usize = 16;
|
pub const PROCESS_COUNT: usize = 16;
|
||||||
|
|
||||||
|
/// Currently active PID.
|
||||||
|
///
|
||||||
|
/// Updated by the scheduler when switching contexts.
|
||||||
pub static mut ACTIVE_PID: usize = 0;
|
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(|| {
|
pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_COUNT]> = LazyCell::new(|| {
|
||||||
array::from_fn(|_| Process {
|
array::from_fn(|_| Process {
|
||||||
pid: -1,
|
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() {
|
pub fn idle() {
|
||||||
loop {
|
loop {
|
||||||
// write_string_temp("idle");
|
// 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() {
|
pub fn scheduler_init() {
|
||||||
info!("scheduler init");
|
info!("scheduler init");
|
||||||
for pid in 0..PROCESS_COUNT {
|
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) {
|
pub fn scheduler_without_ret(interrupt_state: &mut *mut ExecutionContext) {
|
||||||
// info!("scheduler");
|
// info!("scheduler");
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
83
src/time.rs
83
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 alloc::format;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -7,60 +8,86 @@ use crate::{
|
|||||||
vga::{Color, Vga, FONT_WIDTH, WIDTH},
|
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;
|
pub const IRQ_S_TIMER: u8 = 1 << 5;
|
||||||
const CLINT_TIMER_CMP: *mut u64 = 0x02004000 as *mut u64;
|
/// Machine timer interrupt enable bit for the MIE CSR (not used here, but provided for completeness).
|
||||||
const CLINT_TIMER: *const u64 = 0x0200bff8 as *const u64;
|
pub const IRQ_M_TIMER: u8 = 1 << 7;
|
||||||
const TIMER_FREQUENCY: u64 = 10000000; // 10MHz
|
|
||||||
|
/// 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
|
const INTERRUPT_FREQUENCY: u64 = 20; // 20 Hz
|
||||||
|
|
||||||
|
/// Stores the instant when the kernel started.
|
||||||
static mut START_TIME: Instant = Instant(0);
|
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() {
|
pub fn setup_timer_interrupt() {
|
||||||
unsafe { START_TIME = Instant::now() };
|
unsafe { START_TIME = Instant::now(); }
|
||||||
set_csr!(sie, IRQ_S_TIMER);
|
set_csr!(sie, IRQ_S_TIMER);
|
||||||
setup_next_timer_interrupt();
|
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() {
|
pub fn setup_next_timer_interrupt() {
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_volatile(
|
let next = Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY;
|
||||||
CLINT_TIMER_CMP,
|
core::ptr::write_volatile(CLINT_TIMER_CMP, next);
|
||||||
Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn elapsed_time_since_startup() -> Duration {
|
||||||
unsafe { START_TIME.elapsed() }
|
unsafe { START_TIME.elapsed() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a point in time, based on the hardware timer.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Instant(u64);
|
pub struct Instant(pub u64);
|
||||||
|
|
||||||
impl Instant {
|
impl Instant {
|
||||||
|
/// Returns the current value of the hardware timer as an `Instant`.
|
||||||
pub fn now() -> Self {
|
pub fn now() -> Self {
|
||||||
Instant(unsafe { core::ptr::read_volatile(CLINT_TIMER) })
|
Instant(unsafe { core::ptr::read_volatile(CLINT_TIMER) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the duration elapsed since this instant.
|
||||||
pub fn elapsed(&self) -> Duration {
|
pub fn elapsed(&self) -> Duration {
|
||||||
let now = Self::now();
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/uart.rs
18
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 _;
|
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) {
|
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) };
|
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<T: AsRef<str>>(print: T) {
|
pub fn write_uart<T: AsRef<str>>(print: T) {
|
||||||
print.as_ref().chars().for_each(|a| {
|
for a in print.as_ref().chars() {
|
||||||
// Add \r if needed
|
// Add \r if needed
|
||||||
write_char_uart(a);
|
write_char_uart(a);
|
||||||
if a == '\n' {
|
if a == '\n' {
|
||||||
write_char_uart('\r');
|
write_char_uart('\r');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 core::time::Duration;
|
||||||
|
|
||||||
use shared::syscall::{sleep, write_int_temp, write_string_temp};
|
use shared::syscall::{sleep, write_int_temp, write_string_temp};
|
||||||
|
|||||||
22
src/vga.rs
22
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 kernel_macros::include_font_plate;
|
||||||
use log::info;
|
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 WIDTH: usize = 1600;
|
||||||
pub const HEIGHT: usize = 900;
|
pub const HEIGHT: usize = 900;
|
||||||
|
|
||||||
|
/// 24-bit RGB color used by the framebuffer.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Color(u32);
|
pub struct Color(u32);
|
||||||
@@ -27,9 +32,14 @@ impl Color {
|
|||||||
pub const BLUE: Color = Color::from_rgb(0, 0, 255);
|
pub const BLUE: Color = Color::from_rgb(0, 0, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Framebuffer driver type providing text rendering helpers.
|
||||||
pub struct Vga {}
|
pub struct Vga {}
|
||||||
|
|
||||||
impl 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() {
|
pub unsafe fn init() {
|
||||||
for i in 0..32 {
|
for i in 0..32 {
|
||||||
let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11);
|
let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11);
|
||||||
@@ -66,12 +76,18 @@ impl Vga {
|
|||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT`
|
/// `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) {
|
pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) {
|
||||||
let pixel_index = x as usize + y as usize * WIDTH;
|
let pixel_index = x as usize + y as usize * WIDTH;
|
||||||
|
|
||||||
unsafe { *VGA_ADDRESS.add(pixel_index) = color }
|
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) {
|
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' ') {
|
let c = if (c as u8 > b'~') || ((c as u8) < b' ') {
|
||||||
b'/' - b' '
|
b'/' - b' '
|
||||||
@@ -101,6 +117,10 @@ impl Vga {
|
|||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The text must have a length that can fit within a `u16`
|
/// 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<T: AsRef<str>>(
|
pub unsafe fn draw_string<T: AsRef<str>>(
|
||||||
x: u16,
|
x: u16,
|
||||||
mut y: u16,
|
mut y: u16,
|
||||||
@@ -126,12 +146,14 @@ impl Vga {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill the entire framebuffer with a single color.
|
||||||
pub fn clear_screen(color: Color) {
|
pub fn clear_screen(color: Color) {
|
||||||
for i in 0..WIDTH * HEIGHT {
|
for i in 0..WIDTH * HEIGHT {
|
||||||
unsafe { *VGA_ADDRESS.add(i) = color }
|
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 {
|
unsafe fn font_plate_index(x: u16, y: u16) -> bool {
|
||||||
let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize);
|
let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize);
|
||||||
let byte_index = pixel_index / 8;
|
let byte_index = pixel_index / 8;
|
||||||
|
|||||||
35
user.ld
Normal file
35
user.ld
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,11 +1,26 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
#![feature(fmt_internals)]
|
||||||
|
|
||||||
|
use core::fmt::{write, Arguments, Write};
|
||||||
|
|
||||||
|
use os_std::syscall;
|
||||||
os_std::custom_std_setup! {}
|
os_std::custom_std_setup! {}
|
||||||
|
|
||||||
use os_std::syscall::write_string_temp;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
write_string_temp(
|
let mut test = String::new();
|
||||||
"Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls !",
|
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 !");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user