From ea9fd39b02fae54f544e156ba0badcdf0eb61335 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Mon, 19 May 2025 19:49:51 +0200 Subject: [PATCH 01/11] code improvements --- beskar-hal/src/commons.rs | 6 ----- beskar-lib/src/lib.rs | 20 +++++++++------ bootloader/bootloader-api/src/lib.rs | 6 +++-- hyperdrive/src/locks/ticket.rs | 19 +++++++++++++- hyperdrive/src/once.rs | 1 - hyperdrive/src/ptrs/view.rs | 4 +-- hyperdrive/src/ptrs/volatile.rs | 5 ++-- hyperdrive/src/queues/mpsc.rs | 31 ++++++++++------------- kernel/driver-api/src/lib.rs | 2 +- kernel/foundry/acpi/src/lib.rs | 4 +-- kernel/foundry/acpi/src/rsdp.rs | 6 ++--- kernel/foundry/acpi/src/sdt.rs | 17 ++++++------- kernel/foundry/acpi/src/sdt/dsdt.rs | 2 +- kernel/foundry/acpi/src/sdt/fadt.rs | 2 +- kernel/foundry/acpi/src/sdt/hpet_table.rs | 2 +- kernel/foundry/acpi/src/sdt/madt.rs | 2 +- kernel/foundry/acpi/src/sdt/mcfg.rs | 2 +- kernel/foundry/pci/src/commons/msix.rs | 6 ++--- kernel/foundry/pci/src/express.rs | 8 +++--- kernel/src/boot.rs | 15 ++++------- kernel/src/locals.rs | 2 +- kernel/src/mem/page_alloc/pmap.rs | 2 +- kernel/src/process/scheduler/thread.rs | 4 +-- 23 files changed, 85 insertions(+), 83 deletions(-) delete mode 100644 beskar-hal/src/commons.rs diff --git a/beskar-hal/src/commons.rs b/beskar-hal/src/commons.rs deleted file mode 100644 index 77e7122..0000000 --- a/beskar-hal/src/commons.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Definitions of architecture agnostic types and functions to help -//! with the implementation of the architecture specific code. -mod addrs; -pub use addrs::*; - -pub mod paging; diff --git a/beskar-lib/src/lib.rs b/beskar-lib/src/lib.rs index 86e82af..225d819 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; use beskar_core::syscall::{Syscall, SyscallExitCode}; pub use beskar_core::{syscall::ExitCode, time::Duration}; +use hyperdrive::once::Once; mod arch; pub mod io; @@ -46,7 +47,10 @@ macro_rules! entry_point { extern crate alloc; #[unsafe(export_name = "_start")] - pub extern "C" fn __program_entry() { + /// # Safety + /// + /// Do not call this function. + unsafe extern "C" fn __program_entry() { unsafe { $crate::__init() }; ($path)(); $crate::exit($crate::ExitCode::Success); @@ -55,12 +59,12 @@ macro_rules! entry_point { } /// Initialize the standard library. -/// -/// ## Safety -/// -/// Do not call this function. #[doc(hidden)] -pub unsafe fn __init() { - let res = mem::mmap(mem::HEAP_SIZE, None); - unsafe { mem::init_heap(res.as_ptr(), mem::HEAP_SIZE.try_into().unwrap()) }; +pub fn __init() { + static CALL_ONCE: Once<()> = Once::uninit(); + + CALL_ONCE.call_once(|| { + let res = mem::mmap(mem::HEAP_SIZE, None); + unsafe { mem::init_heap(res.as_ptr(), mem::HEAP_SIZE.try_into().unwrap()) }; + }); } diff --git a/bootloader/bootloader-api/src/lib.rs b/bootloader/bootloader-api/src/lib.rs index c2cdcd7..57ddec2 100644 --- a/bootloader/bootloader-api/src/lib.rs +++ b/bootloader/bootloader-api/src/lib.rs @@ -12,11 +12,13 @@ use beskar_core::{ /// This macro defines the entry point of the kernel. /// /// This will be called by the bootloader. +/// +/// You can pass additional arguments that will be forwarded to your entry point function. macro_rules! entry_point { - ($path:path) => { + ($path:path $(, $arg:expr)*) => { #[unsafe(export_name = "_start")] pub extern "C" fn __kernel_entry(boot_info: &'static mut $crate::BootInfo) -> ! { - ($path)(boot_info) + unsafe { ($path)(boot_info $(, $arg)*) } } }; } diff --git a/hyperdrive/src/locks/ticket.rs b/hyperdrive/src/locks/ticket.rs index 44de507..9ded547 100644 --- a/hyperdrive/src/locks/ticket.rs +++ b/hyperdrive/src/locks/ticket.rs @@ -6,12 +6,29 @@ //! It is not suitable for high contention scenarios, but it is a good //! alternative to spin locks in low contention scenarios. //! +//! Note that rustc currently requires that you at least specify either the back-off strategy +//! (and will infer the type of `T`) or the type of `T` (and will use the default `Spin` +//! back-off strategy). +//! +//! ```rust +//! # use hyperdrive::locks::ticket::TicketLock; +//! # use hyperdrive::locks::Spin; +//! # +//! let lock = TicketLock::::new(0); // `Spin` is used +//! let lock = TicketLock::<_, Spin>::new(0); // `T` is inferred +//! ``` +//! +//! ```rust,compile_fail +//! # use hyperdrive::locks::rw::RwLock; +//! let lock = TicketLock::new(0); +//! ``` +//! //! # Example //! //! ```rust //! # use hyperdrive::locks::ticket::TicketLock; //! # use hyperdrive::locks::Spin; -//! let lock = TicketLock::<_, Spin>::new(0); +//! let lock = TicketLock::::new(0); //! //! let mut guard = lock.lock(); //! *guard = 42; diff --git a/hyperdrive/src/once.rs b/hyperdrive/src/once.rs index 81885c7..a770ba1 100644 --- a/hyperdrive/src/once.rs +++ b/hyperdrive/src/once.rs @@ -47,7 +47,6 @@ use core::mem::MaybeUninit; use core::sync::atomic::{AtomicU8, Ordering}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] /// Possible states of the `Once` structure. enum State { Uninitialized, diff --git a/hyperdrive/src/ptrs/view.rs b/hyperdrive/src/ptrs/view.rs index cb45f63..4ad87fb 100644 --- a/hyperdrive/src/ptrs/view.rs +++ b/hyperdrive/src/ptrs/view.rs @@ -70,7 +70,7 @@ impl> View { /// Take ownership of the object, if it is owned. pub fn take(self) -> Option { match self { - Self::Borrow { .. } => None, + Self::Borrow(_) => None, Self::Owned(owned) => Some(owned), } } @@ -165,7 +165,7 @@ impl> ViewMut { /// Take ownership of the object, if it is owned. pub fn take(self) -> Option { match self { - Self::BorrowMut { .. } => None, + Self::BorrowMut(_) => None, Self::Owned(owned) => Some(owned), } } diff --git a/hyperdrive/src/ptrs/volatile.rs b/hyperdrive/src/ptrs/volatile.rs index d4e1911..7009801 100644 --- a/hyperdrive/src/ptrs/volatile.rs +++ b/hyperdrive/src/ptrs/volatile.rs @@ -106,7 +106,7 @@ impl Volatile { #[inline] /// Creates a new volatile pointer from a mutable reference. pub const fn from_mut(ptr: &mut T) -> Self { - Self::new(unsafe { NonNull::new_unchecked(ptr) }) + Self::new(NonNull::from_mut(ptr)) } #[must_use] @@ -170,8 +170,7 @@ impl Volatile { #[inline] /// Creates a new volatile pointer from a reference. pub const fn from_ref(ptr: &T) -> Volatile { - let ptr_mut = unsafe { NonNull::new_unchecked(core::ptr::from_ref(ptr).cast_mut()) }; - Self::new_read_only(ptr_mut) + Self::new_read_only(NonNull::from_ref(ptr)) } #[must_use] diff --git a/hyperdrive/src/queues/mpsc.rs b/hyperdrive/src/queues/mpsc.rs index b48890d..563b101 100644 --- a/hyperdrive/src/queues/mpsc.rs +++ b/hyperdrive/src/queues/mpsc.rs @@ -44,9 +44,7 @@ //! } //! //! unsafe fn get_link(ptr: NonNull) -> NonNull> { -//! let base = ptr.as_ptr().cast::>(); -//! let ptr = unsafe { base.byte_add(offset_of!(Element, next)) }; -//! unsafe { NonNull::new_unchecked(ptr) } +//! unsafe { ptr.byte_add(offset_of!(Self, next)) }.cast() //! } //! } //! ``` @@ -56,9 +54,12 @@ //! `Link` is the API that the queue uses to link the elements together. //! //! While it is a bit more complex on the inside, it behaves like a simple pointer to the next element. -//! This is why it is possible to write `get_link` the way it is in the above example. //! -//! ## Usage +//! ## `MpcsQueue` +//! +//! An intrusive, multiple-producer single-consumer queue. +//! +//! ### Example //! //! ```rust //! # use hyperdrive::queues::mpsc::{Link, MpscQueue, Queueable}; @@ -89,9 +90,7 @@ //! # } //! # //! # unsafe fn get_link(ptr: NonNull) -> NonNull> { -//! # let base = ptr.as_ptr().cast::>(); -//! # let ptr = unsafe { base.byte_add(offset_of!(Element, next)) }; -//! # unsafe { NonNull::new_unchecked(ptr) } +//! # unsafe { ptr.byte_add(offset_of!(Self, next)) }.cast() //! # } //! # } //! # @@ -364,7 +363,7 @@ mod tests { } struct OtherElement { value: u8, - next: Option>, + _next: Option>, } impl Unpin for Element {} @@ -383,9 +382,7 @@ mod tests { } unsafe fn get_link(ptr: NonNull) -> NonNull> { - let base = ptr.as_ptr().cast::>(); - let ptr = unsafe { base.byte_add(offset_of!(Self, next)) }; - unsafe { NonNull::new_unchecked(ptr) } + unsafe { ptr.byte_add(offset_of!(Self, next)) }.cast() } } @@ -402,9 +399,7 @@ mod tests { } unsafe fn get_link(ptr: NonNull) -> NonNull> { - let base = ptr.as_ptr().cast::>(); - let ptr = unsafe { base.byte_add(offset_of!(Self, next)) }; - unsafe { NonNull::new_unchecked(ptr) } + unsafe { ptr.byte_add(offset_of!(Self, _next)) }.cast() } } @@ -435,15 +430,15 @@ mod tests { fn test_mpsc_non_link_field() { let queue: MpscQueue = MpscQueue::new(Box::pin(OtherElement { value: 0, - next: None, + _next: None, })); queue.enqueue(Box::pin(OtherElement { value: 1, - next: None, + _next: None, })); queue.enqueue(Box::pin(OtherElement { value: 2, - next: None, + _next: None, })); let element1 = queue.dequeue().unwrap(); diff --git a/kernel/driver-api/src/lib.rs b/kernel/driver-api/src/lib.rs index 02513d7..1bfe5ea 100644 --- a/kernel/driver-api/src/lib.rs +++ b/kernel/driver-api/src/lib.rs @@ -14,7 +14,7 @@ pub use beskar_core::drivers::{DriverError, DriverResult}; /// /// Be careful to only use the original mapped length, as accessing outside /// could result in undefined behavior if the memory is used by another mapping. -pub trait PhysicalMappingTrait { +pub trait PhysicalMapper { /// Create a new physical mapping. fn new(paddr: PhysAddr, length: usize, flags: Flags) -> Self; diff --git a/kernel/foundry/acpi/src/lib.rs b/kernel/foundry/acpi/src/lib.rs index 110ca3f..96dd396 100644 --- a/kernel/foundry/acpi/src/lib.rs +++ b/kernel/foundry/acpi/src/lib.rs @@ -23,7 +23,7 @@ use sdt::{ static ACPI_REVISION: AcpiRevisionStorage = AcpiRevisionStorage::uninit(); /// Advanced Configuration and Power Interface (ACPI) support. -pub struct Acpi> { +pub struct Acpi> { madt: ParsedMadt, fadt: ParsedFadt, hpet: Option, @@ -32,7 +32,7 @@ pub struct Acpi, } -impl> Acpi { +impl> Acpi { #[must_use] pub fn from_rsdp_paddr(rsdp_paddr: PhysAddr) -> Self { let rsdt_paddr = rsdp::Rsdp::::load(rsdp_paddr).rsdt_paddr(); diff --git a/kernel/foundry/acpi/src/rsdp.rs b/kernel/foundry/acpi/src/rsdp.rs index 5e4059c..249acbc 100644 --- a/kernel/foundry/acpi/src/rsdp.rs +++ b/kernel/foundry/acpi/src/rsdp.rs @@ -3,7 +3,7 @@ use core::mem::offset_of; use super::AcpiRevision; use beskar_core::arch::{PhysAddr, VirtAddr, paging::M4KiB}; use beskar_hal::paging::page_table::Flags; -use driver_api::PhysicalMappingTrait; +use driver_api::PhysicalMapper; #[derive(Clone, Copy, Debug)] #[repr(C, packed)] @@ -34,13 +34,13 @@ struct Xsdp2 { } #[derive(Debug)] -pub struct Rsdp> { +pub struct Rsdp> { start_vaddr: VirtAddr, revision: AcpiRevision, _physical_mapping: M, } -impl> Rsdp { +impl> Rsdp { /// Map, validate, read and unmap the RSDP. /// /// Returns the RSDT address. diff --git a/kernel/foundry/acpi/src/sdt.rs b/kernel/foundry/acpi/src/sdt.rs index cb977bd..2e255d4 100644 --- a/kernel/foundry/acpi/src/sdt.rs +++ b/kernel/foundry/acpi/src/sdt.rs @@ -3,7 +3,7 @@ use core::mem::offset_of; use super::AcpiRevision; use beskar_core::arch::{PhysAddr, VirtAddr, paging::M4KiB}; use beskar_hal::paging::page_table::Flags; -use driver_api::PhysicalMappingTrait; +use driver_api::PhysicalMapper; pub mod dsdt; pub mod fadt; @@ -115,7 +115,7 @@ pub unsafe trait Sdt { } #[derive(Debug)] -pub struct Rsdt> { +pub struct Rsdt> { start_vaddr: VirtAddr, acpi_revision: AcpiRevision, _physical_mapping: M, @@ -123,13 +123,13 @@ pub struct Rsdt> { // Safety: // RSDT is a valid SDT. -unsafe impl> Sdt for Rsdt { +unsafe impl> Sdt for Rsdt { fn start(&self) -> *const u8 { self.start_vaddr.as_ptr() } } -impl> Rsdt { +impl> Rsdt { pub fn load(rsdt_paddr: PhysAddr) -> Self { let flags = Flags::PRESENT | Flags::NO_EXECUTE; @@ -250,7 +250,7 @@ impl Signature { /// ## Safety /// /// The provided physical address must point to a potentially valid SDT. -unsafe fn map>(phys_addr: PhysAddr) -> M { +unsafe fn map>(phys_addr: PhysAddr) -> M { let flags = Flags::PRESENT | Flags::NO_EXECUTE; let header_mapping = M::new(phys_addr, core::mem::size_of::(), flags); @@ -275,13 +275,12 @@ unsafe fn map>(phys_addr: PhysAddr) -> M { macro_rules! impl_sdt { ($name:ident) => { #[derive(Debug)] - pub struct $name> - { + pub struct $name> { start_vaddr: ::beskar_core::arch::VirtAddr, _physical_mapping: M, } - unsafe impl> + unsafe impl> $crate::sdt::Sdt for $name { fn start(&self) -> *const u8 { @@ -289,7 +288,7 @@ macro_rules! impl_sdt { } } - impl> $name { + impl> $name { #[must_use] pub fn load(paddr: ::beskar_core::arch::PhysAddr) -> Self { use $crate::sdt::Sdt; diff --git a/kernel/foundry/acpi/src/sdt/dsdt.rs b/kernel/foundry/acpi/src/sdt/dsdt.rs index 1316d93..564a8a7 100644 --- a/kernel/foundry/acpi/src/sdt/dsdt.rs +++ b/kernel/foundry/acpi/src/sdt/dsdt.rs @@ -21,7 +21,7 @@ struct RawDsdt { def_block: DefinitionBlock, } -impl> Dsdt { +impl> Dsdt { #[must_use] pub fn parse(&self) -> ParsedDsdt { assert_eq!( diff --git a/kernel/foundry/acpi/src/sdt/fadt.rs b/kernel/foundry/acpi/src/sdt/fadt.rs index 866a7cf..a8b501b 100644 --- a/kernel/foundry/acpi/src/sdt/fadt.rs +++ b/kernel/foundry/acpi/src/sdt/fadt.rs @@ -118,7 +118,7 @@ struct FullFadt { hypervisor_vendor_id: u64, } -impl> Fadt { +impl> Fadt { #[must_use] pub fn parse(&self) -> ParsedFadt { assert!(usize::try_from(self.length()).unwrap() >= size_of::()); diff --git a/kernel/foundry/acpi/src/sdt/hpet_table.rs b/kernel/foundry/acpi/src/sdt/hpet_table.rs index 2df9d7e..111db8d 100644 --- a/kernel/foundry/acpi/src/sdt/hpet_table.rs +++ b/kernel/foundry/acpi/src/sdt/hpet_table.rs @@ -70,7 +70,7 @@ impl From for PageProtection { } } -impl> HpetTable { +impl> HpetTable { #[must_use] pub fn parse(&self) -> ParsedHpetTable { assert_eq!( diff --git a/kernel/foundry/acpi/src/sdt/madt.rs b/kernel/foundry/acpi/src/sdt/madt.rs index f38afda..7160f5a 100644 --- a/kernel/foundry/acpi/src/sdt/madt.rs +++ b/kernel/foundry/acpi/src/sdt/madt.rs @@ -229,7 +229,7 @@ struct X2ApicNmi { } // See -impl> Madt { +impl> Madt { #[must_use] #[expect(clippy::too_many_lines, reason = "Many fields to parse")] pub fn parse(&self) -> ParsedMadt { diff --git a/kernel/foundry/acpi/src/sdt/mcfg.rs b/kernel/foundry/acpi/src/sdt/mcfg.rs index cfa37a3..ec5615b 100644 --- a/kernel/foundry/acpi/src/sdt/mcfg.rs +++ b/kernel/foundry/acpi/src/sdt/mcfg.rs @@ -40,7 +40,7 @@ struct McfgHeader { } // See -impl> Mcfg { +impl> Mcfg { #[must_use] pub fn parse(&self) -> ParsedMcfg { let nb_cs = (usize::try_from(self.length()).unwrap() - size_of::()) diff --git a/kernel/foundry/pci/src/commons/msix.rs b/kernel/foundry/pci/src/commons/msix.rs index 91f8873..939c589 100644 --- a/kernel/foundry/pci/src/commons/msix.rs +++ b/kernel/foundry/pci/src/commons/msix.rs @@ -6,10 +6,10 @@ use beskar_core::arch::paging::M4KiB; use beskar_hal::paging::page_table::Flags; use core::marker::PhantomData; use core::ptr::NonNull; -use driver_api::PhysicalMappingTrait; +use driver_api::PhysicalMapper; use hyperdrive::ptrs::volatile::{ReadWrite, Volatile}; -pub struct MsiX, H: MsiHelper> { +pub struct MsiX, H: MsiHelper> { capability: MsiXCapability, table: Volatile, pba: Volatile, @@ -48,7 +48,7 @@ struct MsiX070 { pba: u32, } -impl, H: MsiHelper> MsiX { +impl, H: MsiHelper> MsiX { pub fn new(handler: &mut dyn PciHandler, device: &super::Device) -> Option { let msix_cap = MsiXCapability::find(handler, device)?; diff --git a/kernel/foundry/pci/src/express.rs b/kernel/foundry/pci/src/express.rs index ae6d219..9fd0bf5 100644 --- a/kernel/foundry/pci/src/express.rs +++ b/kernel/foundry/pci/src/express.rs @@ -4,17 +4,17 @@ use acpi::sdt::mcfg::ParsedConfigurationSpace; use alloc::vec::Vec; use beskar_core::arch::{PhysAddr, paging::M2MiB}; use beskar_hal::paging::page_table::Flags; -use driver_api::PhysicalMappingTrait; +use driver_api::PhysicalMapper; use super::commons::{Class, Csp, Device, PciAddress, RegisterOffset, SbdfAddress}; -pub struct PciExpressHandler> { +pub struct PciExpressHandler> { configuration_spaces: &'static [ParsedConfigurationSpace], physical_mappings: Vec, devices: Vec, } -impl> PciExpressHandler { +impl> PciExpressHandler { #[must_use] #[inline] pub fn new(configuration_spaces: &'static [ParsedConfigurationSpace]) -> Self { @@ -169,7 +169,7 @@ impl> PciExpressHandler { } } -impl> super::PciHandler for PciExpressHandler { +impl> super::PciHandler for PciExpressHandler { fn devices(&self) -> &[super::commons::Device] { &self.devices } diff --git a/kernel/src/boot.rs b/kernel/src/boot.rs index d2acb9a..25cfec0 100644 --- a/kernel/src/boot.rs +++ b/kernel/src/boot.rs @@ -14,9 +14,7 @@ use hyperdrive::once::Once; /// This function should never be called directly, but only by the `enter_kmain` function. static KERNEL_MAIN: Once !> = Once::uninit(); -/// Static fence to ensure all cores enter `kmain` when they're all ready -static KERNEL_MAIN_FENCE: AtomicUsize = AtomicUsize::new(0); - +/// Static reference to the ramdisk information static RAMDISK: Once> = Once::uninit(); /// This function is the proper entry point called by the bootloader. @@ -141,6 +139,9 @@ pub fn ramdisk() -> Option<&'static [u8]> { /// It will wait for all cores to be ready before starting the kernel, /// i.e. entering `KERNEL_MAIN`. pub(crate) fn enter_kmain() -> ! { + /// Static fence to ensure all cores enter `kmain` when they're all ready + static KERNEL_MAIN_FENCE: AtomicUsize = AtomicUsize::new(0); + KERNEL_MAIN_FENCE.fetch_add(1, core::sync::atomic::Ordering::Relaxed); while KERNEL_MAIN_FENCE.load(core::sync::atomic::Ordering::Acquire) @@ -158,12 +159,6 @@ pub(crate) fn enter_kmain() -> ! { /// It should only be used once. macro_rules! kernel_main { ($path:path) => { - /// Entry of the kernel called by the bootloader. - /// - /// This should only be the entry point for the BSP. - fn __bootloader_entry_point(boot_info: &'static mut ::bootloader_api::BootInfo) -> ! { - $crate::boot::kbsp_entry(boot_info, $path); - } - ::bootloader_api::entry_point!(__bootloader_entry_point); + ::bootloader_api::entry_point!($crate::boot::kbsp_entry, $path); }; } diff --git a/kernel/src/locals.rs b/kernel/src/locals.rs index b5e756e..6f37252 100644 --- a/kernel/src/locals.rs +++ b/kernel/src/locals.rs @@ -28,7 +28,7 @@ pub fn init() { ..CoreLocalsInfo::empty() }); - ALL_CORE_LOCALS[core_id].call_once(|| unsafe { NonNull::new_unchecked(core_locals.as_mut()) }); + ALL_CORE_LOCALS[core_id].call_once(|| NonNull::from_mut(core_locals.as_mut())); crate::arch::locals::store_locals(Box::leak(core_locals)); diff --git a/kernel/src/mem/page_alloc/pmap.rs b/kernel/src/mem/page_alloc/pmap.rs index 10e426b..7fdc5a6 100644 --- a/kernel/src/mem/page_alloc/pmap.rs +++ b/kernel/src/mem/page_alloc/pmap.rs @@ -126,7 +126,7 @@ where } } -impl driver_api::PhysicalMappingTrait for PhysicalMapping +impl driver_api::PhysicalMapper for PhysicalMapping where for<'a> PageTable<'a>: Mapper, { diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index a9f3864..02f8717 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -452,9 +452,7 @@ impl ThreadStacks { self.user_pages .get() .map(|r| r.start().start_address() + r.size()) - .map(|p| unsafe { - NonNull::new_unchecked(p.align_down(Self::STACK_ALIGNMENT).as_mut_ptr()) - }) + .and_then(|p| NonNull::new(p.align_down(Self::STACK_ALIGNMENT).as_mut_ptr())) } #[must_use] From a5cfb8b806f72858dd8276fe50c5de7000b4bf68 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sun, 25 May 2025 13:12:33 +0200 Subject: [PATCH 02/11] small improvements --- beskar-core/src/drivers/keyboard.rs | 9 -------- build.rs | 3 ++- hyperdrive/src/queues/mpsc.rs | 23 +++++++++++++++----- kernel/foundry/video/src/lib.rs | 1 + kernel/foundry/video/src/screen.rs | 30 +++++++++++--------------- kernel/src/arch/x86_64/ap.rs | 4 ++-- kernel/src/arch/x86_64/apic.rs | 2 +- kernel/src/boot.rs | 8 +++---- kernel/src/drivers/keyboard.rs | 9 ++++---- kernel/src/lib.rs | 2 +- kernel/src/locals.rs | 19 +++++++--------- kernel/src/process/scheduler/thread.rs | 8 +++---- 12 files changed, 58 insertions(+), 60 deletions(-) diff --git a/beskar-core/src/drivers/keyboard.rs b/beskar-core/src/drivers/keyboard.rs index ed49143..dac338e 100644 --- a/beskar-core/src/drivers/keyboard.rs +++ b/beskar-core/src/drivers/keyboard.rs @@ -30,15 +30,6 @@ impl KeyEvent { self.pressed } - #[must_use] - #[inline] - pub const fn stub() -> Self { - Self { - key: KeyCode::Unknown, - pressed: KeyState::Released, - } - } - #[must_use] #[inline] pub fn pack_option(key_event: Option) -> u64 { diff --git a/build.rs b/build.rs index 6b30136..e794630 100644 --- a/build.rs +++ b/build.rs @@ -11,7 +11,7 @@ fn crate_name_to_cargo_venv(crate_name: &str) -> String { } else if c == '-' || c == '_' { cargo_venv.push('_'); } else { - panic!("Invalid character in crate name: {}", c); + panic!("Invalid character in crate name: {c}"); } } cargo_venv @@ -19,6 +19,7 @@ fn crate_name_to_cargo_venv(crate_name: &str) -> String { fn main() { println!("cargo:rerun-if-changed=./beskar-core"); + println!("cargo:rerun-if-changed=./beskar-hal"); println!("cargo:rerun-if-changed=./beskar-lib"); println!("cargo:rerun-if-changed=./bootloader"); println!("cargo:rerun-if-changed=./hyperdrive"); diff --git a/hyperdrive/src/queues/mpsc.rs b/hyperdrive/src/queues/mpsc.rs index 563b101..dacc5ba 100644 --- a/hyperdrive/src/queues/mpsc.rs +++ b/hyperdrive/src/queues/mpsc.rs @@ -144,6 +144,15 @@ pub struct Link { impl Default for Link { fn default() -> Self { + Self::new() + } +} + +impl Link { + #[must_use] + #[inline] + /// Creates a new link. + pub const fn new() -> Self { Self { next: AtomicPtr::new(ptr::null_mut()), _pin: core::marker::PhantomPinned, @@ -178,6 +187,7 @@ pub enum DequeueResult { impl DequeueResult { #[must_use] + #[inline] /// Unwraps the result. /// /// ## Panics @@ -192,6 +202,7 @@ impl DequeueResult { } #[must_use] + #[inline] /// Unwraps the result without checking its value. /// /// ## Safety @@ -213,6 +224,7 @@ impl Default for MpscQueue where T::Handle: Default, { + #[inline] fn default() -> Self { Self::new(T::Handle::default()) } @@ -220,6 +232,7 @@ where impl MpscQueue { #[must_use] + #[inline] pub fn new(stub: T::Handle) -> Self { let stub_ptr = ::release(stub); Self { @@ -363,7 +376,7 @@ mod tests { } struct OtherElement { value: u8, - _next: Option>, + next: Option>, } impl Unpin for Element {} @@ -399,7 +412,7 @@ mod tests { } unsafe fn get_link(ptr: NonNull) -> NonNull> { - unsafe { ptr.byte_add(offset_of!(Self, _next)) }.cast() + unsafe { ptr.byte_add(offset_of!(Self, next)) }.cast() } } @@ -430,15 +443,15 @@ mod tests { fn test_mpsc_non_link_field() { let queue: MpscQueue = MpscQueue::new(Box::pin(OtherElement { value: 0, - _next: None, + next: None, })); queue.enqueue(Box::pin(OtherElement { value: 1, - _next: None, + next: None, })); queue.enqueue(Box::pin(OtherElement { value: 2, - _next: None, + next: None, })); let element1 = queue.dequeue().unwrap(); diff --git a/kernel/foundry/video/src/lib.rs b/kernel/foundry/video/src/lib.rs index 9738d1d..c1a4051 100644 --- a/kernel/foundry/video/src/lib.rs +++ b/kernel/foundry/video/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_op_in_unsafe_fn)] #![warn(clippy::pedantic, clippy::nursery)] #![allow(clippy::missing_panics_doc)] +#![feature(pointer_try_cast_aligned)] pub mod log; pub mod screen; diff --git a/kernel/foundry/video/src/screen.rs b/kernel/foundry/video/src/screen.rs index c9b7f7c..439b6ea 100644 --- a/kernel/foundry/video/src/screen.rs +++ b/kernel/foundry/video/src/screen.rs @@ -92,25 +92,21 @@ struct PixelCompArr(PixelComponents); impl KernelDevice for ScreenDevice { fn read(&mut self, dst: &mut [u8], _offset: usize) -> Result<(), BlockDeviceError> { - if dst.is_empty() { - return Ok(()); - } - - if dst.len() == size_of::() { - // FIXME: If https://github.com/rust-lang/libs-team/issues/588 is implemented, use it. - #[expect(clippy::cast_ptr_alignment, reason = "Alignment is checked manually")] - let pixel_ptr = dst.as_mut_ptr().cast::(); - if !pixel_ptr.is_aligned() { - return Err(BlockDeviceError::UnalignedAccess); - } - unsafe { - pixel_ptr.write(with_screen(|screen| screen.info())); + const INFO_SIZE: usize = size_of::(); + + match dst.len() { + 0 => Ok(()), + INFO_SIZE => { + dst.as_mut_ptr() + .try_cast_aligned::() + .map(|info_ptr| unsafe { + // Safety: The pointer is aligned and the size is correct. + info_ptr.write(with_screen(|screen| screen.info())); + }) + .ok_or(BlockDeviceError::UnalignedAccess) } - - return Ok(()); + _ => Err(BlockDeviceError::Unsupported), } - - Err(BlockDeviceError::Unsupported) } fn write(&mut self, src: &[u8], offset: usize) -> Result<(), BlockDeviceError> { diff --git a/kernel/src/arch/x86_64/ap.rs b/kernel/src/arch/x86_64/ap.rs index f121815..a3aaf8c 100644 --- a/kernel/src/arch/x86_64/ap.rs +++ b/kernel/src/arch/x86_64/ap.rs @@ -120,8 +120,8 @@ pub fn start_up_aps(core_count: usize) { } } - // Wait for all APs to be ready - while locals::get_ready_core_count() != core_count { + // Wait for all APs to register themselves + while locals::core_count() != core_count { core::hint::spin_loop(); } diff --git a/kernel/src/arch/x86_64/apic.rs b/kernel/src/arch/x86_64/apic.rs index ab6a326..5aa8307 100644 --- a/kernel/src/arch/x86_64/apic.rs +++ b/kernel/src/arch/x86_64/apic.rs @@ -453,7 +453,7 @@ impl IoApic { enable_disable_interrupts(false); let id_offset = IOAPICID_CNTER.fetch_add(1, Ordering::Relaxed); - let cpu_count = locals::get_ready_core_count(); + let cpu_count = locals::core_count(); // Each APIC device must have a unique ID to be uniquely addressed // on the APIC Bus. assert!( diff --git a/kernel/src/boot.rs b/kernel/src/boot.rs index 25cfec0..e7895e7 100644 --- a/kernel/src/boot.rs +++ b/kernel/src/boot.rs @@ -3,7 +3,7 @@ use crate::{ drivers, locals, mem, process, storage, syscall, time, }; use bootloader_api::{BootInfo, RamdiskInfo}; -use core::sync::atomic::AtomicUsize; +use core::sync::atomic::{AtomicUsize, Ordering}; use hyperdrive::once::Once; /// Static reference to the kernel main function @@ -142,11 +142,9 @@ pub(crate) fn enter_kmain() -> ! { /// Static fence to ensure all cores enter `kmain` when they're all ready static KERNEL_MAIN_FENCE: AtomicUsize = AtomicUsize::new(0); - KERNEL_MAIN_FENCE.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + KERNEL_MAIN_FENCE.fetch_add(1, Ordering::Relaxed); - while KERNEL_MAIN_FENCE.load(core::sync::atomic::Ordering::Acquire) - != locals::get_ready_core_count() - { + while KERNEL_MAIN_FENCE.load(Ordering::Acquire) != locals::core_count() { core::hint::spin_loop(); } diff --git a/kernel/src/drivers/keyboard.rs b/kernel/src/drivers/keyboard.rs index de33326..63d1ba5 100644 --- a/kernel/src/drivers/keyboard.rs +++ b/kernel/src/drivers/keyboard.rs @@ -1,5 +1,5 @@ use alloc::boxed::Box; -use beskar_core::drivers::keyboard::KeyEvent; +use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyState}; use core::mem::offset_of; use hyperdrive::{ once::Once, @@ -37,10 +37,10 @@ impl Queueable for QueuedKeyEvent { impl QueuedKeyEvent { #[must_use] #[inline] - pub fn new(event: KeyEvent) -> Self { + pub const fn new(event: KeyEvent) -> Self { Self { event, - _link: Link::default(), + _link: Link::new(), } } @@ -65,8 +65,9 @@ impl KeyboardManager { #[must_use] #[inline] pub fn new() -> Self { + let stub = KeyEvent::new(KeyCode::Unknown, KeyState::Pressed); Self { - event_queue: MpscQueue::new(Box::new(QueuedKeyEvent::new(KeyEvent::stub()))), + event_queue: MpscQueue::new(Box::new(QueuedKeyEvent::new(stub))), } } #[inline] diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 4b206c7..53fcdba 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -37,7 +37,7 @@ fn panic(panic_info: &core::panic::PanicInfo) -> ! { ); // If more than one core is present, then both processes and APICs are initialized. - if crate::locals::get_ready_core_count() > 1 { + if crate::locals::core_count() > 1 { use crate::arch::apic::ipi; if process::scheduler::current_process().kind() == beskar_hal::process::Kind::Kernel { diff --git a/kernel/src/locals.rs b/kernel/src/locals.rs index 6f37252..e714e19 100644 --- a/kernel/src/locals.rs +++ b/kernel/src/locals.rs @@ -1,15 +1,14 @@ -use core::{ptr::NonNull, sync::atomic::AtomicUsize}; - -use alloc::boxed::Box; - use crate::arch::{apic::LocalApic, gdt::Gdt, interrupts::Interrupts}; +use alloc::boxed::Box; +use core::{ + ptr::NonNull, + sync::atomic::{AtomicUsize, Ordering}, +}; use hyperdrive::{ locks::mcs::{MUMcsLock, McsLock}, once::Once, }; -/// Count APs that are ready, right before entering `enter_kmain` -static CORE_READY: AtomicUsize = AtomicUsize::new(0); /// Distributes core IDs static CORE_ID: AtomicUsize = AtomicUsize::new(0); @@ -19,7 +18,7 @@ static CORE_ID: AtomicUsize = AtomicUsize::new(0); static ALL_CORE_LOCALS: [Once>; 256] = [const { Once::uninit() }; 256]; pub fn init() { - let core_id = CORE_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + let core_id = CORE_ID.fetch_add(1, Ordering::Relaxed); let apic_id = crate::arch::apic::apic_id(); let mut core_locals = Box::new(CoreLocalsInfo { @@ -31,15 +30,13 @@ pub fn init() { ALL_CORE_LOCALS[core_id].call_once(|| NonNull::from_mut(core_locals.as_mut())); crate::arch::locals::store_locals(Box::leak(core_locals)); - - CORE_READY.fetch_add(1, core::sync::atomic::Ordering::Release); } #[must_use] #[inline] /// Returns the number of currently active cores -pub fn get_ready_core_count() -> usize { - CORE_READY.load(core::sync::atomic::Ordering::Acquire) +pub fn core_count() -> usize { + CORE_ID.load(core::sync::atomic::Ordering::Acquire) } pub struct CoreLocalsInfo { diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index 02f8717..2ed178b 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -92,7 +92,7 @@ impl Thread { stack: None, // Will be overwritten before being used. last_stack_ptr: core::ptr::null_mut(), - link: Link::default(), + link: Link::new(), tls: Once::uninit(), } } @@ -117,7 +117,7 @@ impl Thread { state: ThreadState::Ready, stack: Some(ThreadStacks::new(stack)), last_stack_ptr: stack_ptr, - link: Link::default(), + link: Link::new(), tls: Once::uninit(), } } @@ -180,7 +180,7 @@ impl Thread { } #[must_use] - pub(super) fn new_stub(root_proc: Arc) -> Self { + pub(super) const fn new_stub(root_proc: Arc) -> Self { Self { id: ThreadId(0), root_proc, @@ -188,7 +188,7 @@ impl Thread { state: ThreadState::Ready, stack: None, last_stack_ptr: core::ptr::null_mut(), - link: Link::default(), + link: Link::new(), tls: Once::uninit(), } } From ad13fd7d547a25f2b47ba8183b4948b0bddef338 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sat, 14 Jun 2025 14:24:47 +0200 Subject: [PATCH 03/11] fix: deadlock in ps/2 interrupt handler --- Cargo.lock | 8 +- beskar-core/src/arch/addrs.rs | 42 ++- beskar-hal/src/x86_64/paging/page_table.rs | 4 +- beskar-lib/src/io/keyboard.rs | 15 +- beskar-lib/src/lib.rs | 3 +- hyperdrive/src/locks/rw.rs | 4 +- hyperdrive/src/queues.rs | 2 + hyperdrive/src/queues/mpmc.rs | 354 +++++++++++++++++++++ hyperdrive/src/queues/mpsc.rs | 3 + kernel/foundry/storage/src/fs.rs | 4 +- kernel/foundry/storage/src/vfs.rs | 10 +- kernel/src/arch/x86_64/syscall.rs | 12 + kernel/src/drivers/keyboard.rs | 66 +--- kernel/src/drivers/ps2.rs | 21 +- kernel/src/main.rs | 2 +- kernel/src/process.rs | 8 + kernel/src/process/scheduler/thread.rs | 33 +- userspace/bashkar/src/commands.rs | 11 +- userspace/bashkar/src/video/screen.rs | 8 - 19 files changed, 482 insertions(+), 128 deletions(-) create mode 100644 hyperdrive/src/queues/mpmc.rs diff --git a/Cargo.lock b/Cargo.lock index 985c454..61ba83a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "driver-api" @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", diff --git a/beskar-core/src/arch/addrs.rs b/beskar-core/src/arch/addrs.rs index 2d995dd..977e30e 100644 --- a/beskar-core/src/arch/addrs.rs +++ b/beskar-core/src/arch/addrs.rs @@ -12,6 +12,9 @@ pub struct VirtAddr(u64); pub struct PhysAddr(u64); impl VirtAddr { + pub const MAX: Self = Self(u64::MAX); + pub const ZERO: Self = Self(0); + #[must_use] #[inline] pub const fn new(addr: u64) -> Self { @@ -50,6 +53,26 @@ impl VirtAddr { unsafe { Self::new_unchecked(ptr.cast::<()>() as u64) } } + #[must_use] + #[inline] + /// Create a new virtual address from page table indices. + /// + /// Indices will be truncated to 9 bits each, as per the x86-64 paging scheme. + pub fn from_pt_indices( + p4_index: u16, + p3_index: u16, + p2_index: u16, + p1_index: u16, + offset: u16, + ) -> Self { + let addr = (u64::from(p4_index & 0x1FF) << 39) + | (u64::from(p3_index & 0x1FF) << 30) + | (u64::from(p2_index & 0x1FF) << 21) + | (u64::from(p1_index & 0x1FF) << 12) + | u64::from(offset & 0xFFF); + Self::new_extend(addr) + } + #[must_use] #[inline] /// # Safety @@ -132,7 +155,8 @@ impl VirtAddr { } impl PhysAddr { - pub const MAX_VALID: u64 = 0x000F_FFFF_FFFF_FFFF; + pub const MAX: Self = Self(0x000F_FFFF_FFFF_FFFF); + pub const ZERO: Self = Self(0); #[must_use] #[inline] @@ -153,7 +177,7 @@ impl PhysAddr { #[must_use] #[inline] pub const fn new_truncate(addr: u64) -> Self { - let truncated = addr & Self::MAX_VALID; + let truncated = addr & Self::MAX.0; // Safety: We just truncated the address to fit in the valid range unsafe { Self::new_unchecked(truncated) } } @@ -306,10 +330,22 @@ mod tests { #[test] fn test_v() { - let addr = PhysAddr::new(0x18000031060); + let addr = VirtAddr::new(0x18000031060); assert_eq!(addr.as_u64(), 0x18000031060); } + #[test] + fn test_v_from_idx() { + let addr = VirtAddr::new(0x18000031060); + let p4_index = addr.p4_index(); + let p3_index = addr.p3_index(); + let p2_index = addr.p2_index(); + let p1_index = addr.p1_index(); + let offset = u16::try_from(addr.as_u64() & 0xFFF).unwrap(); + let same_addr = VirtAddr::from_pt_indices(p4_index, p3_index, p2_index, p1_index, offset); + assert_eq!(addr, same_addr); + } + #[test] fn test_v_extends() { let addr = VirtAddr::new_extend(0xFFFF_FFFF_FFFF); diff --git a/beskar-hal/src/x86_64/paging/page_table.rs b/beskar-hal/src/x86_64/paging/page_table.rs index 5366583..8e7a0a5 100644 --- a/beskar-hal/src/x86_64/paging/page_table.rs +++ b/beskar-hal/src/x86_64/paging/page_table.rs @@ -161,12 +161,12 @@ impl Entries { } #[inline] - pub fn iter_entries(&self) -> core::slice::Iter { + pub fn iter_entries(&self) -> core::slice::Iter<'_, Entry> { self.0.iter() } #[inline] - pub fn iter_entries_mut(&mut self) -> core::slice::IterMut { + pub fn iter_entries_mut(&mut self) -> core::slice::IterMut<'_, Entry> { self.0.iter_mut() } diff --git a/beskar-lib/src/io/keyboard.rs b/beskar-lib/src/io/keyboard.rs index 2e5bb9f..c78549c 100644 --- a/beskar-lib/src/io/keyboard.rs +++ b/beskar-lib/src/io/keyboard.rs @@ -1,6 +1,9 @@ use super::File; pub use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyState}; +#[repr(align(8))] +struct KeyboardEventBuffer([u8; size_of::()]); + #[must_use] #[inline] /// Poll the kernel to get keyboard events @@ -13,15 +16,17 @@ pub fn poll_keyboard() -> Option { // FIXME: This is very inefficient and faillible if some other process // is using the keyboard file. - let file = File::open(KEYBOARD_FILE).unwrap(); + let Ok(file) = File::open(KEYBOARD_FILE) else { + return None; + }; - let mut buffer = [0_u8; size_of::()]; - let bytes_read = file.read(&mut buffer, 0).unwrap(); + let mut buffer = KeyboardEventBuffer([0_u8; size_of::()]); + let bytes_read = file.read(&mut buffer.0, 0).unwrap_or(0); file.close().unwrap(); - if bytes_read == buffer.len() { - let value = u64::from_ne_bytes(buffer); + if bytes_read == buffer.0.len() { + let value = u64::from_ne_bytes(buffer.0); KeyEvent::unpack_option(value) } else { None diff --git a/beskar-lib/src/lib.rs b/beskar-lib/src/lib.rs index 225d819..785cb9f 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -15,7 +15,8 @@ pub mod mem; pub mod rand; #[panic_handler] -fn panic(_info: &::core::panic::PanicInfo) -> ! { +fn panic(info: &::core::panic::PanicInfo) -> ! { + println!("Panic occurred: {}", info); exit(ExitCode::Failure); } diff --git a/hyperdrive/src/locks/rw.rs b/hyperdrive/src/locks/rw.rs index 5694e62..2bd00f2 100644 --- a/hyperdrive/src/locks/rw.rs +++ b/hyperdrive/src/locks/rw.rs @@ -93,13 +93,13 @@ impl RwLock { } #[must_use] - pub fn read(&self) -> ReadGuard { + pub fn read(&self) -> ReadGuard<'_, T, B> { self.state.read_lock(); ReadGuard { lock: self } } #[must_use] - pub fn write(&self) -> WriteGuard { + pub fn write(&self) -> WriteGuard<'_, T, B> { self.state.write_lock(); WriteGuard { lock: self } } diff --git a/hyperdrive/src/queues.rs b/hyperdrive/src/queues.rs index df05030..f357839 100644 --- a/hyperdrive/src/queues.rs +++ b/hyperdrive/src/queues.rs @@ -4,8 +4,10 @@ //! //! ## Modules //! +//! - `mpmc` : Multiple-producer multiple-consumer queue. //! - `mpsc` : Multiple-producer single-consumer queue. //! - `ring` : Ring queue backed by a fixed-size array. +pub mod mpmc; pub mod mpsc; pub mod ring; diff --git a/hyperdrive/src/queues/mpmc.rs b/hyperdrive/src/queues/mpmc.rs new file mode 100644 index 0000000..72edcce --- /dev/null +++ b/hyperdrive/src/queues/mpmc.rs @@ -0,0 +1,354 @@ +//! A multiple-producer multiple-consumer queue. +//! +//! This is basically an atomic ring buffer that allows multiple producers and consumers +//! to push and pop elements concurrently. +//! +//! ## Usage +//! +//! ```rust +//! # use hyperdrive::queues::mpmc::MpmcQueue; +//! # +//! let queue = MpmcQueue::<3, usize>::new(); +//! +//! // Push elements into the queue +//! queue.push(1); +//! queue.push(2); +//! queue.push(3); +//! +//! // Pop elements from the queue +//! assert_eq!(queue.pop(), Some(1)); +//! assert_eq!(queue.pop(), Some(2)); +//! assert_eq!(queue.pop(), Some(3)); +//! assert_eq!(queue.pop(), None); // Queue is empty +//! ``` +use core::{ + cell::UnsafeCell, + mem::MaybeUninit, + sync::atomic::{AtomicUsize, Ordering}, +}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct MpmcQueueFullError(T); + +impl core::fmt::Display for MpmcQueueFullError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("MPMC queue buffer is full") + } +} +impl core::fmt::Debug for MpmcQueueFullError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("MpmcQueueFullError").finish() + } +} + +impl core::error::Error for MpmcQueueFullError {} + +#[derive(Debug)] +/// A multiple-producer multiple-consumer queue. +pub struct MpmcQueue { + /// The buffer that holds the data. + buffer: [Slot; SIZE], + /// The index of the next element to be read. + read_index: AtomicUsize, + /// The index of the next element to be written. + write_index: AtomicUsize, +} + +#[derive(Debug)] +struct Slot { + /// The sequence number for this slot. + sequence: AtomicUsize, + /// The value stored in this slot. + value: UnsafeCell>, +} + +impl Slot { + #[must_use] + #[inline] + pub const fn new(sequence: usize) -> Self { + Self { + sequence: AtomicUsize::new(sequence), + value: UnsafeCell::new(MaybeUninit::uninit()), + } + } +} + +// Safety: Data races are avoided by using atomic operations for the indices. +unsafe impl Send for MpmcQueue where T: Send {} +unsafe impl Sync for MpmcQueue where T: Sync {} + +impl Default for MpmcQueue { + fn default() -> Self { + Self::new() + } +} + +impl MpmcQueue { + #[must_use] + #[inline] + /// Creates a new multiple-producer multiple-consumer queue. + /// + /// # Panics + /// + /// If the buffer size is not greater than 0, this function will panic. + pub fn new() -> Self { + assert!(SIZE > 0, "MPMC queue buffer size must be greater than 0"); + Self { + buffer: core::array::from_fn(Slot::new), + read_index: AtomicUsize::new(0), + write_index: AtomicUsize::new(0), + } + } + + #[inline] + /// Pushes a new value into the queue. + /// + /// # Panics + /// + /// If the buffer is full, this function will panic. + /// For a non-failing version, use `try_push`. + pub fn push(&self, value: T) { + self.try_push(value) + .expect("Buffer is full, cannot push new value"); + } + + /// Tries to push a new value into the queue. + /// + /// # Errors + /// + /// If the buffer is full, this function returns a `MpmcQueueFullError` containing the value that could not be pushed. + pub fn try_push(&self, value: T) -> Result<(), MpmcQueueFullError> { + let mut pos = self.write_index.load(Ordering::Relaxed); + + loop { + let slot = &self.buffer[pos % SIZE]; + let seq = slot.sequence.load(Ordering::Acquire); + + match seq.cmp(&pos) { + core::cmp::Ordering::Equal => { + match self.write_index.compare_exchange_weak( + pos, + pos + 1, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_old) => { + // Safety: We are the only thread writing to this slot and the pointer is valid. + unsafe { (*slot.value.get()).write(value) }; + slot.sequence.store(pos + 1, Ordering::Release); + break; // Successfully pushed the value + } + Err(current) => { + pos = current; // Retry with the current position + } + } + } + core::cmp::Ordering::Less => { + return Err(MpmcQueueFullError(value)); // Queue is full + } + core::cmp::Ordering::Greater => { + pos = self.write_index.load(Ordering::Relaxed); // Retry + } + } + } + + Ok(()) + } + + #[must_use] + /// Pops a value from the queue. + pub fn pop(&self) -> Option { + let mut pos = self.read_index.load(Ordering::Relaxed); + + loop { + let slot = &self.buffer[pos % SIZE]; + let seq = slot.sequence.load(Ordering::Acquire); + + match seq.cmp(&(pos + 1)) { + core::cmp::Ordering::Equal => { + match self.read_index.compare_exchange_weak( + pos, + pos + 1, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_old) => { + // Safety: We are the only thread writing to this slot and the pointer is valid. + let value = unsafe { (&*slot.value.get()).assume_init_read() }; + slot.sequence.store(pos + SIZE, Ordering::Release); + return Some(value); // Successfully pushed the value + } + Err(current) => { + pos = current; // Retry with the current position + } + } + } + core::cmp::Ordering::Less => { + return None; // Queue is empty + } + core::cmp::Ordering::Greater => { + pos = self.read_index.load(Ordering::Relaxed); // Retry + } + } + } + } +} + +impl Drop for MpmcQueue { + fn drop(&mut self) { + while let Some(v) = self.pop() { + drop(v); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Arc, Barrier}; + use std::thread; + + #[test] + fn test_mpmc() { + let mpmc = MpmcQueue::<4, usize>::new(); + + mpmc.push(1); + mpmc.push(2); + mpmc.push(3); + + assert_eq!(mpmc.pop(), Some(1)); + assert_eq!(mpmc.pop(), Some(2)); + assert_eq!(mpmc.pop(), Some(3)); + assert_eq!(mpmc.pop(), None); // Buffer is empty + } + + #[test] + #[should_panic = "Buffer is full, cannot push new value"] + fn test_mpmc_fill() { + let mpmc = MpmcQueue::<3, usize>::new(); + + mpmc.push(1); + mpmc.push(2); + mpmc.push(3); + mpmc.push(4); // This should panic + } + + #[test] + fn test_mpmc_cycle() { + let mpmc = MpmcQueue::<3, usize>::new(); + + mpmc.push(1); + mpmc.push(2); + + assert_eq!(mpmc.pop(), Some(1)); + assert_eq!(mpmc.pop(), Some(2)); + + mpmc.push(3); + mpmc.push(4); + + assert_eq!(mpmc.pop(), Some(3)); + assert_eq!(mpmc.pop(), Some(4)); + + assert!(mpmc.pop().is_none()); + } + + #[test] + fn test_mpmc_try_push() { + let mpmc = MpmcQueue::<3, usize>::new(); + + let res = mpmc.try_push(1); + assert!(res.is_ok()); + + let res = mpmc.try_push(2); + assert!(res.is_ok()); + + let res = mpmc.try_push(3); + assert!(res.is_ok()); + + let res = mpmc.try_push(4); + assert_eq!(res, Err(MpmcQueueFullError(4))); + } + + #[test] + #[cfg(miri)] + /// Assert that we are not double dropping any elements. + fn test_mpmc_heap() { + let mpmc = MpmcQueue::<3, Box>::new(); + + mpmc.push(Box::new(1)); + mpmc.push(Box::new(2)); + + assert_eq!(mpmc.pop(), Some(Box::new(1))); + assert_eq!(mpmc.pop(), Some(Box::new(2))); + } + + #[test] + #[cfg(miri)] + fn test_mpmc_drop() { + let mpmc = MpmcQueue::<3, Box>::new(); + + mpmc.push(Box::new(1)); + mpmc.push(Box::new(2)); + + drop(mpmc); + } + + #[test] + fn test_mpmc_concurrent() { + let mpmc = Arc::new(MpmcQueue::<40, usize>::new()); + + let num_producers = 4; + let num_consumers = 4; + let items_per_producer = 10; + let total_items = num_producers * items_per_producer; + let consumed_items = Arc::new(AtomicUsize::new(0)); + + let start_barrier = Arc::new(Barrier::new(num_producers + num_consumers)); + + let mut handles = vec![]; + + // Spawn producer threads + for producer_id in 0..num_producers { + let mpmc_clone = Arc::clone(&mpmc); + let barrier_clone = start_barrier.clone(); + let handle = thread::spawn(move || { + barrier_clone.wait(); + for i in 0..items_per_producer { + let value = producer_id * items_per_producer + i; + mpmc_clone.push(value); + } + }); + handles.push(handle); + } + + // Spawn consumer threads + for _ in 0..num_consumers { + let mpmc_clone = Arc::clone(&mpmc); + let counter_clone = Arc::clone(&consumed_items); + let barrier_clone = start_barrier.clone(); + let handle = thread::spawn(move || { + barrier_clone.wait(); + loop { + if let Some(_value) = mpmc_clone.pop() { + counter_clone.fetch_add(1, Ordering::Relaxed); + } else { + thread::yield_now(); + } + + if counter_clone.load(Ordering::Relaxed) >= total_items { + break; + } + } + }); + handles.push(handle); + } + + // Wait for all threads to complete + for handle in handles { + handle.join().unwrap(); + } + + // Verify all items were consumed + assert_eq!(consumed_items.load(Ordering::Relaxed), total_items); + } +} diff --git a/hyperdrive/src/queues/mpsc.rs b/hyperdrive/src/queues/mpsc.rs index dacc5ba..7eccd5a 100644 --- a/hyperdrive/src/queues/mpsc.rs +++ b/hyperdrive/src/queues/mpsc.rs @@ -106,6 +106,9 @@ use core::{ sync::atomic::{AtomicBool, AtomicPtr, Ordering}, }; +/// A trait that describes a type that can be used in an `MpscQueue`. +/// +/// The type must provide a way to link the elements together and a way to capture and release the data. pub trait Queueable: Sized { /// `Handle` is the type that owns the data. /// diff --git a/kernel/foundry/storage/src/fs.rs b/kernel/foundry/storage/src/fs.rs index 95ba081..20a635f 100644 --- a/kernel/foundry/storage/src/fs.rs +++ b/kernel/foundry/storage/src/fs.rs @@ -5,7 +5,7 @@ pub mod dev; pub mod ext2; pub mod fat; -#[derive(Debug, Error)] +#[derive(Debug, Error, Clone, Copy, Eq, PartialEq)] pub enum FileError { #[error("I/O error")] Io, @@ -102,7 +102,7 @@ impl PathBuf { #[must_use] #[inline] - pub fn as_path(&self) -> Path { + pub fn as_path(&self) -> Path<'_> { Path(&self.0) } } diff --git a/kernel/foundry/storage/src/vfs.rs b/kernel/foundry/storage/src/vfs.rs index b0a3fa0..dc55607 100644 --- a/kernel/foundry/storage/src/vfs.rs +++ b/kernel/foundry/storage/src/vfs.rs @@ -201,8 +201,14 @@ impl Vfs { /// /// This function should only be called with a `u64` of a process that has completed its execution. pub unsafe fn close_all_from_process(&self, pid: u64) { - let mut open_handles = self.open_handles.write(); - open_handles.retain(|_handle, open_file| open_file.process_id != pid); + self.open_handles.write().retain(|_handle, open_file| { + let retained = open_file.process_id != pid; + if !retained { + self.path_to_fs(open_file.path.as_path(), |fs, rel_path| fs.close(rel_path)) + .unwrap(); + } + retained + }); } /// Deletes a file at the given path. diff --git a/kernel/src/arch/x86_64/syscall.rs b/kernel/src/arch/x86_64/syscall.rs index 88048a7..e38a5cd 100644 --- a/kernel/src/arch/x86_64/syscall.rs +++ b/kernel/src/arch/x86_64/syscall.rs @@ -9,6 +9,10 @@ use beskar_hal::registers::{Efer, LStar, Rflags, SFMask, Star, StarSelectors}; #[repr(C, align(8))] struct SyscallRegisters { rax: u64, + r15: u64, + r14: u64, + r13: u64, + r12: u64, rdi: u64, rsi: u64, rdx: u64, @@ -37,10 +41,18 @@ unsafe extern "sysv64" fn syscall_handler_arch() { "push rdx", "push rsi", "push rdi", + "push r12", + "push r13", + "push r14", + "push r15", "push rax", "mov rdi, rsp", // Regs pointer "call {}", "pop rax", // RAX now contains syscall exit code + "pop r15", + "pop r14", + "pop r13", + "pop r12", "pop rdi", "pop rsi", "pop rdx", diff --git a/kernel/src/drivers/keyboard.rs b/kernel/src/drivers/keyboard.rs index 63d1ba5..c1d3dc1 100644 --- a/kernel/src/drivers/keyboard.rs +++ b/kernel/src/drivers/keyboard.rs @@ -1,10 +1,7 @@ -use alloc::boxed::Box; -use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyState}; -use core::mem::offset_of; -use hyperdrive::{ - once::Once, - queues::mpsc::{Link, MpscQueue, Queueable}, -}; +use beskar_core::drivers::keyboard::KeyEvent; +use hyperdrive::{once::Once, queues::mpmc::MpmcQueue}; + +const QUEUE_SIZE: usize = 25; static KEYBOARD_MANAGER: Once = Once::uninit(); @@ -12,47 +9,8 @@ pub fn init() { KEYBOARD_MANAGER.call_once(KeyboardManager::new); } -struct QueuedKeyEvent { - event: KeyEvent, - _link: Link, -} - -impl Queueable for QueuedKeyEvent { - type Handle = Box; - - unsafe fn capture(ptr: core::ptr::NonNull) -> Self::Handle { - unsafe { Box::from_raw(ptr.as_ptr()) } - } - - unsafe fn get_link(ptr: core::ptr::NonNull) -> core::ptr::NonNull> { - unsafe { ptr.byte_add(offset_of!(Self, _link)) }.cast() - } - - fn release(r: Self::Handle) -> core::ptr::NonNull { - let boxed_event = Box::into_raw(r); - unsafe { core::ptr::NonNull::new_unchecked(boxed_event) } - } -} - -impl QueuedKeyEvent { - #[must_use] - #[inline] - pub const fn new(event: KeyEvent) -> Self { - Self { - event, - _link: Link::new(), - } - } - - #[must_use] - #[inline] - pub const fn event(&self) -> KeyEvent { - self.event - } -} - pub struct KeyboardManager { - event_queue: MpscQueue, + event_queue: MpmcQueue, } impl Default for KeyboardManager { @@ -65,21 +23,25 @@ impl KeyboardManager { #[must_use] #[inline] pub fn new() -> Self { - let stub = KeyEvent::new(KeyCode::Unknown, KeyState::Pressed); Self { - event_queue: MpscQueue::new(Box::new(QueuedKeyEvent::new(stub))), + event_queue: MpmcQueue::new(), } } + #[inline] pub fn push_event(&self, event: KeyEvent) { - let queued_event = Box::new(QueuedKeyEvent::new(event)); - self.event_queue.enqueue(queued_event); + let push_res = self.event_queue.try_push(event); + #[cfg(debug_assertions)] + if push_res.is_err() { + // FIXME: Override old events instead of dropping new ones. + video::debug!("Keyboard event queue is full, dropping event: {:?}", event); + } } #[must_use] #[inline] pub fn poll_event(&self) -> Option { - self.event_queue.dequeue().map(|event| event.event()) + self.event_queue.pop() } } diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs index 7b5d75d..ca4032d 100644 --- a/kernel/src/drivers/ps2.rs +++ b/kernel/src/drivers/ps2.rs @@ -5,7 +5,7 @@ use beskar_core::drivers::{ }; use beskar_hal::port::{Port, ReadWrite}; use core::sync::atomic::{AtomicBool, Ordering}; -use hyperdrive::{locks::mcs::McsLock, once::Once}; +use hyperdrive::{locks::ticket::TicketLock, once::Once}; use thiserror::Error; static PS2_AVAILABLE: AtomicBool = AtomicBool::new(false); @@ -43,8 +43,8 @@ enum Ps2Error { type Ps2Result = Result; pub struct Ps2Controller { - data_port: McsLock>, - cmd_sts_port: McsLock>, + data_port: TicketLock>, + cmd_sts_port: TicketLock>, has_two_ports: AtomicBool, } @@ -77,8 +77,8 @@ impl Ps2Controller { #[inline] pub const fn new() -> Self { Self { - data_port: McsLock::new(Port::new(Self::DATA_PORT)), - cmd_sts_port: McsLock::new(Port::new(Self::CMD_STS_PORT)), + data_port: TicketLock::new(Port::new(Self::DATA_PORT)), + cmd_sts_port: TicketLock::new(Port::new(Self::CMD_STS_PORT)), has_two_ports: AtomicBool::new(false), } } @@ -99,7 +99,7 @@ impl Ps2Controller { #[must_use] #[inline] fn status_register(&self) -> u8 { - self.cmd_sts_port.with_locked(|p| unsafe { p.read() }) + unsafe { self.cmd_sts_port.lock().read() } } #[inline] @@ -171,25 +171,24 @@ impl Ps2Controller { #[inline] fn write_command(&self, command: Ps2Command) { - self.cmd_sts_port - .with_locked(|p| unsafe { p.write(command as u8) }); + unsafe { self.cmd_sts_port.lock().write(command as u8) }; } #[must_use] #[inline] fn read_status(&self) -> u8 { - self.cmd_sts_port.with_locked(|p| unsafe { p.read() }) + unsafe { self.cmd_sts_port.lock().read() } } #[inline] fn write_data(&self, data: u8) { - self.data_port.with_locked(|p| unsafe { p.write(data) }); + unsafe { self.data_port.lock().write(data) }; } #[must_use] #[inline] fn read_data(&self) -> u8 { - self.data_port.with_locked(|p| unsafe { p.read() }) + unsafe { self.data_port.lock().read() } } /// Send a Host to Device command to the PS/2 controller. diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 595622a..f798a17 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -56,7 +56,7 @@ fn kmain() -> ! { )); scheduler::spawn_thread(alloc::boxed::Box::pin(Thread::new_from_binary( - user_proc.clone(), + user_proc, Priority::Normal, alloc::vec![0; 1024*64], ))); diff --git a/kernel/src/process.rs b/kernel/src/process.rs index 6aba975..8fe7da8 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -137,6 +137,14 @@ impl Process { } } +impl Drop for Process { + fn drop(&mut self) { + // Safety: The process is finished and has no more threads running. + // We can safely close all files associated with it. + unsafe { crate::storage::vfs().close_all_from_process(self.pid.as_u64()) }; + } +} + struct BinaryData<'a> { input: binary::Binary<'a>, loaded: Once, diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index 2ed178b..3a0248e 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -266,14 +266,6 @@ impl Thread { } } -// impl Drop for Thread { -// #[inline] -// fn drop(&mut self) { -// // TODO: How to free TLS -// // (thread's address space is no longer active here) -// } -// } - #[derive(Debug, Clone, Copy)] /// Represents a snapshot of a thread's state. pub struct ThreadSnapshot { @@ -366,21 +358,19 @@ extern "C" fn user_trampoline() -> ! { if let Some(tlst) = loaded_binary.tls_template() { let tls_size = tlst.mem_size(); let num_pages = tls_size.div_ceil(M4KiB::SIZE); - let pages = super::current_process() + let pages = root_proc .address_space() .with_pgalloc(|palloc| palloc.allocate_pages::(num_pages)) .unwrap(); let flags = Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE; frame_alloc::with_frame_allocator(|fralloc| { - super::current_process() - .address_space() - .with_page_table(|pt| { - for page in pages { - let frame = fralloc.allocate_frame().unwrap(); - pt.map(page, frame, flags, fralloc).flush(); - } - }); + root_proc.address_space().with_page_table(|pt| { + for page in pages { + let frame = fralloc.allocate_frame().unwrap(); + pt.map(page, frame, flags, fralloc).flush(); + } + }); }); let tls_vaddr = pages.start().start_address(); @@ -413,6 +403,7 @@ extern "C" fn user_trampoline() -> ! { crate::arch::locals::store_thread_locals(tls); } + drop(root_proc); // Decrease the reference count of the process unsafe { crate::arch::userspace::enter_usermode(loaded_binary.entry_point(), rsp) }; } @@ -495,14 +486,6 @@ impl ThreadStacks { } } -// impl Drop for ThreadStacks { -// #[inline] -// fn drop(&mut self) { -// // TODO: -// // How to recover allocated frames and free them ? -// } -// } - #[derive(Debug, Clone, Copy)] pub struct Tls { /// The address of the TLS area. diff --git a/userspace/bashkar/src/commands.rs b/userspace/bashkar/src/commands.rs index 0265974..8cc9cd1 100644 --- a/userspace/bashkar/src/commands.rs +++ b/userspace/bashkar/src/commands.rs @@ -25,7 +25,7 @@ pub fn execute_command(command: &str, args: &[&str], tty: &mut Tty) -> CommandRe cmd_echo(args, tty); Ok(()) } - "exit" => cmd_exit(), + "exit" => beskar_lib::exit(beskar_lib::ExitCode::Success), _ => unknown(command, tty), } } @@ -60,15 +60,6 @@ fn cmd_echo(args: &[&str], tty: &mut Tty) { tty.write_str("\n"); } -fn cmd_exit() -> ! { - crate::video::screen::with_screen(|screen| { - // Safety: We are exiting the program, so we can safely close the framebuffer file. - unsafe { screen.close_file() }; - }); - - beskar_lib::exit(beskar_lib::ExitCode::Success); -} - fn unknown(command: &str, tty: &mut Tty) -> CommandResult { let string = alloc::format!("Unknown command: {command}\n"); tty.write_str(&string); diff --git a/userspace/bashkar/src/video/screen.rs b/userspace/bashkar/src/video/screen.rs index 3d342b9..90fc4fb 100644 --- a/userspace/bashkar/src/video/screen.rs +++ b/userspace/bashkar/src/video/screen.rs @@ -109,14 +109,6 @@ impl Screen { pub const fn buffer_mut(&mut self) -> &mut [u8] { self.internal_fb } - - #[inline] - /// # Safety - /// - /// The screen will be invalid for use after this function is called. - pub(crate) unsafe fn close_file(&mut self) { - beskar_lib::io::close(self.fb_file.handle()).unwrap(); - } } /// Returns the screen info. From 7b758776d2a8424067e034aef3c3d10e4eded54f Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sat, 14 Jun 2025 14:47:00 +0200 Subject: [PATCH 04/11] fix: data race in scheduler::current_* --- .github/workflows/heavy.yml | 1 - beskar-hal/src/x86_64/instructions.rs | 14 +++++++++ hyperdrive/README.md | 2 ++ kernel/src/arch/x86_64/interrupts.rs | 24 ++++++++------- kernel/src/lib.rs | 2 +- kernel/src/process/scheduler.rs | 43 ++++++++++++++++----------- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/.github/workflows/heavy.yml b/.github/workflows/heavy.yml index af2d36b..abc9f24 100644 --- a/.github/workflows/heavy.yml +++ b/.github/workflows/heavy.yml @@ -98,4 +98,3 @@ jobs: - name: Run Rustfmt run: cargo fmt --verbose --all -- --check - diff --git a/beskar-hal/src/x86_64/instructions.rs b/beskar-hal/src/x86_64/instructions.rs index efe5476..63c5e71 100644 --- a/beskar-hal/src/x86_64/instructions.rs +++ b/beskar-hal/src/x86_64/instructions.rs @@ -34,5 +34,19 @@ pub fn halt() { } } +#[inline] +pub fn int_disable() { + unsafe { + core::arch::asm!("cli", options(nomem, preserves_flags, nostack)); + } +} + +#[inline] +pub fn int_enable() { + unsafe { + core::arch::asm!("sti", options(nomem, preserves_flags, nostack)); + } +} + /// This value can be used to fill the stack when debugging stack overflows. pub const STACK_DEBUG_INSTR: u8 = 0xCC; diff --git a/hyperdrive/README.md b/hyperdrive/README.md index 524cc19..fe01a88 100644 --- a/hyperdrive/README.md +++ b/hyperdrive/README.md @@ -15,6 +15,8 @@ It defines: - Views - Volatile Pointers with compile-time Access Rights - Queues + - Multiple Producer, Multiple Consumer - Multiple Producer, Single Consumer and intrusive + - Simple FIFO Ring Buffer - Sync - Barrier diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index d1e2ba0..0183de5 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -1,6 +1,7 @@ use super::gdt::{DOUBLE_FAULT_IST, PAGE_FAULT_IST}; use crate::locals; use beskar_hal::{ + instructions::{int_disable, int_enable}, registers::{CS, Cr0, Cr2}, structures::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, }; @@ -197,16 +198,19 @@ extern "x86-interrupt" fn machine_check_handler(_stack_frame: InterruptStackFram info_isr!(spurious_interrupt_handler); #[inline] -pub fn int_disable() { - unsafe { - core::arch::asm!("cli", options(nomem, preserves_flags, nostack)); - } -} - -#[inline] -pub fn int_enable() { - unsafe { - core::arch::asm!("sti", options(nomem, preserves_flags, nostack)); +pub fn without_interrupts(f: F) -> R +where + F: FnOnce() -> R, +{ + let rflags = beskar_hal::registers::Rflags::read(); + if rflags & beskar_hal::registers::Rflags::IF == 0 { + // Interrupts are already disabled, just call the function + f() + } else { + int_disable(); + let result = f(); + int_enable(); + result } } diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 53fcdba..cb8c60a 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -25,7 +25,7 @@ static KERNEL_PANIC: Once<()> = Once::uninit(); #[panic_handler] fn panic(panic_info: &core::panic::PanicInfo) -> ! { - arch::interrupts::int_disable(); + beskar_hal::instructions::int_disable(); #[cfg(debug_assertions)] video::error!("[PANIC]: Core {} - {}", locals!().core_id(), panic_info); diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index 7d35684..eaafd02 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -1,4 +1,4 @@ -use crate::locals; +use crate::{arch::interrupts::without_interrupts, locals}; use alloc::{boxed::Box, collections::btree_map::BTreeMap, sync::Arc}; use beskar_core::arch::VirtAddr; use core::{ @@ -119,14 +119,14 @@ impl Scheduler { /// if scheduling was successful. fn reschedule(&self) -> Option { self.current_thread.try_with_locked(|thread| { - crate::arch::interrupts::int_disable(); + beskar_hal::instructions::int_disable(); // Swap the current thread with the next one. let mut new_thread = Pin::into_inner(if let Some(new_thread) = QUEUE.get().unwrap().next() { new_thread } else { - crate::arch::interrupts::int_enable(); + beskar_hal::instructions::int_enable(); return None; }); @@ -229,32 +229,39 @@ pub(crate) fn reschedule() -> Option { #[inline] /// Returns the current thread ID. pub fn current_thread_id() -> ThreadId { - // Safety: - // If the scheduler changes the values mid read, - // it means the current thread is no longer executed. - // Upon return, the thread will be the same as before! - unsafe { get_scheduler().current_thread.force_lock() }.id() + without_interrupts(|| { + // Safety: + // If the scheduler changes the values mid read, + // it means the current thread is no longer executed. + // Upon return, the thread will be the same as before! + unsafe { get_scheduler().current_thread.force_lock() }.id() + }) } #[must_use] #[inline] /// Returns the current thread's state. pub(crate) fn current_thread_snapshot() -> thread::ThreadSnapshot { - // Safety: - // If the scheduler changes the values mid read, - // it means the current thread is no longer executed. - // Upon return, the thread will be the same as before! - unsafe { get_scheduler().current_thread.force_lock() }.snapshot() + without_interrupts(|| { + // Safety: + // If the scheduler changes the values mid read, + // it means the current thread is no longer executed. + // Upon return, the thread will be the same as before! + unsafe { get_scheduler().current_thread.force_lock() }.snapshot() + }) } #[must_use] #[inline] /// Returns the current process. pub fn current_process() -> Arc { - // Safety: - // Swapping current thread is done using a memory swap of a `Box` (pointer), so it is impossible - // that the current thread is "partly" read before swap and "partly" after swap. - unsafe { get_scheduler().current_thread.force_lock() }.process() + without_interrupts(|| { + // Safety: + // If the scheduler changes the values mid read, + // it means the current thread is no longer executed. + // Upon return, the thread will be the same as before! + unsafe { get_scheduler().current_thread.force_lock() }.process() + }) } #[inline] @@ -304,7 +311,7 @@ pub unsafe fn exit_current_thread() -> ! { thread_yield(); // If no thread is waiting, loop. - crate::arch::interrupts::int_enable(); + beskar_hal::instructions::int_enable(); loop { crate::arch::halt(); } From 2b092e8d9ccf3089b757fb333317588520973b88 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sun, 15 Jun 2025 12:22:36 +0200 Subject: [PATCH 05/11] fix: possible data race when calling get_scheduler --- hyperdrive/src/queues/mpmc.rs | 48 +++++++++++++++++++------- kernel/src/process/scheduler.rs | 47 ++++++++++++------------- kernel/src/process/scheduler/thread.rs | 18 +++++----- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/hyperdrive/src/queues/mpmc.rs b/hyperdrive/src/queues/mpmc.rs index 72edcce..78c931a 100644 --- a/hyperdrive/src/queues/mpmc.rs +++ b/hyperdrive/src/queues/mpmc.rs @@ -71,6 +71,32 @@ impl Slot { value: UnsafeCell::new(MaybeUninit::uninit()), } } + + #[inline] + /// Writes a value to this slot and updates the sequence number. + /// + /// # Safety + /// + /// The caller must ensure that the value is valid and that no other thread + /// is writing to this slot at the same time. + pub unsafe fn write(&self, value: T, pos: usize) { + unsafe { (*self.value.get()).write(value) }; + self.sequence.store(pos + 1, Ordering::Release); + } + + #[must_use] + #[inline] + /// Reads a value from this slot and updates the sequence number. + /// + /// # Safety + /// + /// The caller must ensure that the value is valid and that no other thread + /// is writing to this slot at the same time. + pub unsafe fn read(&self, pos: usize, size: usize) -> T { + let value = unsafe { (&*self.value.get()).assume_init_read() }; + self.sequence.store(pos + size, Ordering::Release); + value + } } // Safety: Data races are avoided by using atomic operations for the indices. @@ -133,10 +159,11 @@ impl MpmcQueue { Ordering::Relaxed, ) { Ok(_old) => { - // Safety: We are the only thread writing to this slot and the pointer is valid. - unsafe { (*slot.value.get()).write(value) }; - slot.sequence.store(pos + 1, Ordering::Release); - break; // Successfully pushed the value + // Safety: We are the only thread writing to this slot. + unsafe { + slot.write(value, pos); + }; + break Ok(()); // Successfully pushed the value } Err(current) => { pos = current; // Retry with the current position @@ -144,15 +171,13 @@ impl MpmcQueue { } } core::cmp::Ordering::Less => { - return Err(MpmcQueueFullError(value)); // Queue is full + break Err(MpmcQueueFullError(value)); // Queue is full } core::cmp::Ordering::Greater => { pos = self.write_index.load(Ordering::Relaxed); // Retry } } } - - Ok(()) } #[must_use] @@ -173,10 +198,9 @@ impl MpmcQueue { Ordering::Relaxed, ) { Ok(_old) => { - // Safety: We are the only thread writing to this slot and the pointer is valid. - let value = unsafe { (&*slot.value.get()).assume_init_read() }; - slot.sequence.store(pos + SIZE, Ordering::Release); - return Some(value); // Successfully pushed the value + // Safety: We are the only thread accessing this slot. + let value = unsafe { slot.read(pos, SIZE) }; + break Some(value); // Successfully pushed the value } Err(current) => { pos = current; // Retry with the current position @@ -184,7 +208,7 @@ impl MpmcQueue { } } core::cmp::Ordering::Less => { - return None; // Queue is empty + break None; // Queue is empty } core::cmp::Ordering::Greater => { pos = self.read_index.load(Ordering::Relaxed); // Retry diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index eaafd02..4a11677 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -190,10 +190,12 @@ impl Scheduler { } } -#[must_use] #[inline] -fn get_scheduler() -> &'static Scheduler { - locals!().scheduler().get().unwrap() +fn with_scheduler R>(f: F) -> R { + without_interrupts(|| { + let scheduler = locals!().scheduler().get().unwrap(); + f(scheduler) + }) } /// A thread should be spawned with this function. @@ -222,19 +224,17 @@ extern "C" fn guard_thread() -> ! { /// /// This function does not perform the context switch. pub(crate) fn reschedule() -> Option { - get_scheduler().reschedule() + with_scheduler(Scheduler::reschedule) } #[must_use] #[inline] /// Returns the current thread ID. pub fn current_thread_id() -> ThreadId { - without_interrupts(|| { + with_scheduler(|scheduler| { // Safety: - // If the scheduler changes the values mid read, - // it means the current thread is no longer executed. - // Upon return, the thread will be the same as before! - unsafe { get_scheduler().current_thread.force_lock() }.id() + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current_thread.force_lock() }.id() }) } @@ -242,12 +242,10 @@ pub fn current_thread_id() -> ThreadId { #[inline] /// Returns the current thread's state. pub(crate) fn current_thread_snapshot() -> thread::ThreadSnapshot { - without_interrupts(|| { + with_scheduler(|scheduler| { // Safety: - // If the scheduler changes the values mid read, - // it means the current thread is no longer executed. - // Upon return, the thread will be the same as before! - unsafe { get_scheduler().current_thread.force_lock() }.snapshot() + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current_thread.force_lock() }.snapshot() }) } @@ -255,12 +253,10 @@ pub(crate) fn current_thread_snapshot() -> thread::ThreadSnapshot { #[inline] /// Returns the current process. pub fn current_process() -> Arc { - without_interrupts(|| { + with_scheduler(|scheduler| { // Safety: - // If the scheduler changes the values mid read, - // it means the current thread is no longer executed. - // Upon return, the thread will be the same as before! - unsafe { get_scheduler().current_thread.force_lock() }.process() + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current_thread.force_lock() }.process() }) } @@ -293,7 +289,9 @@ pub fn set_scheduling(enable: bool) { #[inline] pub fn change_current_thread_priority(priority: priority::Priority) { - get_scheduler().change_current_thread_priority(priority); + with_scheduler(|scheduler| { + scheduler.change_current_thread_priority(priority); + }); } /// Exits the current thread. @@ -305,7 +303,7 @@ pub fn change_current_thread_priority(priority: priority::Priority) { /// The context will be brutally switched without returning. /// If any locks are acquired, they will be poisoned. pub unsafe fn exit_current_thread() -> ! { - get_scheduler().exit_current_thread(); + with_scheduler(Scheduler::exit_current_thread); // Try to reschedule the thread. thread_yield(); @@ -347,9 +345,10 @@ impl hyperdrive::locks::BackOff for Yield { /// Put the current thread to sleep. pub fn sleep() { - get_scheduler() - .should_sleep_thread - .store(true, Ordering::Relaxed); + with_scheduler(|scheduler| { + scheduler.should_sleep_thread.store(true, Ordering::Relaxed); + }); + if !thread_yield() { // TODO: What to do if the thread was not rescheduled? // Maybe push the thread in the sleeping queue and diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index 3a0248e..890293b 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -344,16 +344,16 @@ extern "C" fn user_trampoline() -> ! { let loaded_binary = root_proc.load_binary(); // Allocate a user stack - let rsp = super::get_scheduler() - .current_thread - .with_locked(|t| { + let rsp = super::with_scheduler(|scheduler| { + scheduler.current_thread.with_locked(|t| { t.stack.as_mut().map(|ts| { ts.allocate_all(4 * M4KiB::SIZE); ts.user_stack_top().unwrap() }) }) - .expect("Thread stack not found") - .as_ptr(); + }) + .expect("Current thread stack allocation failed") + .as_ptr(); if let Some(tlst) = loaded_binary.tls_template() { let tls_size = tlst.mem_size(); @@ -397,9 +397,11 @@ extern "C" fn user_trampoline() -> ! { // Locking the scheduler's current thread is a bit ugly, but it is better than force locking it // (as otherwise the scheduler could get stuck on `Once::get`). - super::get_scheduler() - .current_thread - .with_locked(|t| t.tls.call_once(|| tls)); + super::with_scheduler(|scheduler| { + scheduler.current_thread.with_locked(|t| { + t.tls.call_once(|| tls); + }); + }); crate::arch::locals::store_thread_locals(tls); } From 89a810543a12550c932ded1bfb9e2875962fc2e2 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Mon, 16 Jun 2025 18:33:56 +0200 Subject: [PATCH 06/11] code improvements --- Cargo.toml | 3 +- bootloader/src/kernel_elf.rs | 42 ++----------------- kernel/src/arch/x86_64/ap.rs | 6 ++- kernel/src/arch/x86_64/context.rs | 9 ++-- kernel/src/arch/x86_64/gdt.rs | 10 ++--- kernel/src/drivers/pci.rs | 28 ++++++++----- kernel/src/drivers/ps2.rs | 58 +++++++++++++++++--------- kernel/src/drivers/storage/nvme.rs | 2 +- kernel/src/main.rs | 4 +- kernel/src/mem/address_space.rs | 18 ++++---- kernel/src/process.rs | 5 +++ kernel/src/process/scheduler.rs | 21 +++++++--- kernel/src/process/scheduler/thread.rs | 6 +-- 13 files changed, 113 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b4b81d..ebc74f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,8 @@ bashkar = { path = "userspace/bashkar", artifact = "bin", target = "x86_64-unkno [profile.release] panic = "abort" -lto = true +lto = "fat" +strip = "symbols" [profile.dev] panic = "abort" diff --git a/bootloader/src/kernel_elf.rs b/bootloader/src/kernel_elf.rs index 7c953a6..0d34b27 100644 --- a/bootloader/src/kernel_elf.rs +++ b/bootloader/src/kernel_elf.rs @@ -1,8 +1,8 @@ +use crate::mem::{EarlyFrameAllocator, Level4Entries}; use beskar_core::arch::{ PhysAddr, VirtAddr, paging::{CacheFlush, Frame, FrameAllocator, M4KiB, Mapper as _, MemSize, Page, Translator}, }; - use beskar_hal::paging::page_table::{Flags, OffsetPageTable}; use xmas_elf::{ ElfFile, @@ -12,8 +12,6 @@ use xmas_elf::{ sections::Rela, }; -use crate::mem::{EarlyFrameAllocator, Level4Entries}; - pub struct KernelLoadingUtils<'a> { kernel: &'a ElfFile<'a>, level_4_entries: &'a mut Level4Entries, @@ -403,24 +401,7 @@ fn handle_segment_dynamic(dynamic_segment: ProgramHeader, klu: &mut KernelLoadin fn copy_from_krnlspc(klu: &KernelLoadingUtils, addr: VirtAddr, buf: &mut [u8]) { let end_addr = { let offset = u64::try_from(buf.len() - 1).unwrap(); - assert!( - offset < 0x1_0000_0000_0000, - "Outside of virtual address space" - ); - - let mut addr = addr.as_u64().checked_add(offset).expect("Address overflow"); - - match addr >> 47 { - 0x1 => { - addr |= 0x1FFFF << 47; - } - 0x2 => { - panic!("Address overflow"); - } - _ => {} - } - - VirtAddr::new(addr) + addr + offset }; let start_page = Page::::containing_address(addr); @@ -466,24 +447,7 @@ fn copy_from_krnlspc(klu: &KernelLoadingUtils, addr: VirtAddr, buf: &mut [u8]) { fn copy_to_krnlspc(klu: &mut KernelLoadingUtils, addr: VirtAddr, buf: &[u8]) { let end_addr = { let offset = u64::try_from(buf.len() - 1).unwrap(); - assert!( - offset < 0x1_0000_0000_0000, - "Outside of virtual address space" - ); - - let mut addr = addr.as_u64().checked_add(offset).expect("Address overflow"); - - match addr >> 47 { - 0x1 => { - addr |= 0x1FFFF << 47; - } - 0x2 => { - panic!("Address overflow"); - } - _ => {} - } - - VirtAddr::new(addr) + addr + offset }; let start_page = Page::::containing_address(addr); diff --git a/kernel/src/arch/x86_64/ap.rs b/kernel/src/arch/x86_64/ap.rs index a3aaf8c..8041d68 100644 --- a/kernel/src/arch/x86_64/ap.rs +++ b/kernel/src/arch/x86_64/ap.rs @@ -81,7 +81,11 @@ pub fn start_up_aps(core_count: usize) { // Update section .data of the AP trampoline code // Entry Point address - write_sipi(payload_vaddr, 0, crate::boot::kap_entry as u64); + write_sipi( + payload_vaddr, + 0, + u64::try_from(crate::boot::kap_entry as usize).unwrap(), + ); // Pointer to the address of the top of the stack // Note that using `as_ptr` is safe as the trampoline code uses atomic instructions diff --git a/kernel/src/arch/x86_64/context.rs b/kernel/src/arch/x86_64/context.rs index 1227e86..c5b091e 100644 --- a/kernel/src/arch/x86_64/context.rs +++ b/kernel/src/arch/x86_64/context.rs @@ -1,4 +1,4 @@ -use beskar_hal::registers::Cr0; +use beskar_hal::registers::{Cr0, Rflags}; #[unsafe(naked)] /// Switches the current stack and CR3 to the ones provided. @@ -69,7 +69,7 @@ pub unsafe extern "C" fn switch(old_stack: *mut *mut u8, new_stack: *const u8, c ); } -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] /// Registers that are relevant for the thread context. pub struct ThreadRegisters { @@ -95,7 +95,10 @@ pub struct ThreadRegisters { impl ThreadRegisters { #[must_use] #[inline] - pub const fn new(rflags: u64, rip: u64, rbp: u64) -> Self { + pub fn new(entry: extern "C" fn() -> !, rbp: *mut u8) -> Self { + let rip = u64::try_from(entry as usize).unwrap(); + let rbp = u64::try_from(rbp as usize).unwrap(); + let rflags = Rflags::IF; Self { r15: 0, r14: 0, diff --git a/kernel/src/arch/x86_64/gdt.rs b/kernel/src/arch/x86_64/gdt.rs index e203fa4..f7aefd1 100644 --- a/kernel/src/arch/x86_64/gdt.rs +++ b/kernel/src/arch/x86_64/gdt.rs @@ -16,7 +16,7 @@ pub const PAGE_FAULT_IST: u8 = 1; pub struct Gdt { loaded: bool, - gdt: MaybeUninit, + inner_gdt: MaybeUninit, tss: MaybeUninit, kernel_code_selector: MaybeUninit, kernel_data_selector: MaybeUninit, @@ -30,7 +30,7 @@ impl Gdt { pub const fn uninit() -> Self { Self { loaded: false, - gdt: MaybeUninit::uninit(), + inner_gdt: MaybeUninit::uninit(), tss: MaybeUninit::uninit(), kernel_code_selector: MaybeUninit::uninit(), kernel_data_selector: MaybeUninit::uninit(), @@ -55,7 +55,7 @@ impl Gdt { let user_data_selector = gdt.append(GdtDescriptor::user_data_segment()); let user_code_selector = gdt.append(GdtDescriptor::user_code_segment()); - self.gdt.write(gdt); + self.inner_gdt.write(gdt); self.tss.write(tss); self.kernel_code_selector.write(kernel_code_selector); self.kernel_data_selector.write(kernel_data_selector); @@ -64,7 +64,7 @@ impl Gdt { // Safety: We just initialized the GDT. // According to function's safety guards, `self` is valid for `'static`. - let gdt = unsafe { &mut *core::ptr::from_mut(self.gdt.assume_init_mut()) }; + let gdt = unsafe { &mut *core::ptr::from_mut(self.inner_gdt.assume_init_mut()) }; // Safety: We just initialized the TSS. // According to function's safety guards, `self` is valid for `'static`. @@ -179,7 +179,7 @@ impl Gdt { #[inline] pub const fn gdt(&self) -> Option<&GlobalDescriptorTable> { if self.loaded { - Some(unsafe { self.gdt.assume_init_ref() }) + Some(unsafe { self.inner_gdt.assume_init_ref() }) } else { None } diff --git a/kernel/src/drivers/pci.rs b/kernel/src/drivers/pci.rs index da8a944..bcfc03a 100644 --- a/kernel/src/drivers/pci.rs +++ b/kernel/src/drivers/pci.rs @@ -8,16 +8,24 @@ static PCIE_HANDLER: MUMcsLock>> = MUMc static LEGACY_PCI_HANDLER: McsLock = McsLock::new(LegacyPciHandler::new()); pub fn init() -> DriverResult<()> { - if let Ok(device_count) = init_express() { - video::info!("PCIe devices found: {}", device_count); - DriverResult::Ok(()) - } else if let Ok(device_count) = init_legacy() { - video::info!("Legacy PCI devices found: {}", device_count); - DriverResult::Ok(()) - } else { - video::error!("PCI failed to initialize or no PCI devices were found"); - DriverResult::Err(DriverError::Invalid) - } + init_express().map_or_else( + |_| { + init_legacy().map_or_else( + |_| { + video::error!("PCI failed to initialize or no PCI devices were found"); + DriverResult::Err(DriverError::Invalid) + }, + |device_count| { + video::info!("Legacy PCI devices found: {}", device_count); + DriverResult::Ok(()) + }, + ) + }, + |device_count| { + video::info!("PCIe devices found: {}", device_count); + DriverResult::Ok(()) + }, + ) } fn init_express() -> DriverResult { diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs index ca4032d..2474ccd 100644 --- a/kernel/src/drivers/ps2.rs +++ b/kernel/src/drivers/ps2.rs @@ -24,20 +24,35 @@ pub fn init() -> DriverResult<()> { } #[derive(Error, Debug, Clone, Copy)] -enum Ps2Error { +pub enum Ps2Error { #[error("PS/2 controller self-test failed")] - SelfTestFailed, + SelfTest, #[error("PS/2 controller first port test failed")] - FirstPortTestFailed, - #[error("PS/2 controller second port test failed")] - SecondPortTestFailed, + FirstPortTest, + // #[error("PS/2 controller second port test failed")] + // SecondPortTest, #[error("PS/2 keyboard reset failed")] - KeyboardResetFailed, + KeyboardReset, + #[error("PS/2 controller does not support keyboard")] + KeyboardUnsupported, #[error("PS/2 controller data send failed")] - SendingFailed, + Sending, #[error("PS/2 controller data receive failed")] - ReceivingFailed, + Receiving, +} + +impl From for beskar_core::drivers::DriverError { + fn from(error: Ps2Error) -> Self { + match error { + Ps2Error::KeyboardUnsupported => Self::Absent, + Ps2Error::FirstPortTest + // | Ps2Error::SecondPortTest + | Ps2Error::KeyboardReset + | Ps2Error::SelfTest => Self::Invalid, + Ps2Error::Sending | Ps2Error::Receiving => Self::Unknown, + } + } } type Ps2Result = Result; @@ -52,8 +67,8 @@ enum Ps2Command { DisableFirstPort = 0xAD, DisableSecondPort = 0xA7, EnableFirstPort = 0xAE, - EnableSecondPort = 0xA8, - TestSecondPort = 0xA9, + // EnableSecondPort = 0xA8, + // TestSecondPort = 0xA9, TestFirstPort = 0xAB, ReadConfigByte = 0x20, WriteConfigByte = 0x60, @@ -107,12 +122,12 @@ impl Ps2Controller { let _ = self.read_data(); } - pub fn initialize(&self) -> DriverResult<()> { + pub fn initialize(&self) -> Ps2Result<()> { let keyboard_support = ACPI.get().unwrap().fadt().ps2_keyboard(); PS2_AVAILABLE.store(keyboard_support, Ordering::Relaxed); if !keyboard_support { video::warn!("PS/2 controller not supported by ACPI"); - return Err(beskar_core::drivers::DriverError::Absent); + return Err(Ps2Error::KeyboardUnsupported); } self.write_command(Ps2Command::DisableFirstPort); @@ -139,7 +154,7 @@ impl Ps2Controller { } if !has_passed { video::warn!("PS/2 controller self-test failed"); - return Err(beskar_core::drivers::DriverError::Invalid); + return Err(Ps2Error::SelfTest); } } self.write_config(config); @@ -156,7 +171,7 @@ impl Ps2Controller { } if !has_passed { video::warn!("PS/2 controller first port test failed"); - return Err(beskar_core::drivers::DriverError::Invalid); + return Err(Ps2Error::FirstPortTest); } } @@ -199,7 +214,7 @@ impl Ps2Controller { return Ok(()); } } - Err(Ps2Error::SendingFailed) + Err(Ps2Error::Sending) } /// Receive a Device to Host command from the PS/2 controller. @@ -209,7 +224,7 @@ impl Ps2Controller { return Ok(self.read_data()); } } - Err(Ps2Error::ReceivingFailed) + Err(Ps2Error::Receiving) } #[inline] @@ -220,6 +235,7 @@ impl Ps2Controller { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ScancodeSet { Set1 = 1, Set2 = 2, @@ -299,7 +315,7 @@ impl<'a> Ps2Keyboard<'a> { // Resend } else if value == 0xFC || value == 0xFD { // SelfTestFail - return Err(Ps2Error::KeyboardResetFailed); + return Err(Ps2Error::KeyboardReset); } } // Try one last time to propagate error @@ -323,7 +339,11 @@ impl<'a> Ps2Keyboard<'a> { } #[must_use] - pub fn scancode_to_keycode(&self, scancode: u8) -> Option { + pub fn scancode_to_keycode(&self, extended: bool, scancode: u8) -> Option { + if extended { + // TODO: Handle extended keys + return None; + } match self.scancode_set { ScancodeSet::Set1 => Self::scancode_set1_to_keycode(scancode), // 2 => self.scancode_set2_to_char(scancode), @@ -501,7 +521,7 @@ pub fn handle_keyboard_interrupt() { fn handle_real_key(extended: bool, key: u8) { let keyboard = PS2_KEYBOARD.get().unwrap(); - let Some(key_event) = keyboard.scancode_to_keycode(key) else { + let Some(key_event) = keyboard.scancode_to_keycode(extended, key) else { video::warn!("Unknown key: {:#X}", key); return; }; diff --git a/kernel/src/drivers/storage/nvme.rs b/kernel/src/drivers/storage/nvme.rs index 2e92c00..7064451 100644 --- a/kernel/src/drivers/storage/nvme.rs +++ b/kernel/src/drivers/storage/nvme.rs @@ -197,7 +197,7 @@ impl NvmeControllers { .maximum_data_transfer_size() .map_or(u64::MAX, |raw| { let mps_min = u64::from(self.capabilities().mpsmin()); - mps_min.checked_mul(1 << raw.get()).unwrap_or(u64::MAX) + mps_min.saturating_mul(1 << raw.get()) }); // --- Part Three: I/O queues creation --- diff --git a/kernel/src/main.rs b/kernel/src/main.rs index f798a17..848a372 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -36,7 +36,7 @@ fn kmain() -> ! { beskar_hal::process::Kind::Driver, None, )); - scheduler::spawn_thread(alloc::boxed::Box::pin(Thread::new( + scheduler::spawn_thread(alloc::boxed::Box::new(Thread::new( driver_proc, Priority::Low, alloc::vec![0; 1024 * 128], @@ -55,7 +55,7 @@ fn kmain() -> ! { )), )); - scheduler::spawn_thread(alloc::boxed::Box::pin(Thread::new_from_binary( + scheduler::spawn_thread(alloc::boxed::Box::new(Thread::new_from_binary( user_proc, Priority::Normal, alloc::vec![0; 1024*64], diff --git a/kernel/src/mem/address_space.rs b/kernel/src/mem/address_space.rs index e568d5f..27c812c 100644 --- a/kernel/src/mem/address_space.rs +++ b/kernel/src/mem/address_space.rs @@ -21,14 +21,13 @@ pub fn init(recursive_index: u16, kernel_info: &KernelInfo) { KERNEL_CODE_INFO.call_once(|| *kernel_info); let kernel_pt = { - let bootloader_pt_vaddr = { - let recursive_index = u64::from(recursive_index); - let vaddr = (recursive_index << 39) - | (recursive_index << 30) - | (recursive_index << 21) - | (recursive_index << 12); - VirtAddr::new_extend(vaddr) - }; + let bootloader_pt_vaddr = VirtAddr::from_pt_indices( + recursive_index, + recursive_index, + recursive_index, + recursive_index, + 0, + ); // Safety: The page table given by the bootloader is valid let bootloader_pt = unsafe { &mut *bootloader_pt_vaddr.as_mut_ptr() }; @@ -178,7 +177,8 @@ impl AddressSpace { /// Returns whether a certain memory range is owned by the address space. pub fn is_addr_owned(&self, start: VirtAddr, end: VirtAddr) -> bool { let Some(idx) = self.pgalloc_pml4_idx else { - video::warn!("`AddressSpace::is_addr_owned` called on a non-user address space"); + #[cfg(debug_assertions)] + video::warn!("`AddressSpace::is_addr_owned` called without PGALLOC PML4 index"); return false; }; diff --git a/kernel/src/process.rs b/kernel/src/process.rs index 8fe7da8..e8a53ee 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -139,6 +139,11 @@ impl Process { impl Drop for Process { fn drop(&mut self) { + video::debug!( + "Process \"{}\" (PID: {}) is being dropped", + self.name, + self.pid.as_u64() + ); // Safety: The process is finished and has no more threads running. // We can safely close all files associated with it. unsafe { crate::storage::vfs().close_all_from_process(self.pid.as_u64()) }; diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index 4a11677..cb243c5 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -52,7 +52,7 @@ pub unsafe fn init(kernel_thread: thread::Thread) { guard_thread, ); - spawn_thread(Box::pin(clean_thread)); + spawn_thread(Box::new(clean_thread)); }); } @@ -101,6 +101,11 @@ impl Scheduler { self.should_exit_thread.store(true, Ordering::Relaxed); } + #[inline] + pub fn sleep_current_thread(&self) { + self.should_sleep_thread.store(true, Ordering::Relaxed); + } + #[must_use] #[inline] pub fn current_priority(&self) -> priority::Priority { @@ -188,6 +193,11 @@ impl Scheduler { }) })? } + + pub fn schedule(mut thread: Box) { + unsafe { thread.set_state(thread::ThreadState::Ready) }; + QUEUE.get().unwrap().append(Pin::new(thread)); + } } #[inline] @@ -261,8 +271,8 @@ pub fn current_process() -> Arc { } #[inline] -pub fn spawn_thread(thread: Pin>) { - QUEUE.get().unwrap().append(thread); +pub fn spawn_thread(thread: Box) { + Scheduler::schedule(thread); } /// Sets the scheduling of the scheduler. @@ -315,6 +325,7 @@ pub unsafe fn exit_current_thread() -> ! { } } +#[expect(clippy::must_use_candidate, reason = "Yields the CPU")] /// Hint to the scheduler to reschedule the current thread. /// /// Returns `true` if the thread was rescheduled, `false` otherwise. @@ -346,7 +357,7 @@ impl hyperdrive::locks::BackOff for Yield { /// Put the current thread to sleep. pub fn sleep() { with_scheduler(|scheduler| { - scheduler.should_sleep_thread.store(true, Ordering::Relaxed); + scheduler.sleep_current_thread(); }); if !thread_yield() { @@ -363,7 +374,7 @@ pub fn sleep() { pub fn wake_up(thread: ThreadId) -> bool { SLEEPING.with_locked(|wq| { wq.remove(&thread).is_some_and(|thread| { - QUEUE.get().unwrap().append(Pin::new(thread)); + Scheduler::schedule(thread); true }) }) diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index 890293b..474428f 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -4,9 +4,7 @@ use beskar_core::arch::{ VirtAddr, paging::{CacheFlush, FrameAllocator, M4KiB, Mapper, MemSize, PageRangeInclusive}, }; -use beskar_hal::{ - instructions::STACK_DEBUG_INSTR, paging::page_table::Flags, registers::Rflags, userspace::Ring, -}; +use beskar_hal::{instructions::STACK_DEBUG_INSTR, paging::page_table::Flags, userspace::Ring}; use core::{ mem::offset_of, pin::Pin, @@ -167,7 +165,7 @@ impl Thread { stack_bottom -= size_of::(); // Push the thread registers - let thread_regs = ThreadRegisters::new(Rflags::IF, entry_point as u64, stack_ptr as u64); + let thread_regs = ThreadRegisters::new(entry_point, stack_ptr); let thread_regs_bytes = unsafe { core::mem::transmute::()]>(thread_regs) }; From c4e51c6a85735d105b89b8dadf6c91a6393ac801 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Mon, 14 Jul 2025 13:55:38 +0200 Subject: [PATCH 07/11] code improvements --- Cargo.lock | 19 +- beskar-core/src/arch/addrs.rs | 2 + beskar-core/src/syscall.rs | 1 + beskar-core/src/time.rs | 4 + beskar-hal/src/x86_64/instructions.rs | 21 ++- beskar-hal/src/x86_64/paging.rs | 2 +- beskar-hal/src/x86_64/structures.rs | 10 +- hyperdrive/src/locks/mcs.rs | 41 ++--- hyperdrive/src/locks/rw.rs | 6 +- hyperdrive/src/locks/ticket.rs | 1 - hyperdrive/src/once.rs | 59 +++++-- hyperdrive/src/queues/mpmc.rs | 7 +- hyperdrive/src/queues/mpsc.rs | 1 + kernel/foundry/storage/src/fs.rs | 10 ++ kernel/foundry/storage/src/fs/in_mem.rs | 225 ++++++++++++++++++++++++ kernel/foundry/storage/src/lib.rs | 1 + kernel/src/arch/x86_64/interrupts.rs | 39 ++-- kernel/src/lib.rs | 8 +- kernel/src/mem/heap.rs | 7 +- kernel/src/process.rs | 7 +- kernel/src/process/scheduler.rs | 206 ++++++++++------------ kernel/src/process/scheduler/thread.rs | 20 +-- userspace/bashkar/src/commands.rs | 8 +- userspace/bashkar/src/main.rs | 2 + 24 files changed, 471 insertions(+), 236 deletions(-) create mode 100644 kernel/foundry/storage/src/fs/in_mem.rs diff --git a/Cargo.lock b/Cargo.lock index 61ba83a..b39679f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,18 +152,19 @@ checksum = "1064d564ae026ae123bf5d607318b42a5b31d70a3c48e6ea7ee44cce4cdb095e" [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", @@ -219,6 +220,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "storage" version = "0.1.0" @@ -230,9 +237,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/beskar-core/src/arch/addrs.rs b/beskar-core/src/arch/addrs.rs index 977e30e..a28c7ea 100644 --- a/beskar-core/src/arch/addrs.rs +++ b/beskar-core/src/arch/addrs.rs @@ -16,6 +16,7 @@ impl VirtAddr { pub const ZERO: Self = Self(0); #[must_use] + #[track_caller] #[inline] pub const fn new(addr: u64) -> Self { Self::try_new(addr).expect("Invalid virtual address") @@ -159,6 +160,7 @@ impl PhysAddr { pub const ZERO: Self = Self(0); #[must_use] + #[track_caller] #[inline] pub const fn new(addr: u64) -> Self { Self::try_new(addr).expect("Invalid physical address") diff --git a/beskar-core/src/syscall.rs b/beskar-core/src/syscall.rs index d1f534f..a27ba50 100644 --- a/beskar-core/src/syscall.rs +++ b/beskar-core/src/syscall.rs @@ -68,6 +68,7 @@ pub enum SyscallExitCode { } impl SyscallExitCode { + #[track_caller] #[inline] /// Unwraps the syscall exit code, panicking if it is a failure. /// diff --git a/beskar-core/src/time.rs b/beskar-core/src/time.rs index 9e6e6dd..e719b28 100644 --- a/beskar-core/src/time.rs +++ b/beskar-core/src/time.rs @@ -24,6 +24,7 @@ impl Instant { Self { micros } } #[must_use] + #[track_caller] #[inline] /// Create a new `Instant` from a number of milliseconds. pub const fn from_millis(millis: u64) -> Self { @@ -33,6 +34,7 @@ impl Instant { Self::from_micros(micros) } #[must_use] + #[track_caller] #[inline] /// Create a new `Instant` from a number of seconds. pub const fn from_secs(secs: u64) -> Self { @@ -160,6 +162,7 @@ impl Duration { Self { micros } } #[must_use] + #[track_caller] #[inline] /// Create a new `Duration` from a number of milliseconds. pub const fn from_millis(millis: u64) -> Self { @@ -169,6 +172,7 @@ impl Duration { Self::from_micros(micros) } #[must_use] + #[track_caller] #[inline] /// Create a new `Duration` from a number of seconds. pub const fn from_secs(secs: u64) -> Self { diff --git a/beskar-hal/src/x86_64/instructions.rs b/beskar-hal/src/x86_64/instructions.rs index 63c5e71..5cb794f 100644 --- a/beskar-hal/src/x86_64/instructions.rs +++ b/beskar-hal/src/x86_64/instructions.rs @@ -1,7 +1,7 @@ #[inline] pub unsafe fn load_tss(selector: u16) { unsafe { - core::arch::asm!("ltr {:x}", in(reg) selector, options(nostack, preserves_flags)); + core::arch::asm!("ltr {:x}", in(reg) selector, options(nostack, readonly, preserves_flags)); } } @@ -48,5 +48,24 @@ pub fn int_enable() { } } +#[inline] +pub fn without_interrupts(f: F) -> R +where + F: FnOnce() -> R, +{ + use crate::registers::Rflags; + + let rflags = Rflags::read(); + if rflags & Rflags::IF == 0 { + // Interrupts are already disabled, just call the function + f() + } else { + int_disable(); + let result = f(); + int_enable(); + result + } +} + /// This value can be used to fill the stack when debugging stack overflows. pub const STACK_DEBUG_INSTR: u8 = 0xCC; diff --git a/beskar-hal/src/x86_64/paging.rs b/beskar-hal/src/x86_64/paging.rs index d5719aa..7b5373d 100644 --- a/beskar-hal/src/x86_64/paging.rs +++ b/beskar-hal/src/x86_64/paging.rs @@ -14,7 +14,7 @@ impl TlbFlush { #[inline] pub fn flush(&self) { unsafe { - core::arch::asm!("invlpg [{}]", in(reg) self.0.start_address().as_u64(), options(nostack, preserves_flags)); + core::arch::asm!("invlpg [{}]", in(reg) self.0.start_address().as_u64(), options(nostack, nomem, preserves_flags)); } } diff --git a/beskar-hal/src/x86_64/structures.rs b/beskar-hal/src/x86_64/structures.rs index 95be39b..6a52417 100644 --- a/beskar-hal/src/x86_64/structures.rs +++ b/beskar-hal/src/x86_64/structures.rs @@ -81,8 +81,8 @@ impl IdtEntry { self.ptr_high = u32::try_from((addr >> 32) & 0xFFFF_FFFF).unwrap(); self.options_cs = cs; - self.options = 0b1110_0000_0000; // 64-bit interrupt gate - self.options |= 1 << 15; // Present bit + // 64-bit present interrupt gate + self.options = 0b1000_1110_0000_0000; } /// Set the stack index for this IDT entry. @@ -412,9 +412,9 @@ impl InterruptDescriptorTable { #[must_use] #[inline] - pub fn irq(&mut self, index: u8) -> &mut IdtEntry { - let offset_idx = index.checked_sub(32).unwrap(); - &mut self.interrupts[usize::from(offset_idx)] + pub fn irq(&mut self, index: u8) -> Option<&mut IdtEntry> { + let offset_idx = index.checked_sub(32)?; + self.interrupts.get_mut(usize::from(offset_idx)) } pub fn load(&'static self) { diff --git a/hyperdrive/src/locks/mcs.rs b/hyperdrive/src/locks/mcs.rs index e52dcfe..9e4e1a6 100644 --- a/hyperdrive/src/locks/mcs.rs +++ b/hyperdrive/src/locks/mcs.rs @@ -195,11 +195,11 @@ impl McsLock { /// This function allows for a more fine-grained control over the duration of the lock. pub fn lock<'s, 'node>(&'s self, node: &'node mut McsNode) -> McsGuard<'node, 's, T, B> { // Assert the node is ready to be used - node.locked.store(true, Ordering::Release); + node.locked.store(true, Ordering::Relaxed); node.set_next(ptr::null_mut()); // Place the node at the end of the queue - let prev = self.tail.swap(node, Ordering::AcqRel); + let prev = self.tail.swap(node, Ordering::Acquire); if let Some(prev_ptr) = NonNull::new(prev) { unsafe { prev_ptr.as_ref() }.set_next(node); @@ -224,16 +224,14 @@ impl McsLock { node: &'node mut McsNode, ) -> Option> { // Assert the node is ready to be used - node.locked.store(true, Ordering::Release); node.set_next(ptr::null_mut()); + // Note: we do not care about `locked` here as this field will never be accessed // Try to place the node at the end of the queue self.tail - .compare_exchange(ptr::null_mut(), node, Ordering::AcqRel, Ordering::Relaxed) + .compare_exchange(ptr::null_mut(), node, Ordering::Acquire, Ordering::Relaxed) .ok()?; - node.locked.store(false, Ordering::Release); - Some(McsGuard { lock: self, node }) } @@ -320,7 +318,7 @@ impl DerefMut for McsGuard<'_, '_, T, B> { impl Drop for McsGuard<'_, '_, T, B> { fn drop(&mut self) { - // Check if the node is at the front of the queue + // Check if the node is the back of the queue if self.node.next().is_none() { if self .lock @@ -411,24 +409,18 @@ impl MUMcsLock { } #[must_use] + #[inline] /// Locks the lock and returns a guard. /// /// ## Panics /// /// Panics if the lock is not initialized. pub fn lock<'s, 'node>(&'s self, node: &'node mut McsNode) -> MUMcsGuard<'node, 's, T, B> { - // Panicking before locking the inner lock has two effects: - // - It doesn't poison the lock - // - If anyone de-initializes the lock exactly after this check, - // it will result in undefined behavior. + // Panicking before locking the inner lock so that it doesn't poison the lock assert!(self.is_initialized(), "MUMcsLock not initialized"); let guard = self.inner_lock.lock(node); - // If de-initialization is implemented, this line should be uncommented. - // It can cause poisoning in the very specific case mentioned above. - // assert!(self.is_initialized(), "MUMcsLock not initialized"); - MUMcsGuard { inner_guard: guard } } @@ -447,16 +439,11 @@ impl MUMcsLock { let guard = self.inner_lock.try_lock(node)?; - // If de-initialization is implemented, this line should be uncommented. - // if !self.is_initialized() { - // drop(guard); - // return None; - // } - Some(MUMcsGuard { inner_guard: guard }) } #[must_use] + #[inline] /// Try to lock the lock if it has been initialized. /// Returns `None` if the lock has not been initialized. /// @@ -465,14 +452,7 @@ impl MUMcsLock { &'s self, node: &'node mut McsNode, ) -> Option> { - if self.is_initialized() { - // If anyone de-initializes the lock exactly between these two lines, - // there could still be a panic. - // If de-initialization is implemented, `compare_exchange` should be used. - Some(self.lock(node)) - } else { - None - } + self.is_initialized().then(|| self.lock(node)) } #[inline] @@ -527,8 +507,7 @@ impl MUMcsLock { #[inline] /// Consume the lock and returns the inner data if it is initialized. pub fn into_inner(mut self) -> Option { - if self.is_initialized() { - self.is_init.store(false, Ordering::Relaxed); + if self.is_init.swap(false, Ordering::Acquire) { // Safety: The lock is initialized. // We cannot use `assume_init` as `inner_lock` is part of `self`. // Therefore, we need to `assume_init_read` the value, and "uninitialize" diff --git a/hyperdrive/src/locks/rw.rs b/hyperdrive/src/locks/rw.rs index 2bd00f2..c8378dd 100644 --- a/hyperdrive/src/locks/rw.rs +++ b/hyperdrive/src/locks/rw.rs @@ -62,7 +62,7 @@ use super::{BackOff, Spin}; use core::{ cell::UnsafeCell, marker::PhantomData, - sync::atomic::{AtomicBool, AtomicUsize, Ordering}, + sync::atomic::{AtomicBool, AtomicU32, Ordering}, }; #[derive(Default)] @@ -161,7 +161,7 @@ impl Drop for WriteGuard<'_, T, B> { /// The state of the lock. struct AtomicState { /// The number of readers. - readers: AtomicUsize, + readers: AtomicU32, /// Whether a writer has acquired the lock. writer: AtomicBool, /// Back-off strategy. @@ -173,7 +173,7 @@ impl AtomicState { #[inline] pub const fn new() -> Self { Self { - readers: AtomicUsize::new(0), + readers: AtomicU32::new(0), writer: AtomicBool::new(false), _back_off: PhantomData, } diff --git a/hyperdrive/src/locks/ticket.rs b/hyperdrive/src/locks/ticket.rs index 9ded547..0a44b4d 100644 --- a/hyperdrive/src/locks/ticket.rs +++ b/hyperdrive/src/locks/ticket.rs @@ -84,7 +84,6 @@ impl TicketLock { } #[must_use] - #[inline] /// Locks the ticket lock and returns a guard. pub fn lock(&self) -> TicketGuard<'_, T, B> { // Get the ticket number for this thread. diff --git a/hyperdrive/src/once.rs b/hyperdrive/src/once.rs index a770ba1..d0dddc8 100644 --- a/hyperdrive/src/once.rs +++ b/hyperdrive/src/once.rs @@ -3,6 +3,10 @@ //! This structure is somewhat similar to `std::sync::Once`, and it does not provide interior mutability. //! It is used to perform a one-time initialization of a value, and then provide a reference to it. //! +//! If initialization fails, the value will be marked as poisoned and a panic will occur. +//! This behavior depends on panic unwinding, so it does not work in `no_std` environments +//! with `panic = "abort"`. +//! //! If you need one-time initialization with interior mutability, consider combining this structure with a lock. //! //! ## Examples @@ -49,9 +53,9 @@ use core::sync::atomic::{AtomicU8, Ordering}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// Possible states of the `Once` structure. enum State { - Uninitialized, + Initialized = 0, Initializing, - Initialized, + Uninitialized, Poisoned, } @@ -61,9 +65,9 @@ impl State { /// Converts the state to a `u8` value. const fn as_u8(self) -> u8 { match self { - Self::Uninitialized => 0, + Self::Initialized => 0, Self::Initializing => 1, - Self::Initialized => 2, + Self::Uninitialized => 2, Self::Poisoned => 3, } } @@ -74,9 +78,9 @@ impl State { /// Returns `None` if the value is not a valid state. const fn from_u8(value: u8) -> Option { match value { - 0 => Some(Self::Uninitialized), + 0 => Some(Self::Initialized), 1 => Some(Self::Initializing), - 2 => Some(Self::Initialized), + 2 => Some(Self::Uninitialized), 3 => Some(Self::Poisoned), _ => None, } @@ -101,7 +105,7 @@ impl AtomicState { #[must_use] #[inline] pub const fn uninit() -> Self { - Self(AtomicU8::new(State::Uninitialized.as_u8())) + Self::new(State::Uninitialized) } #[must_use] @@ -233,14 +237,23 @@ impl Once { } #[must_use] + #[track_caller] /// Returns a reference to the value if it has been initialized. /// /// If the value is still initializing, this function will block until initialization is complete. /// - /// # Warning + /// # Panics /// - /// If initialization fails, the value will be marked as poisoned and `None` will be returned. + /// If initialization fails, the value will be marked as poisoned and a panic will occur. + /// This behavior depends on panic unwinding, so it does not work in `no_std` environments + /// with `panic = "abort"`. pub fn get(&self) -> Option<&T> { + #[cold] + #[track_caller] + fn poisoned() -> ! { + panic!("Once is poisoned, cannot get value"); + } + match self.state.load(Ordering::Acquire) { State::Initialized => { // Safety: @@ -257,10 +270,11 @@ impl Once { Some(unsafe { (*self.value.get()).assume_init_ref() }) } else { debug_assert_eq!(state, State::Poisoned); - None + poisoned(); } } - State::Uninitialized | State::Poisoned => None, + State::Uninitialized => None, + State::Poisoned => poisoned(), } } } @@ -409,10 +423,27 @@ mod test { }; handle_that_panics.join().unwrap_err(); - // Poisoning the `Once` should not leave the value in an `Initializing` state. - // This call should finish immediately (instead of infinite loop). - assert!(once.get().is_none()); assert!(!once.is_initialized()); assert!(once.is_poisoned()); } + + #[test] + #[should_panic(expected = "Once is poisoned, cannot get value")] + fn test_poison_get() { + let once = Arc::new(Once::uninit()); + + let handle_that_panics = { + let once = once.clone(); + spawn(move || { + once.call_once(|| { + panic!("Initialization failed!"); + }); + }) + }; + handle_that_panics.join().unwrap_err(); + + // Poisoning the `Once` should not leave the value in an `Initializing` state. + // This call should finish (panic) immediately (instead of infinite loop). + let _ = once.get(); + } } diff --git a/hyperdrive/src/queues/mpmc.rs b/hyperdrive/src/queues/mpmc.rs index 78c931a..17e9dfd 100644 --- a/hyperdrive/src/queues/mpmc.rs +++ b/hyperdrive/src/queues/mpmc.rs @@ -302,8 +302,11 @@ mod tests { mpmc.push(Box::new(1)); mpmc.push(Box::new(2)); - assert_eq!(mpmc.pop(), Some(Box::new(1))); - assert_eq!(mpmc.pop(), Some(Box::new(2))); + let popped1 = mpmc.pop().unwrap(); + let popped2 = mpmc.pop().unwrap(); + + assert_eq!(*popped1, 1); + assert_eq!(*popped2, 2); } #[test] diff --git a/hyperdrive/src/queues/mpsc.rs b/hyperdrive/src/queues/mpsc.rs index 7eccd5a..e02953e 100644 --- a/hyperdrive/src/queues/mpsc.rs +++ b/hyperdrive/src/queues/mpsc.rs @@ -190,6 +190,7 @@ pub enum DequeueResult { impl DequeueResult { #[must_use] + #[track_caller] #[inline] /// Unwraps the result. /// diff --git a/kernel/foundry/storage/src/fs.rs b/kernel/foundry/storage/src/fs.rs index 20a635f..0df1769 100644 --- a/kernel/foundry/storage/src/fs.rs +++ b/kernel/foundry/storage/src/fs.rs @@ -4,6 +4,7 @@ use thiserror::Error; pub mod dev; pub mod ext2; pub mod fat; +pub mod in_mem; #[derive(Debug, Error, Clone, Copy, Eq, PartialEq)] pub enum FileError { @@ -114,6 +115,15 @@ impl core::borrow::Borrow for PathBuf { } } +impl<'a> Path<'a> { + #[must_use] + #[inline] + /// Creates a new `Path` from the given string slice. + pub const fn new(path: &'a str) -> Self { + Self(path) + } +} + impl Path<'_> { #[must_use] #[inline] diff --git a/kernel/foundry/storage/src/fs/in_mem.rs b/kernel/foundry/storage/src/fs/in_mem.rs new file mode 100644 index 0000000..29dcbbe --- /dev/null +++ b/kernel/foundry/storage/src/fs/in_mem.rs @@ -0,0 +1,225 @@ +//! A custom, realy simple read-only file system suitable for e.g. ramdisks. + +use super::FileSystem; +use alloc::vec::Vec; + +#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[repr(C, packed)] +pub struct RawHeader { + /// Should be a 32 byte long ASCII name. + name: [u8; 32], + size: usize, +} + +impl RawHeader { + #[must_use] + #[inline] + /// Creates a new `RawHeader` with the given size and name. + pub const fn new(size: usize, name: [u8; 32]) -> Self { + Self { name, size } + } + + #[must_use] + #[inline] + /// Returns the size of the file. + pub const fn size(&self) -> usize { + self.size + } + + #[must_use] + #[inline] + /// Returns the name of the file. + pub const fn name(&self) -> &[u8; 32] { + &self.name + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct FileInfo { + /// Should be a 32 byte long ASCII name. + name: [u8; 32], + size: usize, + offset: usize, +} + +impl FileInfo { + #[must_use] + #[inline] + /// Creates a new `RawHeader` with the given size and name. + pub const fn new(raw_header: &RawHeader, offset: usize) -> Self { + Self { + name: raw_header.name, + size: raw_header.size, + offset, + } + } + + #[must_use] + #[inline] + /// Returns the size of the file. + pub const fn size(&self) -> usize { + self.size + } + + #[must_use] + #[inline] + /// Returns the name of the file as a string slice. + pub fn name(&self) -> &str { + core::str::from_utf8(&self.name) + .unwrap() + .trim_end_matches('\0') + } +} + +#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +pub enum InMemoryFSError { + #[error("Buffer is too small")] + BufferTooSmall, + #[error("Invalid header size")] + InvalidHeaderSize, + #[error("Invalid header name")] + InvalidHeaderName, +} + +#[derive(Default)] +/// A pass-through file system for device files. +pub struct InMemoryFS<'a> { + raw: &'a [u8], + infos: Vec, +} + +impl<'a> InMemoryFS<'a> { + #[inline] + /// Creates a new `InMemoryFS` instance with the given data. + pub fn new(data: &'a [u8]) -> Result { + let mut infos = Vec::new(); + + if !data.is_empty() { + let mut cursor = 0; + while cursor < data.len() { + if data.len() < cursor.saturating_add(size_of::()) { + return Err(InMemoryFSError::BufferTooSmall); + } + + let raw_header = unsafe { + // SAFETY: We made sure that the buffer is large enough. + data[cursor..] + .as_ptr() + .try_cast_aligned::() + .unwrap() + .as_ref() + .unwrap() + }; + + if raw_header.size().saturating_add(cursor) > data.len() { + return Err(InMemoryFSError::InvalidHeaderSize); + } + if core::str::from_utf8(raw_header.name()).is_err() { + return Err(InMemoryFSError::InvalidHeaderName); + } + + infos.push(FileInfo::new(raw_header, cursor)); + + cursor += size_of::() + raw_header.size(); + } + } + + Ok(Self { raw: data, infos }) + } +} + +impl FileSystem for InMemoryFS<'_> { + fn close(&mut self, _path: super::Path) -> super::FileResult<()> { + Ok(()) + } + + #[inline] + fn create(&mut self, _path: super::Path) -> super::FileResult<()> { + // InMemoryFS does not support creating files + Err(super::FileError::UnsupportedOperation) + } + + #[inline] + fn delete(&mut self, _path: super::Path) -> super::FileResult<()> { + // InMemoryFS does not support deleting files + Err(super::FileError::UnsupportedOperation) + } + + fn exists(&mut self, path: super::Path) -> super::FileResult { + Ok(self.infos.iter().any(|file| file.name() == path.as_str())) + } + + fn open(&mut self, _path: super::Path) -> super::FileResult<()> { + Ok(()) + } + + fn read( + &mut self, + path: super::Path, + buffer: &mut [u8], + offset: usize, + ) -> super::FileResult { + // Find the device associated with the given path. + let Some(file) = self.infos.iter().find(|file| file.name() == path.as_str()) else { + return Err(super::FileError::NotFound); + }; + + let read_bytes = file.size().saturating_sub(offset).min(buffer.len()); + + let src = { + let start_offset = file.offset + offset; + &self.raw[start_offset..start_offset + read_bytes] + }; + let dst = &mut buffer[..read_bytes]; + + dst.copy_from_slice(src); + + Ok(read_bytes) + } + + fn write( + &mut self, + _path: super::Path, + _buffer: &[u8], + _offset: usize, + ) -> super::FileResult { + // InMemoryFS does not support writing to files + Err(super::FileError::UnsupportedOperation) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fs::Path; + + #[test] + fn test_in_memory_fs() { + let data = [ + RawHeader::new( + 0, + *b"file1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ), + RawHeader::new( + 0, + *b"file2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ), + ]; + let mut fs = InMemoryFS::new(unsafe { + core::slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * size_of::(), + ) + }) + .unwrap(); + + assert!(fs.exists(Path("file1")).unwrap()); + assert!(fs.exists(Path("file2")).unwrap()); + assert!(!fs.exists(Path("file3")).unwrap()); + + let mut buffer = [0; 5]; + + let bytes_read = fs.read(Path("file1"), &mut buffer, 0).unwrap(); + assert_eq!(bytes_read, 0); + } +} diff --git a/kernel/foundry/storage/src/lib.rs b/kernel/foundry/storage/src/lib.rs index 9624213..f1c2b16 100644 --- a/kernel/foundry/storage/src/lib.rs +++ b/kernel/foundry/storage/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_op_in_unsafe_fn)] #![warn(clippy::pedantic, clippy::nursery)] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] +#![feature(pointer_try_cast_aligned)] extern crate alloc; pub use beskar_core::storage::{BlockDevice, BlockDeviceError, KernelDevice}; diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index 0183de5..421267f 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -1,11 +1,15 @@ use super::gdt::{DOUBLE_FAULT_IST, PAGE_FAULT_IST}; use crate::locals; +use beskar_core::arch::VirtAddr; use beskar_hal::{ - instructions::{int_disable, int_enable}, + instructions::int_enable, registers::{CS, Cr0, Cr2}, structures::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, }; -use core::{cell::UnsafeCell, sync::atomic::AtomicU8}; +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicU8, Ordering}, +}; pub fn init() { let interrupts = locals!().interrupts(); @@ -60,7 +64,9 @@ pub fn init() { idt.page_fault.set_stack_index(PAGE_FAULT_IST); } - idt.irq(0xFF).set_handler_fn(spurious_interrupt_handler, cs); + idt.irq(0xFF) + .unwrap() + .set_handler_fn(spurious_interrupt_handler, cs); idt.load(); @@ -197,23 +203,6 @@ extern "x86-interrupt" fn machine_check_handler(_stack_frame: InterruptStackFram info_isr!(spurious_interrupt_handler); -#[inline] -pub fn without_interrupts(f: F) -> R -where - F: FnOnce() -> R, -{ - let rflags = beskar_hal::registers::Rflags::read(); - if rflags & beskar_hal::registers::Rflags::IF == 0 { - // Interrupts are already disabled, just call the function - f() - } else { - int_disable(); - let result = f(); - int_enable(); - result - } -} - #[inline] /// Allocates a new IRQ handler in the IDT and return its index. /// @@ -230,17 +219,17 @@ pub fn new_irq( let core_id = core.unwrap_or_else(|| locals!().core_id()); let core_locals = crate::locals::get_specific_core_locals(core_id).unwrap(); - let idx = IDX.fetch_add(1, core::sync::atomic::Ordering::Relaxed); - assert!(idx < 255, "No more IRQs available"); + let idx = IDX.fetch_add(1, Ordering::Relaxed); let idt = unsafe { &mut *core_locals.interrupts().idt.get() }; + let idt_entry = idt.irq(idx).expect("IRQ counter has overflown"); assert_eq!( - idt.irq(idx).handler_vaddr().as_u64(), - 0, + idt_entry.handler_vaddr(), + VirtAddr::ZERO, "IRQ {idx} is already used", ); - idt.irq(idx).set_handler_fn(handler, CS::read()); + idt_entry.set_handler_fn(handler, CS::read()); (idx, core_id) } diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index cb8c60a..3ba2645 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -28,13 +28,9 @@ fn panic(panic_info: &core::panic::PanicInfo) -> ! { beskar_hal::instructions::int_disable(); #[cfg(debug_assertions)] - video::error!("[PANIC]: Core {} - {}", locals!().core_id(), panic_info); + video::error!("[PANIC] {panic_info}"); #[cfg(not(debug_assertions))] - video::error!( - "[PANIC]: Core {} - {}", - locals!().core_id(), - panic_info.message() - ); + video::error!("[PANIC] {}", panic_info.message()); // If more than one core is present, then both processes and APICs are initialized. if crate::locals::core_count() > 1 { diff --git a/kernel/src/mem/heap.rs b/kernel/src/mem/heap.rs index 447420e..6629a14 100644 --- a/kernel/src/mem/heap.rs +++ b/kernel/src/mem/heap.rs @@ -69,10 +69,9 @@ impl Heap { } pub unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) { - let Some(ptr) = NonNull::new(ptr) else { - return; - }; - unsafe { self.linked_list.deallocate(ptr, layout) }; + if let Some(ptr) = NonNull::new(ptr) { + unsafe { self.linked_list.deallocate(ptr, layout) } + } } } diff --git a/kernel/src/process.rs b/kernel/src/process.rs index e8a53ee..ed0aa34 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -1,3 +1,4 @@ +use crate::mem::address_space::{self, AddressSpace}; use alloc::{ string::{String, ToString}, sync::Arc, @@ -7,8 +8,6 @@ use binary::LoadedBinary; use core::sync::atomic::{AtomicU16, AtomicU64, Ordering}; use hyperdrive::{once::Once, ptrs::view::ViewRef}; -use crate::mem::address_space::{self, AddressSpace}; - pub mod binary; pub mod scheduler; @@ -193,8 +192,6 @@ pub fn current() -> Arc { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Pcid(u16); -static PCID_COUNTER: AtomicU16 = AtomicU16::new(0); - impl Default for Pcid { fn default() -> Self { Self::new() @@ -205,6 +202,8 @@ impl Pcid { #[must_use] #[inline] pub fn new() -> Self { + static PCID_COUNTER: AtomicU16 = AtomicU16::new(0); + let raw: u16 = PCID_COUNTER.fetch_add(1, Ordering::Relaxed); if raw > 4095 { diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index cb243c5..07e1353 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -1,9 +1,10 @@ -use crate::{arch::interrupts::without_interrupts, locals}; +use crate::locals; use alloc::{boxed::Box, collections::btree_map::BTreeMap, sync::Arc}; use beskar_core::arch::VirtAddr; +use beskar_hal::instructions::without_interrupts; use core::{ pin::Pin, - sync::atomic::{AtomicBool, Ordering}, + sync::atomic::{AtomicBool, AtomicPtr, Ordering}, }; use hyperdrive::{locks::mcs::McsLock, once::Once, queues::mpsc::MpscQueue}; use priority::ThreadQueue; @@ -76,45 +77,41 @@ impl ContextSwitch { } pub struct Scheduler { - current_thread: McsLock>, + current: McsLock>, /// A local, atomic, priority for the current thread. - current_priority: priority::AtomicPriority, - should_exit_thread: AtomicBool, - should_sleep_thread: AtomicBool, + should_exit: AtomicBool, + should_sleep: AtomicBool, } impl Scheduler { #[must_use] + #[inline] fn new(kernel_thread: thread::Thread) -> Self { - let current_priority = priority::AtomicPriority::new(kernel_thread.priority()); - Self { - current_thread: McsLock::new(Box::new(kernel_thread)), - current_priority, - should_exit_thread: AtomicBool::new(false), - should_sleep_thread: AtomicBool::new(false), + current: McsLock::new(Box::new(kernel_thread)), + should_exit: AtomicBool::new(false), + should_sleep: AtomicBool::new(false), } } #[inline] - pub fn exit_current_thread(&self) { - self.should_exit_thread.store(true, Ordering::Relaxed); - } - - #[inline] - pub fn sleep_current_thread(&self) { - self.should_sleep_thread.store(true, Ordering::Relaxed); - } - - #[must_use] - #[inline] - pub fn current_priority(&self) -> priority::Priority { - self.current_priority.load(Ordering::Relaxed) + /// Sets an inner flag to indicate that the current thread should exit. + /// + /// This function does not perform the context switch, but it will + /// ensure that the next time the scheduler is called, the current thread + /// will be exited. + fn set_exit(&self) { + self.should_exit.store(true, Ordering::Relaxed); } #[inline] - pub fn change_current_thread_priority(&self, new_priority: priority::Priority) { - self.current_priority.store(new_priority, Ordering::Relaxed); + /// Sets an inner flag to indicate that the current thread should sleep. + /// + /// This function does not perform the context switch, but it will + /// ensure that the next time the scheduler is called, the current thread + /// will be put to sleep. + fn set_sleep(&self) { + self.should_sleep.store(true, Ordering::Relaxed); } #[must_use] @@ -123,84 +120,72 @@ impl Scheduler { /// This function does not change the context, but will disable interrupts /// if scheduling was successful. fn reschedule(&self) -> Option { - self.current_thread.try_with_locked(|thread| { - beskar_hal::instructions::int_disable(); + self.current + .try_with_locked(|thread| { + // Swap the current thread with the next one. + let mut new_thread = Pin::into_inner(QUEUE.get().unwrap().next()?); + + core::mem::swap(thread.as_mut(), new_thread.as_mut()); + let mut old_thread = new_thread; // Renaming for clarity. + + // Gather information about the old thread. + let old_should_exit = self.should_exit.swap(false, Ordering::Relaxed); + let old_should_wait = self.should_sleep.swap(false, Ordering::Relaxed); + + debug_assert_eq!(thread.state(), thread::ThreadState::Ready); + unsafe { thread.set_state(thread::ThreadState::Running) }; + + // Handle stack pointers. + let old_stack = if old_should_exit { + // In the case of the thread exiting, we cannot write to the `Thread` struct anymore. + // Therefore, we write to a useless static variable because we won't need RSP value. + static USELESS: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + // Note: there may be data races here, but we do not care. + USELESS.as_ptr() + } else { + old_thread.last_stack_ptr_mut() + }; + let new_stack = thread.last_stack_ptr(); + + let cr3 = thread.process().address_space().cr3_raw(); + if let Some(tls) = thread.tls() { + crate::arch::locals::store_thread_locals(tls); + } + + if let Some(rsp0) = thread.snapshot().kernel_stack_top() { + let tss = unsafe { locals!().gdt().force_lock() }.tss_mut().unwrap(); + tss.privilege_stack_table[0] = VirtAddr::from_ptr(rsp0.as_ptr()); + } - // Swap the current thread with the next one. - let mut new_thread = - Pin::into_inner(if let Some(new_thread) = QUEUE.get().unwrap().next() { - new_thread + if old_should_exit { + // As the scheduler must not acquire locks, it cannot drop heap-allocated memory. + // This job should be done by a cleaning thread. + FINISHED.get().unwrap().enqueue(Pin::new(old_thread)); + } else if old_should_wait { + unsafe { old_thread.set_state(thread::ThreadState::Sleeping) }; + SLEEPING.with_locked(|wq| wq.insert(old_thread.id(), old_thread)); } else { - beskar_hal::instructions::int_enable(); - return None; - }); - - core::mem::swap(thread.as_mut(), new_thread.as_mut()); - let mut old_thread = new_thread; // Renaming for clarity. - - // Gather information about the old thread. - let old_priority = self - .current_priority - .swap(thread.priority(), Ordering::Relaxed); - unsafe { old_thread.set_priority(old_priority) }; - let old_should_exit = self.should_exit_thread.swap(false, Ordering::Relaxed); - let old_should_wait = self.should_sleep_thread.swap(false, Ordering::Relaxed); - - debug_assert_eq!(thread.state(), thread::ThreadState::Ready); - unsafe { thread.set_state(thread::ThreadState::Running) }; - - // Handle stack pointers. - let old_stack = if old_should_exit { - // In the case of the thread exiting, we cannot write to the `Thread` struct anymore. - // Therefore, we write to a useless static variable because we won't need RSP value. - static mut USELESS: *mut u8 = core::ptr::null_mut(); - #[expect(static_mut_refs, reason = "We do not care about data races here.")] - // Safety: There will be data races, but we don't care lol - unsafe { - &mut USELESS + unsafe { old_thread.set_state(thread::ThreadState::Ready) }; + QUEUE.get().unwrap().append(Pin::new(old_thread)); } - } else { - old_thread.last_stack_ptr_mut() - }; - let new_stack = thread.last_stack_ptr(); - - let cr3 = thread.process().address_space().cr3_raw(); - if let Some(tls) = thread.tls() { - crate::arch::locals::store_thread_locals(tls); - } - - if let Some(rsp0) = thread.snapshot().kernel_stack_top() { - let tss = unsafe { locals!().gdt().force_lock() }.tss_mut().unwrap(); - tss.privilege_stack_table[0] = VirtAddr::from_ptr(rsp0.as_ptr()); - } - - if old_should_exit { - // As the scheduler must not acquire locks, it cannot drop heap-allocated memory. - // This job should be done by a cleaning thread. - FINISHED.get().unwrap().enqueue(Pin::new(old_thread)); - } else if old_should_wait { - unsafe { old_thread.set_state(thread::ThreadState::Sleeping) }; - SLEEPING.with_locked(|wq| wq.insert(old_thread.id(), old_thread)); - } else { - unsafe { old_thread.set_state(thread::ThreadState::Ready) }; - QUEUE.get().unwrap().append(Pin::new(old_thread)); - } - - Some(ContextSwitch { - old_stack, - new_stack, - cr3, - }) - })? - } - pub fn schedule(mut thread: Box) { - unsafe { thread.set_state(thread::ThreadState::Ready) }; - QUEUE.get().unwrap().append(Pin::new(thread)); + beskar_hal::instructions::int_disable(); + + Some(ContextSwitch { + old_stack, + new_stack, + cr3, + }) + }) + .flatten() } } #[inline] +/// Executes a closure with the scheduler. +/// +/// Note that this function does not involve any locking, +/// it simply makes sure that interrupts are disabled. fn with_scheduler R>(f: F) -> R { without_interrupts(|| { let scheduler = locals!().scheduler().get().unwrap(); @@ -244,7 +229,7 @@ pub fn current_thread_id() -> ThreadId { with_scheduler(|scheduler| { // Safety: // Interrupts are disabled, so the current thread cannot change. - unsafe { scheduler.current_thread.force_lock() }.id() + unsafe { scheduler.current.force_lock() }.id() }) } @@ -255,7 +240,7 @@ pub(crate) fn current_thread_snapshot() -> thread::ThreadSnapshot { with_scheduler(|scheduler| { // Safety: // Interrupts are disabled, so the current thread cannot change. - unsafe { scheduler.current_thread.force_lock() }.snapshot() + unsafe { scheduler.current.force_lock() }.snapshot() }) } @@ -266,13 +251,14 @@ pub fn current_process() -> Arc { with_scheduler(|scheduler| { // Safety: // Interrupts are disabled, so the current thread cannot change. - unsafe { scheduler.current_thread.force_lock() }.process() + unsafe { scheduler.current.force_lock() }.process() }) } #[inline] -pub fn spawn_thread(thread: Box) { - Scheduler::schedule(thread); +pub fn spawn_thread(mut thread: Box) { + unsafe { thread.set_state(thread::ThreadState::Ready) }; + QUEUE.get().unwrap().append(Pin::new(thread)); } /// Sets the scheduling of the scheduler. @@ -297,13 +283,6 @@ pub fn set_scheduling(enable: bool) { }); } -#[inline] -pub fn change_current_thread_priority(priority: priority::Priority) { - with_scheduler(|scheduler| { - scheduler.change_current_thread_priority(priority); - }); -} - /// Exits the current thread. /// /// This function will enable interrupts, otherwise the system would halt. @@ -313,7 +292,7 @@ pub fn change_current_thread_priority(priority: priority::Priority) { /// The context will be brutally switched without returning. /// If any locks are acquired, they will be poisoned. pub unsafe fn exit_current_thread() -> ! { - with_scheduler(Scheduler::exit_current_thread); + with_scheduler(Scheduler::set_exit); // Try to reschedule the thread. thread_yield(); @@ -357,7 +336,7 @@ impl hyperdrive::locks::BackOff for Yield { /// Put the current thread to sleep. pub fn sleep() { with_scheduler(|scheduler| { - scheduler.sleep_current_thread(); + scheduler.set_sleep(); }); if !thread_yield() { @@ -370,11 +349,12 @@ pub fn sleep() { /// Wakes up a thread that is sleeping. /// -/// Returns `true` if the thread was woken up, `false` otherwise. +/// Returns `true` if the thread was woken up, +/// `false` if the thread was not sleeping. pub fn wake_up(thread: ThreadId) -> bool { SLEEPING.with_locked(|wq| { wq.remove(&thread).is_some_and(|thread| { - Scheduler::schedule(thread); + spawn_thread(thread); true }) }) diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index 474428f..1dfd90f 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -52,7 +52,7 @@ impl Eq for Thread {} impl PartialOrd for Thread { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.id.cmp(&other.id)) + Some(self.cmp(other)) } } impl Ord for Thread { @@ -191,16 +191,6 @@ impl Thread { } } - /// Changes the priority of the thread. - /// - /// ## Safety - /// - /// This function should only be called on a currently active thread, - /// as queues in the scheduler are sorted by priority. - pub(super) const unsafe fn set_priority(&mut self, priority: Priority) { - self.priority = priority; - } - #[inline] /// Changes the state of the thread. /// @@ -343,8 +333,8 @@ extern "C" fn user_trampoline() -> ! { // Allocate a user stack let rsp = super::with_scheduler(|scheduler| { - scheduler.current_thread.with_locked(|t| { - t.stack.as_mut().map(|ts| { + scheduler.current.with_locked(|thread| { + thread.stack.as_mut().map(|ts| { ts.allocate_all(4 * M4KiB::SIZE); ts.user_stack_top().unwrap() }) @@ -396,8 +386,8 @@ extern "C" fn user_trampoline() -> ! { // Locking the scheduler's current thread is a bit ugly, but it is better than force locking it // (as otherwise the scheduler could get stuck on `Once::get`). super::with_scheduler(|scheduler| { - scheduler.current_thread.with_locked(|t| { - t.tls.call_once(|| tls); + scheduler.current.with_locked(|thread| { + thread.tls.call_once(|| tls); }); }); crate::arch::locals::store_thread_locals(tls); diff --git a/userspace/bashkar/src/commands.rs b/userspace/bashkar/src/commands.rs index 8cc9cd1..854418f 100644 --- a/userspace/bashkar/src/commands.rs +++ b/userspace/bashkar/src/commands.rs @@ -32,13 +32,11 @@ pub fn execute_command(command: &str, args: &[&str], tty: &mut Tty) -> CommandRe /// Parse a command line into a command and arguments pub fn parse_command_line(line: &str) -> (String, Vec) { - let mut parts: Vec = line.split_whitespace().map(ToString::to_string).collect(); + let mut args = line.split_whitespace().map(ToString::to_string); - if parts.is_empty() { - return (String::new(), Vec::new()); - } + let command = args.next().unwrap_or_default(); + let parts: Vec<_> = args.collect(); - let command = parts.remove(0); (command, parts) } diff --git a/userspace/bashkar/src/main.rs b/userspace/bashkar/src/main.rs index 8cb97c4..2ef82e2 100644 --- a/userspace/bashkar/src/main.rs +++ b/userspace/bashkar/src/main.rs @@ -18,6 +18,8 @@ fn main() { let input = tty.get_input_line(); let (command, args) = bashkar::commands::parse_command_line(input); + // FIXME: Cloning the args into Strings is necessary to avoid borrowing issues below, + // but it would be better to avoid this. let args_ref: Vec<&str> = args.iter().map(String::as_str).collect(); let _exec_res = bashkar::commands::execute_command(&command, &args_ref, tty); From 044114bfbeb73f56b084e1a0282c84a25cbef3c5 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Mon, 14 Jul 2025 14:41:34 +0200 Subject: [PATCH 08/11] fix: too loose atomic ordering in McsLock::lock --- hyperdrive/src/locks/mcs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hyperdrive/src/locks/mcs.rs b/hyperdrive/src/locks/mcs.rs index 9e4e1a6..56d1508 100644 --- a/hyperdrive/src/locks/mcs.rs +++ b/hyperdrive/src/locks/mcs.rs @@ -199,7 +199,7 @@ impl McsLock { node.set_next(ptr::null_mut()); // Place the node at the end of the queue - let prev = self.tail.swap(node, Ordering::Acquire); + let prev = self.tail.swap(node, Ordering::AcqRel); if let Some(prev_ptr) = NonNull::new(prev) { unsafe { prev_ptr.as_ref() }.set_next(node); @@ -452,7 +452,7 @@ impl MUMcsLock { &'s self, node: &'node mut McsNode, ) -> Option> { - self.is_initialized().then(|| self.lock(node)) + self.is_initialized().then(move || self.lock(node)) } #[inline] From 35a1b925e6c01f300ba52afdfcf9cc14d1718d98 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sun, 3 Aug 2025 22:55:30 +0200 Subject: [PATCH 09/11] fixes and better build script --- .vscode/settings.json | 11 +++-- beskar-core/Cargo.toml | 2 +- beskar-core/src/drivers/keyboard.rs | 7 +-- beskar-core/src/syscall.rs | 15 +++++-- beskar-lib/src/io.rs | 3 +- beskar-lib/src/io/keyboard.rs | 4 +- beskar-lib/src/lib.rs | 7 +-- beskar-lib/src/rand.rs | 4 +- bootloader/bootloader-api/src/lib.rs | 4 +- build.rs | 65 +++++++++++++++++++--------- hyperdrive/src/once.rs | 6 +-- hyperdrive/src/queues/mpmc.rs | 14 +++--- hyperdrive/src/queues/mpsc.rs | 3 ++ kernel/foundry/holonet/src/l3/ip.rs | 2 +- kernel/foundry/holonet/src/l4/tcp.rs | 2 +- kernel/foundry/holonet/src/l4/udp.rs | 2 +- kernel/foundry/storage/src/vfs.rs | 4 +- kernel/src/arch/x86_64/cpuid.rs | 2 + kernel/src/arch/x86_64/syscall.rs | 23 +++------- kernel/src/process.rs | 4 +- kernel/src/time.rs | 4 +- 21 files changed, 104 insertions(+), 84 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 62fea5e..2b99eff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,15 @@ { - // These settings are mandatory when working in `no_std` environments. - // Otherwise, rust-analyzer will panic trying to find the standard library + // Prevent rust-analyzer from searching for `stdlib` in `no_std` environments "rust-analyzer.check.allTargets": false, "rust-analyzer.check.targets": [ "x86_64-unknown-none", "x86_64-unknown-uefi" ], - "rust-analyzer.check.features": [], + // Do not trigger bindeps + "rust-analyzer.check.extraArgs": ["--exclude", "beskar-os"], + // Do not run build scripts + "rust-analyzer.cargo.buildScripts.enable": false, + + // Optimization for large `no_std` projects + "rust-analyzer.lens.enable": false, } diff --git a/beskar-core/Cargo.toml b/beskar-core/Cargo.toml index 4558434..eb98ae9 100644 --- a/beskar-core/Cargo.toml +++ b/beskar-core/Cargo.toml @@ -5,5 +5,5 @@ edition = "2024" [dependencies] noto-sans-mono-bitmap = { version = "0.3.1", default-features = false, features = ["font_weights_default", "size_20", "unicode_ranges_all"] } -num_enum = { version = "0.7.3", default-features = false } +num_enum = { version = "0.7.4", default-features = false } thiserror = { workspace = true } diff --git a/beskar-core/src/drivers/keyboard.rs b/beskar-core/src/drivers/keyboard.rs index dac338e..08ea451 100644 --- a/beskar-core/src/drivers/keyboard.rs +++ b/beskar-core/src/drivers/keyboard.rs @@ -1,4 +1,4 @@ -use num_enum::{IntoPrimitive, TryFromPrimitive}; +use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; #[derive(Debug, Clone, Copy)] pub struct KeyEvent { @@ -47,14 +47,14 @@ impl KeyEvent { None } else { debug_assert!(value >> 16 == 0); - let key = KeyCode::try_from(u8::try_from(value & 0xFF).unwrap()).unwrap(); + let key = KeyCode::from(u8::try_from(value & 0xFF).unwrap()); let pressed = KeyState::try_from(u8::try_from((value >> 8) & 0xFF).unwrap()).unwrap(); Some(Self { key, pressed }) } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum KeyCode { A, @@ -169,6 +169,7 @@ pub enum KeyCode { WindowsLeft, WindowsRight, + #[num_enum(default)] Unknown, } diff --git a/beskar-core/src/syscall.rs b/beskar-core/src/syscall.rs index a27ba50..7889323 100644 --- a/beskar-core/src/syscall.rs +++ b/beskar-core/src/syscall.rs @@ -1,6 +1,6 @@ -use num_enum::{IntoPrimitive, TryFromPrimitive}; +use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, IntoPrimitive)] #[repr(u64)] pub enum Syscall { /// Exit syscall. @@ -52,10 +52,11 @@ pub enum Syscall { /// Invalid syscall. /// /// Any syscall that is not recognized. + #[num_enum(default)] Invalid = u64::MAX, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, IntoPrimitive)] #[repr(u64)] pub enum SyscallExitCode { /// The syscall succeeded @@ -64,6 +65,7 @@ pub enum SyscallExitCode { Failure = 1, /// Any other (invalid) exit code. + #[num_enum(default)] Other, } @@ -76,7 +78,12 @@ impl SyscallExitCode { /// /// Panics if the syscall exit code is not a success. pub fn unwrap(self) { - assert_ne!(self, Self::Failure, "Syscall failed!"); + assert_eq!( + self, + Self::Success, + "Called unwrap on a syscall exit code that was not successful: {:?}", + self + ); } } diff --git a/beskar-lib/src/io.rs b/beskar-lib/src/io.rs index 67dcb6e..db803a9 100644 --- a/beskar-lib/src/io.rs +++ b/beskar-lib/src/io.rs @@ -69,8 +69,7 @@ pub fn open(path: &str) -> Result { /// Returns a `FileError` if the syscall fails. pub fn close(handle: Handle) -> Result<(), FileError> { let res = syscalls::syscall_1(Syscall::Close, handle.cast_unsigned()); - if SyscallExitCode::try_from(res).unwrap_or(SyscallExitCode::Other) == SyscallExitCode::Success - { + if SyscallExitCode::from(res) == SyscallExitCode::Success { Ok(()) } else { Err(FileError { code: -1 }) diff --git a/beskar-lib/src/io/keyboard.rs b/beskar-lib/src/io/keyboard.rs index c78549c..d815a87 100644 --- a/beskar-lib/src/io/keyboard.rs +++ b/beskar-lib/src/io/keyboard.rs @@ -16,9 +16,7 @@ pub fn poll_keyboard() -> Option { // FIXME: This is very inefficient and faillible if some other process // is using the keyboard file. - let Ok(file) = File::open(KEYBOARD_FILE) else { - return None; - }; + let file = File::open(KEYBOARD_FILE).ok()?; let mut buffer = KeyboardEventBuffer([0_u8; size_of::()]); let bytes_read = file.read(&mut buffer.0, 0).unwrap_or(0); diff --git a/beskar-lib/src/lib.rs b/beskar-lib/src/lib.rs index 785cb9f..f94a12f 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -35,10 +35,7 @@ pub fn exit(code: ExitCode) -> ! { /// Panics if the syscall fails (should never happen). pub fn sleep(duration: Duration) { let res = arch::syscalls::syscall_1(Syscall::Sleep, duration.total_millis()); - assert_eq!( - SyscallExitCode::try_from(res).unwrap_or(SyscallExitCode::Failure), - SyscallExitCode::Success - ); + SyscallExitCode::from(res).unwrap(); } #[macro_export] @@ -52,7 +49,7 @@ macro_rules! entry_point { /// /// Do not call this function. unsafe extern "C" fn __program_entry() { - unsafe { $crate::__init() }; + $crate::__init(); ($path)(); $crate::exit($crate::ExitCode::Success); } diff --git a/beskar-lib/src/rand.rs b/beskar-lib/src/rand.rs index d40d5cd..511204e 100644 --- a/beskar-lib/src/rand.rs +++ b/beskar-lib/src/rand.rs @@ -9,11 +9,11 @@ use core::mem::MaybeUninit; /// Panics if the syscall fails. /// This will happen if the input data is invalid or if randomness fails to be generated. pub fn rand_fill(buf: &mut [u8]) { - const KEYBOARD_FILE: &str = "/dev/rand"; + const RAND_FILE: &str = "/dev/rand"; // FIXME: This is very inefficient and faillible if some other process // is using the rand file. - let file = File::open(KEYBOARD_FILE).unwrap(); + let file = File::open(RAND_FILE).unwrap(); let read_res = file.read(buf, 0); diff --git a/bootloader/bootloader-api/src/lib.rs b/bootloader/bootloader-api/src/lib.rs index 57ddec2..e5d4eac 100644 --- a/bootloader/bootloader-api/src/lib.rs +++ b/bootloader/bootloader-api/src/lib.rs @@ -17,8 +17,8 @@ use beskar_core::{ macro_rules! entry_point { ($path:path $(, $arg:expr)*) => { #[unsafe(export_name = "_start")] - pub extern "C" fn __kernel_entry(boot_info: &'static mut $crate::BootInfo) -> ! { - unsafe { ($path)(boot_info $(, $arg)*) } + extern "C" fn __kernel_entry(boot_info: &'static mut $crate::BootInfo) -> ! { + ($path)(boot_info $(, $arg)*) } }; } diff --git a/build.rs b/build.rs index e794630..de7ab46 100644 --- a/build.rs +++ b/build.rs @@ -1,30 +1,53 @@ +//! When it comes to building multiple binaries in a single workspace, +//! we are given two choices: +//! +//! - Use the main binary (i.e. `src`) as a CLI application to interact with the +//! workspace. This as the disadvantage of being longer to type as well as +//! longer to build as it is harder to benefit from parallel compilation. +//! - Use a build script to build the binaries, using the `bindeps` unstable +//! cargo feature. This feature received a lot of hype so it is likely to +//! become stable in the future. This has the disadvantage of being less +//! flexible, as a simple `cargo check` will run the build script. +//! +//! The second approach is the one we are taking here as it is extremely convenient +//! to iterate on the workspaece using only `cargo b`. #![forbid(unsafe_code)] -use std::{env::var, fs, str::FromStr}; +use std::{env::var, fs}; +/// List of package names for userspace applications. const USERSPACE_APPS: [&str; 1] = ["bashkar"]; +/// A macro to print cargo instructions. +macro_rules! cargo { + ($param:expr, $value:expr) => { + println!("cargo:{param}={value}", param = $param, value = $value); + }; +} + +/// Converts a crate name to the corresponding CARGO_BIN_FILE environment variable name. fn crate_name_to_cargo_venv(crate_name: &str) -> String { - let mut cargo_venv = String::from_str("CARGO_BIN_FILE_").unwrap(); - for c in crate_name.chars() { - if c.is_ascii_alphanumeric() { - cargo_venv.push(c.to_ascii_uppercase()); - } else if c == '-' || c == '_' { - cargo_venv.push('_'); - } else { - panic!("Invalid character in crate name: {c}"); - } - } - cargo_venv + format!( + "CARGO_BIN_FILE_{}", + crate_name + .chars() + .map(|c| match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' => c.to_ascii_uppercase(), + '-' | '_' => '_', + _ => panic!("Invalid character in crate name: '{c}'"), + }) + .collect::() + ) } fn main() { - println!("cargo:rerun-if-changed=./beskar-core"); - println!("cargo:rerun-if-changed=./beskar-hal"); - println!("cargo:rerun-if-changed=./beskar-lib"); - println!("cargo:rerun-if-changed=./bootloader"); - println!("cargo:rerun-if-changed=./hyperdrive"); - println!("cargo:rerun-if-changed=./kernel"); - println!("cargo:rerun-if-changed=./userspace"); + cargo!("rerun-if-changed", "./build.rs"); + cargo!("rerun-if-changed", "./beskar-core"); + cargo!("rerun-if-changed", "./beskar-hal"); + cargo!("rerun-if-changed", "./beskar-lib"); + cargo!("rerun-if-changed", "./bootloader"); + cargo!("rerun-if-changed", "./hyperdrive"); + cargo!("rerun-if-changed", "./kernel"); + cargo!("rerun-if-changed", "./userspace"); let bootloader_path = var("CARGO_BIN_FILE_BOOTLOADER").unwrap(); let kernel_path = var("CARGO_BIN_FILE_KERNEL").unwrap(); @@ -37,8 +60,8 @@ fn main() { fs::copy(&kernel_path, "efi_disk/efi/kernelx64.elf").expect("Failed to copy kernel"); // TODO:: Build a disk image for the ramdisk - let hello_world = var("CARGO_BIN_FILE_BASHKAR").unwrap(); - fs::copy(&hello_world, "efi_disk/efi/ramdisk.img").expect("Failed to copy userspace"); + let bashkar = var("CARGO_BIN_FILE_BASHKAR").unwrap(); + fs::copy(&bashkar, "efi_disk/efi/ramdisk.img").expect("Failed to copy bashkar"); for crate_name in USERSPACE_APPS { let cargo_venv = crate_name_to_cargo_venv(crate_name); let _built_path = var(cargo_venv).expect("Failed to get built path"); diff --git a/hyperdrive/src/once.rs b/hyperdrive/src/once.rs index d0dddc8..81269ef 100644 --- a/hyperdrive/src/once.rs +++ b/hyperdrive/src/once.rs @@ -63,7 +63,7 @@ impl State { #[must_use] #[inline] /// Converts the state to a `u8` value. - const fn as_u8(self) -> u8 { + pub const fn as_u8(self) -> u8 { match self { Self::Initialized => 0, Self::Initializing => 1, @@ -76,7 +76,7 @@ impl State { #[inline] /// Converts a `u8` value to a `State`. /// Returns `None` if the value is not a valid state. - const fn from_u8(value: u8) -> Option { + pub const fn from_u8(value: u8) -> Option { match value { 0 => Some(Self::Initialized), 1 => Some(Self::Initializing), @@ -93,7 +93,7 @@ impl State { /// # Safety /// /// The value must be a valid state. - const unsafe fn from_u8_unchecked(value: u8) -> Self { + pub const unsafe fn from_u8_unchecked(value: u8) -> Self { unsafe { Self::from_u8(value).unwrap_unchecked() } } } diff --git a/hyperdrive/src/queues/mpmc.rs b/hyperdrive/src/queues/mpmc.rs index 17e9dfd..f9422ad 100644 --- a/hyperdrive/src/queues/mpmc.rs +++ b/hyperdrive/src/queues/mpmc.rs @@ -47,7 +47,7 @@ impl core::error::Error for MpmcQueueFullError {} /// A multiple-producer multiple-consumer queue. pub struct MpmcQueue { /// The buffer that holds the data. - buffer: [Slot; SIZE], + buffer: [Slot; SIZE], /// The index of the next element to be read. read_index: AtomicUsize, /// The index of the next element to be written. @@ -55,14 +55,14 @@ pub struct MpmcQueue { } #[derive(Debug)] -struct Slot { +struct Slot { /// The sequence number for this slot. sequence: AtomicUsize, /// The value stored in this slot. value: UnsafeCell>, } -impl Slot { +impl Slot { #[must_use] #[inline] pub const fn new(sequence: usize) -> Self { @@ -78,7 +78,7 @@ impl Slot { /// # Safety /// /// The caller must ensure that the value is valid and that no other thread - /// is writing to this slot at the same time. + /// is accessing this slot at the same time. pub unsafe fn write(&self, value: T, pos: usize) { unsafe { (*self.value.get()).write(value) }; self.sequence.store(pos + 1, Ordering::Release); @@ -92,9 +92,9 @@ impl Slot { /// /// The caller must ensure that the value is valid and that no other thread /// is writing to this slot at the same time. - pub unsafe fn read(&self, pos: usize, size: usize) -> T { + pub unsafe fn read(&self, pos: usize) -> T { let value = unsafe { (&*self.value.get()).assume_init_read() }; - self.sequence.store(pos + size, Ordering::Release); + self.sequence.store(pos + SIZE, Ordering::Release); value } } @@ -199,7 +199,7 @@ impl MpmcQueue { ) { Ok(_old) => { // Safety: We are the only thread accessing this slot. - let value = unsafe { slot.read(pos, SIZE) }; + let value = unsafe { slot.read(pos) }; break Some(value); // Successfully pushed the value } Err(current) => { diff --git a/hyperdrive/src/queues/mpsc.rs b/hyperdrive/src/queues/mpsc.rs index e02953e..cc5749d 100644 --- a/hyperdrive/src/queues/mpsc.rs +++ b/hyperdrive/src/queues/mpsc.rs @@ -173,6 +173,8 @@ pub struct MpscQueue { being_dequeued: AtomicBool, /// The stub node. stub: NonNull, + /// dropck marker. + _marker: core::marker::PhantomData, } unsafe impl Send for MpscQueue {} @@ -244,6 +246,7 @@ impl MpscQueue { tail: AtomicPtr::new(stub_ptr.as_ptr()), being_dequeued: AtomicBool::new(false), stub: stub_ptr, + _marker: core::marker::PhantomData, } } diff --git a/kernel/foundry/holonet/src/l3/ip.rs b/kernel/foundry/holonet/src/l3/ip.rs index 98f4fe9..61d7c43 100644 --- a/kernel/foundry/holonet/src/l3/ip.rs +++ b/kernel/foundry/holonet/src/l3/ip.rs @@ -1 +1 @@ -pub use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +pub use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; diff --git a/kernel/foundry/holonet/src/l4/tcp.rs b/kernel/foundry/holonet/src/l4/tcp.rs index 8b13789..a944e81 100644 --- a/kernel/foundry/holonet/src/l4/tcp.rs +++ b/kernel/foundry/holonet/src/l4/tcp.rs @@ -1 +1 @@ - +pub use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; diff --git a/kernel/foundry/holonet/src/l4/udp.rs b/kernel/foundry/holonet/src/l4/udp.rs index 8b13789..a944e81 100644 --- a/kernel/foundry/holonet/src/l4/udp.rs +++ b/kernel/foundry/holonet/src/l4/udp.rs @@ -1 +1 @@ - +pub use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; diff --git a/kernel/foundry/storage/src/vfs.rs b/kernel/foundry/storage/src/vfs.rs index dc55607..e9de0cc 100644 --- a/kernel/foundry/storage/src/vfs.rs +++ b/kernel/foundry/storage/src/vfs.rs @@ -197,10 +197,8 @@ impl Vfs { #[inline] /// Closes all files opened by the given process ID. /// - /// # Safety - /// /// This function should only be called with a `u64` of a process that has completed its execution. - pub unsafe fn close_all_from_process(&self, pid: u64) { + pub fn close_all_from_process(&self, pid: u64) { self.open_handles.write().retain(|_handle, open_file| { let retained = open_file.process_id != pid; if !retained { diff --git a/kernel/src/arch/x86_64/cpuid.rs b/kernel/src/arch/x86_64/cpuid.rs index 3892a16..aa14fd3 100644 --- a/kernel/src/arch/x86_64/cpuid.rs +++ b/kernel/src/arch/x86_64/cpuid.rs @@ -294,6 +294,7 @@ fn cpuid_supported() -> bool { pub enum CpuVendor { Intel, Amd, + ViaTechnology, Other, } @@ -302,6 +303,7 @@ impl From<&[u8; 12]> for CpuVendor { match vendor { b"GenuineIntel" | b"GenuineIotel" => Self::Intel, b"AuthenticAMD" => Self::Amd, + b"CentaurHauls" | b"VIA VIA VIA " => Self::ViaTechnology, _ => Self::Other, } } diff --git a/kernel/src/arch/x86_64/syscall.rs b/kernel/src/arch/x86_64/syscall.rs index e38a5cd..eac056c 100644 --- a/kernel/src/arch/x86_64/syscall.rs +++ b/kernel/src/arch/x86_64/syscall.rs @@ -7,12 +7,12 @@ use beskar_hal::registers::{Efer, LStar, Rflags, SFMask, Star, StarSelectors}; #[derive(Debug, Clone, Copy)] #[repr(C, align(8))] +/// Represents the pushed registers during a syscall. +/// +/// We only push Caller-saved registers, as the others will be saved +/// by the inner syscall handlers. struct SyscallRegisters { rax: u64, - r15: u64, - r14: u64, - r13: u64, - r12: u64, rdi: u64, rsi: u64, rdx: u64, @@ -41,18 +41,10 @@ unsafe extern "sysv64" fn syscall_handler_arch() { "push rdx", "push rsi", "push rdi", - "push r12", - "push r13", - "push r14", - "push r15", "push rax", "mov rdi, rsp", // Regs pointer "call {}", "pop rax", // RAX now contains syscall exit code - "pop r15", - "pop r14", - "pop r13", - "pop r12", "pop rdi", "pop rsi", "pop rdx", @@ -69,7 +61,7 @@ unsafe extern "sysv64" fn syscall_handler_arch() { /// Handles stack switching and calling the actual syscall handler. /// /// This function is called from the assembly stub above. -extern "sysv64" fn syscall_handler_impl(regs: *mut SyscallRegisters) { +extern "sysv64" fn syscall_handler_impl(regs: &mut SyscallRegisters) { // Currently, we are on the user stack. It is undefined whether we are right where the // assembly stub left us (because of the prologue), but the place we want to be is in the `regs` argument. @@ -108,10 +100,7 @@ extern "sysv64" fn syscall_handler_inner(regs: &mut SyscallRegisters) { six: regs.r9, }; - let res = syscall( - Syscall::try_from(regs.rax).unwrap_or(Syscall::Invalid), - &args, - ); + let res = syscall(Syscall::from(regs.rax), &args); // Store result regs.rax = res.as_u64(); diff --git a/kernel/src/process.rs b/kernel/src/process.rs index ed0aa34..a92969d 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -143,9 +143,7 @@ impl Drop for Process { self.name, self.pid.as_u64() ); - // Safety: The process is finished and has no more threads running. - // We can safely close all files associated with it. - unsafe { crate::storage::vfs().close_all_from_process(self.pid.as_u64()) }; + crate::storage::vfs().close_all_from_process(self.pid.as_u64()); } } diff --git a/kernel/src/time.rs b/kernel/src/time.rs index 02ff390..eb5fa6f 100644 --- a/kernel/src/time.rs +++ b/kernel/src/time.rs @@ -11,9 +11,9 @@ struct TscClock; pub fn init() { let hpet_res = crate::drivers::hpet::init(); - HPET_AVAILABLE.store(hpet_res.is_ok(), Ordering::Release); + HPET_AVAILABLE.store(hpet_res.is_ok(), Ordering::Relaxed); let tsc_res = crate::drivers::tsc::init(); - TSC_AVAILABLE.store(tsc_res.is_ok(), Ordering::Release); + TSC_AVAILABLE.store(tsc_res.is_ok(), Ordering::Relaxed); } /// Waits for AT LEAST the given number of milliseconds. From 07222dcdfd1a33c09d8546ef3736fcfe631e959c Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sat, 16 Aug 2025 20:09:54 +0200 Subject: [PATCH 10/11] update and build with latest nightly --- Cargo.lock | 24 ++++++++++++------------ beskar-core/src/syscall.rs | 3 +-- bootloader/src/video/log.rs | 4 ++-- hyperdrive/src/locks/ticket.rs | 13 +------------ hyperdrive/src/queues/ring.rs | 8 ++++++++ kernel/src/arch/x86_64/interrupts.rs | 4 ++-- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b39679f..7336288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "bootloader" @@ -184,9 +184,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "e7442a916bc70e3bf71a5305a899d53301649d2838345fa309420318b5ffafe8" dependencies = [ "unicode-ident", ] @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "storage" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -248,18 +248,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", diff --git a/beskar-core/src/syscall.rs b/beskar-core/src/syscall.rs index 7889323..3bb6f77 100644 --- a/beskar-core/src/syscall.rs +++ b/beskar-core/src/syscall.rs @@ -81,8 +81,7 @@ impl SyscallExitCode { assert_eq!( self, Self::Success, - "Called unwrap on a syscall exit code that was not successful: {:?}", - self + "Called unwrap on a syscall exit code that was not successful: {self:?}", ); } } diff --git a/bootloader/src/video/log.rs b/bootloader/src/video/log.rs index 927464c..004d229 100644 --- a/bootloader/src/video/log.rs +++ b/bootloader/src/video/log.rs @@ -15,7 +15,7 @@ pub fn init_serial() { } pub fn init_screen() { - let info = crate::video::with_physical_framebuffer(|screen| screen.info()); + let info = super::with_physical_framebuffer(|screen| screen.info()); let screen = ScreenWriter::new(info); SCREEN_LOGGER.init(screen); } @@ -118,7 +118,7 @@ impl ScreenWriter { impl core::fmt::Write for ScreenWriter { fn write_str(&mut self, s: &str) -> core::fmt::Result { - crate::video::with_physical_framebuffer(|screen| { + super::with_physical_framebuffer(|screen| { self.0.write_str(screen.buffer_mut(), s); }); Ok(()) diff --git a/hyperdrive/src/locks/ticket.rs b/hyperdrive/src/locks/ticket.rs index 0a44b4d..de8ffab 100644 --- a/hyperdrive/src/locks/ticket.rs +++ b/hyperdrive/src/locks/ticket.rs @@ -109,16 +109,6 @@ impl TicketLock { unsafe { &mut *self.data.get() } } - #[inline] - /// Unlocks the ticket lock. - /// - /// # Safety - /// - /// The caller must be the owner of the lock. - unsafe fn unlock(&self) { - self.now_serving.fetch_add(1, Ordering::Release); - } - #[must_use] #[inline] /// Consumes the lock and returns the inner data. @@ -135,8 +125,7 @@ pub struct TicketGuard<'l, T, B: BackOff> { impl Drop for TicketGuard<'_, T, B> { #[inline] fn drop(&mut self) { - // Safety: If the guard exists, we have the lock. - unsafe { self.lock.unlock() }; + self.lock.now_serving.fetch_add(1, Ordering::Release); } } diff --git a/hyperdrive/src/queues/ring.rs b/hyperdrive/src/queues/ring.rs index bd33722..6f71c9a 100644 --- a/hyperdrive/src/queues/ring.rs +++ b/hyperdrive/src/queues/ring.rs @@ -77,6 +77,10 @@ impl Ring { #[must_use] #[inline] /// Creates a new ring buffer. + /// + /// # Panics + /// + /// This function panics if the `SIZE` is 0. pub const fn new() -> Self { assert!(SIZE > 0, "Ring buffer size must be greater than 0"); Self { @@ -159,6 +163,10 @@ impl Ring { } /// Pushes a new value into the ring buffer. + /// + /// # Panics + /// + /// This function panics if the buffer is full. pub const fn push(&mut self, value: T) { let next_write_index = self.next_write_index(); diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index 421267f..08ce393 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -132,7 +132,7 @@ macro_rules! panic_isr { macro_rules! panic_isr_with_errcode { ($name:ident) => { - extern "x86-interrupt" fn $name(stack_frame: InterruptStackFrame, err_code: u64) -> () { + extern "x86-interrupt" fn $name(stack_frame: InterruptStackFrame, err_code: u64) { panic!( "EXCEPTION: {} INTERRUPT {:#x} on core {}\n{:#?}", stringify!($name), @@ -146,7 +146,7 @@ macro_rules! panic_isr_with_errcode { macro_rules! info_isr { ($name:ident) => { - extern "x86-interrupt" fn $name(_stack_frame: InterruptStackFrame) -> () { + extern "x86-interrupt" fn $name(_stack_frame: InterruptStackFrame) { video::info!( "{} INTERRUPT on core {} - t{}", stringify!($name), From 2efa245f0721c4666a92e83c3caaebabb5b9bc67 Mon Sep 17 00:00:00 2001 From: Mathis Bottinelli Date: Sat, 20 Sep 2025 23:54:01 +0200 Subject: [PATCH 11/11] doom: port DOOM to BeskarOS --- Cargo.lock | 64 ++++++-- Cargo.toml | 3 +- README.md | 6 +- beskar-core/src/drivers/keyboard.rs | 197 +++++++++++++++++++++++-- beskar-core/src/storage.rs | 2 + beskar-lib/src/arch/x86_64.rs | 1 + beskar-lib/src/arch/x86_64/syscalls.rs | 9 +- beskar-lib/src/arch/x86_64/time.rs | 26 ++++ beskar-lib/src/io/keyboard.rs | 2 +- beskar-lib/src/lib.rs | 4 + beskar-lib/src/mem.rs | 8 +- beskar-lib/src/time.rs | 34 +++++ build.rs | 15 +- kernel/src/arch/x86_64/apic/timer.rs | 9 +- kernel/src/drivers/ps2.rs | 194 ++++++++++++------------ kernel/src/mem/address_space.rs | 6 +- kernel/src/process/binary/elf.rs | 61 ++++---- kernel/src/process/scheduler.rs | 4 +- userspace/README.md | 5 + userspace/bashkar/src/video/screen.rs | 1 - userspace/bashkar/src/video/tty.rs | 35 ++++- userspace/doom/.gitignore | 1 + userspace/doom/Cargo.toml | 12 ++ userspace/doom/README.md | 38 +++++ userspace/doom/build.rs | 34 +++++ userspace/doom/src/game.rs | 190 ++++++++++++++++++++++++ userspace/doom/src/input.rs | 174 ++++++++++++++++++++++ userspace/doom/src/lib.rs | 8 + userspace/doom/src/main.rs | 29 ++++ userspace/doom/src/screen.rs | 148 +++++++++++++++++++ 30 files changed, 1133 insertions(+), 187 deletions(-) create mode 100644 beskar-lib/src/arch/x86_64/time.rs create mode 100644 beskar-lib/src/time.rs create mode 100644 userspace/doom/.gitignore create mode 100644 userspace/doom/Cargo.toml create mode 100644 userspace/doom/README.md create mode 100644 userspace/doom/build.rs create mode 100644 userspace/doom/src/game.rs create mode 100644 userspace/doom/src/input.rs create mode 100644 userspace/doom/src/lib.rs create mode 100644 userspace/doom/src/main.rs create mode 100644 userspace/doom/src/screen.rs diff --git a/Cargo.lock b/Cargo.lock index 7336288..be08881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,15 +58,15 @@ dependencies = [ [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bootloader" @@ -88,11 +88,31 @@ dependencies = [ "beskar-hal", ] +[[package]] +name = "cc" +version = "1.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "doom" +version = "0.1.0" +dependencies = [ + "beskar-core", + "beskar-lib", + "cc", + "hyperdrive", +] [[package]] name = "driver-api" @@ -102,6 +122,12 @@ dependencies = [ "beskar-hal", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "holonet" version = "0.1.0" @@ -140,9 +166,9 @@ checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "noto-sans-mono-bitmap" @@ -184,9 +210,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.98" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7442a916bc70e3bf71a5305a899d53301649d2838345fa309420318b5ffafe8" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -226,6 +252,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "storage" version = "0.1.0" @@ -248,18 +280,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -320,9 +352,9 @@ checksum = "ab14ea9660d240e7865ce9d54ecdbd1cd9fa5802ae6f4512f093c7907e921533" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "video" diff --git a/Cargo.toml b/Cargo.toml index ebc74f1..924552f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/mathisbot/beskar-os/" members = [ "bootloader", "kernel", - "userspace/bashkar" + "userspace/*" ] [workspace.dependencies] @@ -27,6 +27,7 @@ xmas-elf = "0.10.0" bootloader = { path = "bootloader", artifact = "bin", target = "x86_64-unknown-uefi" } kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } bashkar = { path = "userspace/bashkar", artifact = "bin", target = "x86_64-unknown-none" } +# doom = { path = "userspace/doom", artifact = "bin", target = "x86_64-unknown-none" } [profile.release] panic = "abort" diff --git a/README.md b/README.md index 69514ba..93de707 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Throughout the development process, I always follow four objectives: I have many milestones ideas for the OS: -1. Being completely self-hosted -2. Successfully ping `1.1.1.1` -3. Run Doom? +- [X] Run Doom (see [Doom](userspace/doom/README.md)) +- [ ] Successfully ping `1.1.1.1` +- [ ] Being completely self-hosted ## Design and architecture diff --git a/beskar-core/src/drivers/keyboard.rs b/beskar-core/src/drivers/keyboard.rs index 08ea451..0e46eb4 100644 --- a/beskar-core/src/drivers/keyboard.rs +++ b/beskar-core/src/drivers/keyboard.rs @@ -10,7 +10,7 @@ impl KeyEvent { /// The value used to represent `None` when packing the key event. /// /// This value MUST NOT represent a valid key event. - const NONE: u64 = u64::MAX; + const NONE: u64 = 0xFFFF; #[must_use] #[inline] @@ -43,14 +43,9 @@ impl KeyEvent { #[must_use] #[inline] pub fn unpack_option(value: u64) -> Option { - if value == Self::NONE { - None - } else { - debug_assert!(value >> 16 == 0); - let key = KeyCode::from(u8::try_from(value & 0xFF).unwrap()); - let pressed = KeyState::try_from(u8::try_from((value >> 8) & 0xFF).unwrap()).unwrap(); - Some(Self { key, pressed }) - } + let key = KeyCode::from(u8::try_from(value & 0xFF).ok()?); + let pressed = KeyState::try_from(u8::try_from((value >> 8) & 0xFF).unwrap()).ok()?; + Some(Self { key, pressed }) } } @@ -175,8 +170,32 @@ pub enum KeyCode { impl KeyCode { #[must_use] - pub const fn as_char(&self) -> char { - match self { + #[inline] + pub const fn is_numpad(&self) -> bool { + matches!( + self, + Self::Numpad0 + | Self::Numpad1 + | Self::Numpad2 + | Self::Numpad3 + | Self::Numpad4 + | Self::Numpad5 + | Self::Numpad6 + | Self::Numpad7 + | Self::Numpad8 + | Self::Numpad9 + | Self::NumpadAdd + | Self::NumpadSub + | Self::NumpadMul + | Self::NumpadDiv + | Self::NumpadEnter + | Self::NumpadDot + ) + } + + #[must_use] + pub const fn as_char(&self, modifiers: KeyModifiers) -> char { + let raw = match self { Self::A => 'a', Self::B => 'b', Self::C => 'c', @@ -204,8 +223,23 @@ impl KeyCode { Self::Y => 'y', Self::Z => 'z', Self::Space => ' ', - + Self::Numpad0 => '0', + Self::Numpad1 => '1', + Self::Numpad2 => '2', + Self::Numpad3 => '3', + Self::Numpad4 => '4', + Self::Numpad5 => '5', + Self::Numpad6 => '6', + Self::Numpad7 => '7', + Self::Numpad8 => '8', + Self::Numpad9 => '9', _ => '\0', + }; + + if raw.is_ascii_alphabetic() && modifiers.is_uppercase() { + raw.to_ascii_uppercase() + } else { + raw } } } @@ -216,3 +250,142 @@ pub enum KeyState { Pressed, Released, } + +#[derive(Default, Debug, Clone, Copy)] +pub struct KeyModifiers { + flags: u8, +} + +impl KeyModifiers { + const SHIFT: u8 = 0b0000_0001; + const CTRL: u8 = 0b0000_0010; + const ALT: u8 = 0b0000_0100; + const CAPS_LOCK: u8 = 0b0000_1000; + const NUM_LOCK: u8 = 0b0001_0000; + + #[must_use] + #[inline] + pub const fn new() -> Self { + Self { flags: 0 } + } + + #[must_use] + #[inline] + pub const fn is_shifted(&self) -> bool { + self.flags & Self::SHIFT != 0 + } + + #[must_use] + #[inline] + pub const fn is_ctrled(&self) -> bool { + self.flags & Self::CTRL != 0 + } + + #[must_use] + #[inline] + pub const fn is_alted(&self) -> bool { + self.flags & Self::ALT != 0 + } + + #[must_use] + #[inline] + pub const fn is_caps_locked(&self) -> bool { + self.flags & Self::CAPS_LOCK != 0 + } + + #[must_use] + #[inline] + pub const fn is_num_locked(&self) -> bool { + self.flags & Self::NUM_LOCK != 0 + } + + #[must_use] + #[inline] + pub const fn is_uppercase(&self) -> bool { + self.is_shifted() ^ self.is_caps_locked() + } + + #[inline] + pub const fn set_shifted(&mut self, shifted: bool) { + if shifted { + self.flags |= Self::SHIFT; + } else { + self.flags &= !Self::SHIFT; + } + } + + #[inline] + pub const fn set_ctrled(&mut self, ctrled: bool) { + if ctrled { + self.flags |= Self::CTRL; + } else { + self.flags &= !Self::CTRL; + } + } + + #[inline] + pub const fn set_alted(&mut self, alted: bool) { + if alted { + self.flags |= Self::ALT; + } else { + self.flags &= !Self::ALT; + } + } + + #[inline] + pub const fn set_caps_locked(&mut self, caps_locked: bool) { + if caps_locked { + self.flags |= Self::CAPS_LOCK; + } else { + self.flags &= !Self::CAPS_LOCK; + } + } + + #[inline] + pub const fn set_num_locked(&mut self, num_locked: bool) { + if num_locked { + self.flags |= Self::NUM_LOCK; + } else { + self.flags &= !Self::NUM_LOCK; + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_keycode_packing() { + let key_event = super::KeyEvent::new(super::KeyCode::A, super::KeyState::Pressed); + let packed = super::KeyEvent::pack_option(Some(key_event)); + let unpacked = super::KeyEvent::unpack_option(packed).unwrap(); + assert_eq!(key_event.key(), unpacked.key()); + assert_eq!(key_event.pressed(), unpacked.pressed()); + + let none_packed = super::KeyEvent::pack_option(None); + assert_eq!(none_packed, super::KeyEvent::NONE); + let none_unpacked = super::KeyEvent::unpack_option(none_packed); + assert!(none_unpacked.is_none()); + } + + #[test] + fn test_keycode_casing() { + let mut modifiers = super::KeyModifiers::new(); + + assert!(!modifiers.is_uppercase()); + assert_eq!(super::KeyCode::A.as_char(modifiers), 'a'); + assert_eq!(super::KeyCode::Z.as_char(modifiers), 'z'); + assert_eq!(super::KeyCode::Space.as_char(modifiers), ' '); + + modifiers.set_caps_locked(true); + assert!(modifiers.is_uppercase()); + assert_eq!(super::KeyCode::A.as_char(modifiers), 'A'); + assert_eq!(super::KeyCode::Z.as_char(modifiers), 'Z'); + assert_eq!(super::KeyCode::Space.as_char(modifiers), ' '); + + modifiers.set_shifted(true); + assert!(!modifiers.is_uppercase()); + assert_eq!(super::KeyCode::A.as_char(modifiers), 'a'); + assert_eq!(super::KeyCode::Z.as_char(modifiers), 'z'); + assert_eq!(super::KeyCode::Space.as_char(modifiers), ' '); + } +} diff --git a/beskar-core/src/storage.rs b/beskar-core/src/storage.rs index cd7fa4c..274dc83 100644 --- a/beskar-core/src/storage.rs +++ b/beskar-core/src/storage.rs @@ -68,10 +68,12 @@ pub trait KernelDevice { impl BlockDevice for T { const BLOCK_SIZE: usize = 1; + #[inline] fn read(&mut self, dst: &mut [u8], offset: usize) -> Result<(), BlockDeviceError> { self.read(dst, offset) } + #[inline] fn write(&mut self, src: &[u8], offset: usize) -> Result<(), BlockDeviceError> { self.write(src, offset) } diff --git a/beskar-lib/src/arch/x86_64.rs b/beskar-lib/src/arch/x86_64.rs index 1d7397e..7d2d0c9 100644 --- a/beskar-lib/src/arch/x86_64.rs +++ b/beskar-lib/src/arch/x86_64.rs @@ -1 +1,2 @@ pub mod syscalls; +pub mod time; diff --git a/beskar-lib/src/arch/x86_64/syscalls.rs b/beskar-lib/src/arch/x86_64/syscalls.rs index 13649a8..6411bbe 100644 --- a/beskar-lib/src/arch/x86_64/syscalls.rs +++ b/beskar-lib/src/arch/x86_64/syscalls.rs @@ -6,9 +6,10 @@ pub fn syscall_1(syscall: Syscall, arg1: u64) -> u64 { unsafe { ::core::arch::asm!( "syscall", - in("rax") syscall as u64, + in("rax") u64::from(syscall), lateout("rax") res_code, in("rdi") arg1, + options(nostack, preserves_flags) ); } res_code @@ -20,10 +21,11 @@ pub fn syscall_2(syscall: Syscall, arg1: u64, arg2: u64) -> u64 { unsafe { ::core::arch::asm!( "syscall", - in("rax") syscall as u64, + in("rax") u64::from(syscall), lateout("rax") res_code, in("rdi") arg1, in("rsi") arg2, + options(nostack, preserves_flags) ); } res_code @@ -35,12 +37,13 @@ pub fn syscall_4(syscall: Syscall, arg1: u64, arg2: u64, arg3: u64, arg4: u64) - unsafe { ::core::arch::asm!( "syscall", - in("rax") syscall as u64, + in("rax") u64::from(syscall), lateout("rax") res_code, in("rdi") arg1, in("rsi") arg2, in("rdx") arg3, in("r10") arg4, + options(nostack, preserves_flags) ); } res_code diff --git a/beskar-lib/src/arch/x86_64/time.rs b/beskar-lib/src/arch/x86_64/time.rs new file mode 100644 index 0000000..7f939bd --- /dev/null +++ b/beskar-lib/src/arch/x86_64/time.rs @@ -0,0 +1,26 @@ +use beskar_core::time::MILLIS_PER_SEC; + +#[must_use] +#[inline] +pub fn read_tsc_fenced() -> u64 { + unsafe { + core::arch::x86_64::_mm_mfence(); + let tsc = core::arch::x86_64::_rdtsc(); + core::arch::x86_64::_mm_lfence(); + tsc + } +} + +#[must_use] +/// Returns the TSC frequency in Hz. +pub fn get_tsc_frequency() -> u64 { + const MEASURE_TIME_MS: u64 = 100; + const QUANTUM_PER_SEC: u64 = MILLIS_PER_SEC / MEASURE_TIME_MS; + + let start = read_tsc_fenced(); + crate::sleep(crate::time::Duration::from_millis(MEASURE_TIME_MS)); + let end = read_tsc_fenced(); + + let delta = end - start; + delta * QUANTUM_PER_SEC +} diff --git a/beskar-lib/src/io/keyboard.rs b/beskar-lib/src/io/keyboard.rs index d815a87..7a0bbbe 100644 --- a/beskar-lib/src/io/keyboard.rs +++ b/beskar-lib/src/io/keyboard.rs @@ -1,5 +1,5 @@ use super::File; -pub use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyState}; +pub use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyModifiers, KeyState}; #[repr(align(8))] struct KeyboardEventBuffer([u8; size_of::()]); diff --git a/beskar-lib/src/lib.rs b/beskar-lib/src/lib.rs index f94a12f..0283633 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -13,6 +13,7 @@ mod arch; pub mod io; pub mod mem; pub mod rand; +pub mod time; #[panic_handler] fn panic(info: &::core::panic::PanicInfo) -> ! { @@ -42,6 +43,7 @@ pub fn sleep(duration: Duration) { /// Sets the entry point for the program. macro_rules! entry_point { ($path:path) => { + #[macro_use] extern crate alloc; #[unsafe(export_name = "_start")] @@ -64,5 +66,7 @@ pub fn __init() { CALL_ONCE.call_once(|| { let res = mem::mmap(mem::HEAP_SIZE, None); unsafe { mem::init_heap(res.as_ptr(), mem::HEAP_SIZE.try_into().unwrap()) }; + + time::init(); }); } diff --git a/beskar-lib/src/mem.rs b/beskar-lib/src/mem.rs index 7060683..d7b73a8 100644 --- a/beskar-lib/src/mem.rs +++ b/beskar-lib/src/mem.rs @@ -1,5 +1,8 @@ use crate::arch::syscalls; -use beskar_core::syscall::Syscall; +use beskar_core::{ + arch::paging::{M4KiB, MemSize as _}, + syscall::Syscall, +}; use core::{num::NonZeroU64, ptr::NonNull}; use hyperdrive::locks::mcs::MUMcsLock; @@ -10,7 +13,8 @@ struct Heap; #[global_allocator] static HEAP: Heap = Heap; -pub(crate) const HEAP_SIZE: u64 = 1024 * 1024; // 1 MiB +pub(crate) const HEAP_SIZE: u64 = 20 * 1024 * 1024; // 20 MiB +beskar_core::static_assert!(HEAP_SIZE.is_multiple_of(M4KiB::SIZE)); unsafe impl core::alloc::GlobalAlloc for Heap { unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 { diff --git a/beskar-lib/src/time.rs b/beskar-lib/src/time.rs new file mode 100644 index 0000000..a09bd9f --- /dev/null +++ b/beskar-lib/src/time.rs @@ -0,0 +1,34 @@ +pub use beskar_core::time::{Duration, Instant, MILLIS_PER_SEC}; +use hyperdrive::once::Once; + +static STARTUP_TIME: Once = Once::uninit(); + +#[must_use] +/// Reads the time in milliseconds since an arbitrary point in the past. +fn read_time_raw() -> u64 { + #[cfg(target_arch = "x86_64")] + { + static FREQ: Once = Once::uninit(); + FREQ.call_once(crate::arch::time::get_tsc_frequency); + let freq = *FREQ.get().unwrap(); + let tsc = crate::arch::time::read_tsc_fenced(); + tsc * MILLIS_PER_SEC / freq + } + + #[cfg(not(any(target_arch = "x86_64")))] + { + unimplemented!("Time reading not implemented for this architecture"); + } +} + +/// Initializes the time module. +pub(crate) fn init() { + STARTUP_TIME.call_once(|| Instant::from_millis(read_time_raw())); +} + +#[must_use] +#[inline] +/// Returns the current instant. +pub fn now() -> Instant { + Instant::from_millis(read_time_raw()) +} diff --git a/build.rs b/build.rs index de7ab46..68db698 100644 --- a/build.rs +++ b/build.rs @@ -59,12 +59,15 @@ fn main() { .expect("Failed to copy bootloader.efi"); fs::copy(&kernel_path, "efi_disk/efi/kernelx64.elf").expect("Failed to copy kernel"); - // TODO:: Build a disk image for the ramdisk let bashkar = var("CARGO_BIN_FILE_BASHKAR").unwrap(); fs::copy(&bashkar, "efi_disk/efi/ramdisk.img").expect("Failed to copy bashkar"); - for crate_name in USERSPACE_APPS { - let cargo_venv = crate_name_to_cargo_venv(crate_name); - let _built_path = var(cargo_venv).expect("Failed to get built path"); - // TODO: Add to the ramdisk image - } + // let doom = var("CARGO_BIN_FILE_DOOM").unwrap(); + // fs::copy(&doom, "efi_disk/efi/ramdisk.img").expect("Failed to copy doom"); + + // TODO:: Build a disk image for the ramdisk + // for crate_name in USERSPACE_APPS { + // let cargo_venv = crate_name_to_cargo_venv(crate_name); + // let _built_path = var(cargo_venv).expect("Failed to get built path"); + // // TODO: Add to the ramdisk image + // } } diff --git a/kernel/src/arch/x86_64/apic/timer.rs b/kernel/src/arch/x86_64/apic/timer.rs index 945dc85..2d0ee15 100644 --- a/kernel/src/arch/x86_64/apic/timer.rs +++ b/kernel/src/arch/x86_64/apic/timer.rs @@ -188,8 +188,9 @@ impl Configuration { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum Mode { + #[default] Inactive, OneShot(ModeConfiguration), Periodic(ModeConfiguration), @@ -197,12 +198,6 @@ pub enum Mode { TscDeadline, } -impl Default for Mode { - fn default() -> Self { - Self::Inactive - } -} - impl Mode { fn as_vte_bits(&self) -> u32 { match self { diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs index 2474ccd..490b367 100644 --- a/kernel/src/drivers/ps2.rs +++ b/kernel/src/drivers/ps2.rs @@ -339,13 +339,10 @@ impl<'a> Ps2Keyboard<'a> { } #[must_use] + #[inline] pub fn scancode_to_keycode(&self, extended: bool, scancode: u8) -> Option { - if extended { - // TODO: Handle extended keys - return None; - } match self.scancode_set { - ScancodeSet::Set1 => Self::scancode_set1_to_keycode(scancode), + ScancodeSet::Set1 => Self::scancode_set1_to_keycode(extended, scancode), // 2 => self.scancode_set2_to_char(scancode), // 3 => self.scancode_set3_to_char(scancode), _ => None, @@ -353,7 +350,7 @@ impl<'a> Ps2Keyboard<'a> { } #[must_use] - fn scancode_set1_to_keycode(mut scancode: u8) -> Option { + fn scancode_set1_to_keycode(extended: bool, mut scancode: u8) -> Option { let pressed = if scancode & 0x80 == 0 { KeyState::Pressed } else { @@ -361,95 +358,100 @@ impl<'a> Ps2Keyboard<'a> { KeyState::Released }; - let keycode = match scancode { - 0x1E => Some(KeyCode::A), - 0x30 => Some(KeyCode::B), - 0x2E => Some(KeyCode::C), - 0x20 => Some(KeyCode::D), - 0x12 => Some(KeyCode::E), - 0x21 => Some(KeyCode::F), - 0x22 => Some(KeyCode::G), - 0x23 => Some(KeyCode::H), - 0x17 => Some(KeyCode::I), - 0x24 => Some(KeyCode::J), - 0x25 => Some(KeyCode::K), - 0x26 => Some(KeyCode::L), - 0x32 => Some(KeyCode::M), - 0x31 => Some(KeyCode::N), - 0x18 => Some(KeyCode::O), - 0x19 => Some(KeyCode::P), - 0x10 => Some(KeyCode::Q), - 0x13 => Some(KeyCode::R), - 0x1F => Some(KeyCode::S), - 0x14 => Some(KeyCode::T), - 0x16 => Some(KeyCode::U), - 0x2F => Some(KeyCode::V), - 0x11 => Some(KeyCode::W), - 0x2D => Some(KeyCode::X), - 0x15 => Some(KeyCode::Y), - 0x2C => Some(KeyCode::Z), - - 0x0B => Some(KeyCode::Num0), - 0x02 => Some(KeyCode::Num1), - 0x03 => Some(KeyCode::Num2), - 0x04 => Some(KeyCode::Num3), - 0x05 => Some(KeyCode::Num4), - 0x06 => Some(KeyCode::Num5), - 0x07 => Some(KeyCode::Num6), - 0x08 => Some(KeyCode::Num7), - 0x09 => Some(KeyCode::Num8), - 0x0A => Some(KeyCode::Num9), - - 0x0C => Some(KeyCode::Minus), - 0x0D => Some(KeyCode::Equal), - 0x1A => Some(KeyCode::LeftBracket), - 0x1B => Some(KeyCode::RightBracket), - 0x2B => Some(KeyCode::Backslash), - 0x27 => Some(KeyCode::Semicolon), - 0x28 => Some(KeyCode::Apostrophe), - 0x29 => Some(KeyCode::Tilde), - 0x33 => Some(KeyCode::Comma), - 0x34 => Some(KeyCode::Dot), - 0x35 => Some(KeyCode::Slash), - - 0x1C => Some(KeyCode::Enter), - 0x39 => Some(KeyCode::Space), - 0x0E => Some(KeyCode::Backspace), - 0x0F => Some(KeyCode::Tab), - 0x3A => Some(KeyCode::CapsLock), - 0x2A => Some(KeyCode::ShiftLeft), - 0x36 => Some(KeyCode::ShiftRight), - 0x1D => Some(KeyCode::CtrlLeft), - 0x38 => Some(KeyCode::AltLeft), - - 0x3B => Some(KeyCode::F1), - 0x3C => Some(KeyCode::F2), - 0x3D => Some(KeyCode::F3), - 0x3E => Some(KeyCode::F4), - 0x3F => Some(KeyCode::F5), - 0x40 => Some(KeyCode::F6), - 0x41 => Some(KeyCode::F7), - 0x42 => Some(KeyCode::F8), - 0x43 => Some(KeyCode::F9), - 0x44 => Some(KeyCode::F10), - 0x57 => Some(KeyCode::F11), - 0x58 => Some(KeyCode::F12), - - 0x52 => Some(KeyCode::Numpad0), - 0x4F => Some(KeyCode::Numpad1), - 0x50 => Some(KeyCode::Numpad2), - 0x51 => Some(KeyCode::Numpad3), - 0x4B => Some(KeyCode::Numpad4), - 0x4C => Some(KeyCode::Numpad5), - 0x4D => Some(KeyCode::Numpad6), - 0x47 => Some(KeyCode::Numpad7), - 0x48 => Some(KeyCode::Numpad8), - 0x49 => Some(KeyCode::Numpad9), - 0x4E => Some(KeyCode::NumpadAdd), - 0x4A => Some(KeyCode::NumpadSub), - 0x37 => Some(KeyCode::NumpadMul), - // 0x35 => Some(KeyCode::NumpadDiv), - 0x53 => Some(KeyCode::NumpadDot), + let keycode = match (extended, scancode) { + (false, 0x1E) => Some(KeyCode::A), + (false, 0x30) => Some(KeyCode::B), + (false, 0x2E) => Some(KeyCode::C), + (false, 0x20) => Some(KeyCode::D), + (false, 0x12) => Some(KeyCode::E), + (false, 0x21) => Some(KeyCode::F), + (false, 0x22) => Some(KeyCode::G), + (false, 0x23) => Some(KeyCode::H), + (false, 0x17) => Some(KeyCode::I), + (false, 0x24) => Some(KeyCode::J), + (false, 0x25) => Some(KeyCode::K), + (false, 0x26) => Some(KeyCode::L), + (false, 0x32) => Some(KeyCode::M), + (false, 0x31) => Some(KeyCode::N), + (false, 0x18) => Some(KeyCode::O), + (false, 0x19) => Some(KeyCode::P), + (false, 0x10) => Some(KeyCode::Q), + (false, 0x13) => Some(KeyCode::R), + (false, 0x1F) => Some(KeyCode::S), + (false, 0x14) => Some(KeyCode::T), + (false, 0x16) => Some(KeyCode::U), + (false, 0x2F) => Some(KeyCode::V), + (false, 0x11) => Some(KeyCode::W), + (false, 0x2D) => Some(KeyCode::X), + (false, 0x15) => Some(KeyCode::Y), + (false, 0x2C) => Some(KeyCode::Z), + + (false, 0x0B) => Some(KeyCode::Num0), + (false, 0x02) => Some(KeyCode::Num1), + (false, 0x03) => Some(KeyCode::Num2), + (false, 0x04) => Some(KeyCode::Num3), + (false, 0x05) => Some(KeyCode::Num4), + (false, 0x06) => Some(KeyCode::Num5), + (false, 0x07) => Some(KeyCode::Num6), + (false, 0x08) => Some(KeyCode::Num7), + (false, 0x09) => Some(KeyCode::Num8), + (false, 0x0A) => Some(KeyCode::Num9), + + (false, 0x0C) => Some(KeyCode::Minus), + (false, 0x0D) => Some(KeyCode::Equal), + (false, 0x1A) => Some(KeyCode::LeftBracket), + (false, 0x1B) => Some(KeyCode::RightBracket), + (false, 0x2B) => Some(KeyCode::Backslash), + (false, 0x27) => Some(KeyCode::Semicolon), + (false, 0x28) => Some(KeyCode::Apostrophe), + (false, 0x29) => Some(KeyCode::Tilde), + (false, 0x33) => Some(KeyCode::Comma), + (false, 0x34) => Some(KeyCode::Dot), + (false, 0x35) => Some(KeyCode::Slash), + + (false, 0x1C) => Some(KeyCode::Enter), + (false, 0x39) => Some(KeyCode::Space), + (false, 0x0E) => Some(KeyCode::Backspace), + (false, 0x0F) => Some(KeyCode::Tab), + (false, 0x3A) => Some(KeyCode::CapsLock), + (false, 0x2A) => Some(KeyCode::ShiftLeft), + (false, 0x36) => Some(KeyCode::ShiftRight), + (false, 0x1D) => Some(KeyCode::CtrlLeft), + (false, 0x38) => Some(KeyCode::AltLeft), + + (false, 0x3B) => Some(KeyCode::F1), + (false, 0x3C) => Some(KeyCode::F2), + (false, 0x3D) => Some(KeyCode::F3), + (false, 0x3E) => Some(KeyCode::F4), + (false, 0x3F) => Some(KeyCode::F5), + (false, 0x40) => Some(KeyCode::F6), + (false, 0x41) => Some(KeyCode::F7), + (false, 0x42) => Some(KeyCode::F8), + (false, 0x43) => Some(KeyCode::F9), + (false, 0x44) => Some(KeyCode::F10), + (false, 0x57) => Some(KeyCode::F11), + (false, 0x58) => Some(KeyCode::F12), + + (false, 0x52) => Some(KeyCode::Numpad0), + (false, 0x4F) => Some(KeyCode::Numpad1), + (false, 0x50) => Some(KeyCode::Numpad2), + (false, 0x51) => Some(KeyCode::Numpad3), + (false, 0x4B) => Some(KeyCode::Numpad4), + (false, 0x4C) => Some(KeyCode::Numpad5), + (false, 0x4D) => Some(KeyCode::Numpad6), + (false, 0x47) => Some(KeyCode::Numpad7), + (false, 0x48) => Some(KeyCode::Numpad8), + (false, 0x49) => Some(KeyCode::Numpad9), + (false, 0x4E) => Some(KeyCode::NumpadAdd), + (false, 0x4A) => Some(KeyCode::NumpadSub), + (false, 0x37) => Some(KeyCode::NumpadMul), + // (false, 0x35) => Some(KeyCode::NumpadDiv), + (false, 0x53) => Some(KeyCode::NumpadDot), + + (true, 0x48) => Some(KeyCode::ArrowUp), + (true, 0x50) => Some(KeyCode::ArrowDown), + (true, 0x4B) => Some(KeyCode::ArrowLeft), + (true, 0x4D) => Some(KeyCode::ArrowRight), // TODO: Modifier keys _ => None, @@ -522,7 +524,7 @@ fn handle_real_key(extended: bool, key: u8) { let keyboard = PS2_KEYBOARD.get().unwrap(); let Some(key_event) = keyboard.scancode_to_keycode(extended, key) else { - video::warn!("Unknown key: {:#X}", key); + video::warn!("Unknown key: {:#X} (extended: {})", key, extended); return; }; diff --git a/kernel/src/mem/address_space.rs b/kernel/src/mem/address_space.rs index 27c812c..794f81d 100644 --- a/kernel/src/mem/address_space.rs +++ b/kernel/src/mem/address_space.rs @@ -148,9 +148,8 @@ impl AddressSpace { }); let lvl4_vaddr = { - assert!(u16::try_from(recursive_idx).is_ok(), "Index is too large"); - let i = u64::try_from(recursive_idx).unwrap(); - VirtAddr::new_extend((i << 39) | (i << 30) | (i << 21) | (i << 12)) + let i = u16::try_from(recursive_idx).unwrap(); + VirtAddr::from_pt_indices(i, i, i, i, 0) }; // Create a new process page allocator with a whole PLM4 index area free (256TiB) @@ -177,7 +176,6 @@ impl AddressSpace { /// Returns whether a certain memory range is owned by the address space. pub fn is_addr_owned(&self, start: VirtAddr, end: VirtAddr) -> bool { let Some(idx) = self.pgalloc_pml4_idx else { - #[cfg(debug_assertions)] video::warn!("`AddressSpace::is_addr_owned` called without PGALLOC PML4 index"); return false; }; diff --git a/kernel/src/process/binary/elf.rs b/kernel/src/process/binary/elf.rs index 1683336..af6abd3 100644 --- a/kernel/src/process/binary/elf.rs +++ b/kernel/src/process/binary/elf.rs @@ -70,8 +70,13 @@ pub fn load(input: &[u8]) -> BinaryResult { for (page, wp) in page_range.into_iter().zip(working_page_range) { let frame = fralloc.allocate_frame().unwrap(); pt.map(page, frame, dummy_flags, fralloc).flush(); - pt.map(wp, frame, Flags::PRESENT | Flags::WRITABLE, fralloc) - .flush(); + pt.map( + wp, + frame, + Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, + fralloc, + ) + .flush(); } }); }); @@ -142,7 +147,10 @@ fn sanity_check(elf: &ElfFile) -> BinaryResult<()> { elf.header.pt1.os_abi() == header::OsAbi::SystemV || elf.header.pt1.os_abi() == header::OsAbi::Linux ); - ensure!(elf.header.pt2.entry_point() != 0); + ensure!(matches!( + elf.header.pt2.type_().as_type(), + header::Type::Executable | header::Type::SharedObject + )); Ok(()) } @@ -164,14 +172,12 @@ fn load_segments( handle_segment_load(elf, ph, offset, working_offset)?; } Type::Tls => { - if tls_template.is_some() { - return Err(LoadError::InvalidBinary); - } - tls_template = Some(TlsTemplate { + let previous = tls_template.replace(TlsTemplate { start: offset + ph.virtual_addr(), file_size: ph.file_size(), mem_size: ph.mem_size(), }); + ensure!(previous.is_none()); } Type::Interp => { return Err(LoadError::InvalidBinary); @@ -182,14 +188,14 @@ fn load_segments( // Relocate memory addresses for ph in elf.program_iter() { - if faillible!(ph.get_type()) == Type::Dynamic { + if ph.get_type() == Ok(Type::Dynamic) { handle_segment_dynamic(elf, ph, offset, working_offset)?; } } // Handle GNU_RELRO segments for ph in elf.program_iter() { - if faillible!(ph.get_type()) == Type::GnuRelro { + if ph.get_type() == Ok(Type::GnuRelro) { handle_segment_gnurelro(ph, offset)?; } } @@ -249,27 +255,30 @@ fn zero_bss( working_offset: VirtAddr, ) { let zero_start = offset + ph.virtual_addr() + ph.file_size(); - let zero_end = offset + ph.virtual_addr() + ph.mem_size() - 1; + let zero_end = offset + ph.virtual_addr() + ph.mem_size(); let working_zero_start = working_offset + ph.virtual_addr() + ph.file_size(); - let unaligned = zero_start.as_u64() % M4KiB::SIZE; - if unaligned != 0 { - let len = - usize::try_from((M4KiB::SIZE - unaligned).min(ph.mem_size() - ph.file_size())).unwrap(); - for i in 0..len { - unsafe { - working_zero_start - .as_mut_ptr::() - .byte_add(i) - .write_volatile(0); - } + { + let unaligned_down = zero_start.as_u64() % M4KiB::SIZE; + if unaligned_down != 0 { + let len = + usize::try_from((M4KiB::SIZE - unaligned_down).min(ph.mem_size() - ph.file_size())) + .unwrap(); + unsafe { working_zero_start.as_mut_ptr::().write_bytes(0, len) }; } + let unaligned_up = zero_end.as_u64() % M4KiB::SIZE; + let len = usize::try_from(unaligned_up.min(ph.mem_size() - ph.file_size())).unwrap(); + unsafe { + (working_offset + ph.virtual_addr() + ph.mem_size() - unaligned_up) + .as_mut_ptr::() + .write_bytes(0, len); + }; } let zero_start_page = Page::::from_start_address(zero_start.align_up(M4KiB::SIZE)).unwrap(); - let zero_end_page = Page::::containing_address(zero_end); + let zero_end_page = Page::::containing_address(zero_end.align_down(M4KiB::SIZE) - 1); let mut segment_flags = Flags::PRESENT; if ph.flags().is_write() { @@ -283,20 +292,12 @@ fn zero_bss( } for page in Page::range_inclusive(zero_start_page, zero_end_page) { - // FIXME: Free these frames on binary unload - crate::mem::frame_alloc::with_frame_allocator(|fralloc| { - let frame = fralloc.allocate_frame().unwrap(); - // We need to zero the frame, so start by setting the page as writable - pt.map(page, frame, Flags::PRESENT | Flags::WRITABLE, fralloc) - .flush(); - }); unsafe { page.start_address().as_mut_ptr::().write_bytes( 0, usize::try_from(M4KiB::SIZE).unwrap() / size_of::(), ); } - // Finally, set the page with the correct flags (potentially without WRITABLE) pt.update_flags(page, segment_flags).unwrap().flush(); } } diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index 07e1353..ee1d975 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -335,9 +335,7 @@ impl hyperdrive::locks::BackOff for Yield { /// Put the current thread to sleep. pub fn sleep() { - with_scheduler(|scheduler| { - scheduler.set_sleep(); - }); + with_scheduler(Scheduler::set_sleep); if !thread_yield() { // TODO: What to do if the thread was not rescheduled? diff --git a/userspace/README.md b/userspace/README.md index 694c999..f16febd 100644 --- a/userspace/README.md +++ b/userspace/README.md @@ -4,3 +4,8 @@ This package contains various programs that are intended to be run as guest/user This includes: - Bashkar: BeskarOS basic shell +- Doom: Yes, it can run Doom + +## Additionnal Information + +For more information, see the `README`s of each program. diff --git a/userspace/bashkar/src/video/screen.rs b/userspace/bashkar/src/video/screen.rs index 90fc4fb..d276153 100644 --- a/userspace/bashkar/src/video/screen.rs +++ b/userspace/bashkar/src/video/screen.rs @@ -120,7 +120,6 @@ pub fn screen_info() -> &'static Info { SCREEN_INFO.get().unwrap() } -/// Draws a primitive on the internal buffer. pub fn with_screen R>(f: F) -> R { SCREEN.with_locked(f) } diff --git a/userspace/bashkar/src/video/tty.rs b/userspace/bashkar/src/video/tty.rs index f839ee3..a990e79 100644 --- a/userspace/bashkar/src/video/tty.rs +++ b/userspace/bashkar/src/video/tty.rs @@ -24,6 +24,8 @@ pub struct Tty { input_buffer: String, /// Current cursor position cursor_pos: usize, + // Keyboard modifiers + modifiers: keyboard::KeyModifiers, } impl Default for Tty { @@ -57,6 +59,7 @@ impl Tty { writer: FramebufferWriter::new(new_info), input_buffer: String::new(), cursor_pos: 0, + modifiers: keyboard::KeyModifiers::new(), } } @@ -136,7 +139,18 @@ impl Tty { let key = event.key(); let pressed = event.pressed(); - if pressed != KeyState::Pressed { + if pressed != KeyState::Pressed + // Modifiers keys still need to be handled when released + && !matches!( + key, + KeyCode::ShiftLeft + | KeyCode::ShiftRight + | KeyCode::CtrlLeft + | KeyCode::CtrlRight + | KeyCode::AltLeft + | KeyCode::AltRight + ) + { return false; } @@ -165,8 +179,25 @@ impl Tty { }); true } + KeyCode::CapsLock => { + self.modifiers + .set_caps_locked(!self.modifiers.is_caps_locked()); + false + } + KeyCode::ShiftLeft | KeyCode::ShiftRight => { + self.modifiers.set_shifted(pressed == KeyState::Pressed); + false + } + KeyCode::CtrlLeft | KeyCode::CtrlRight => { + self.modifiers.set_ctrled(pressed == KeyState::Pressed); + false + } + KeyCode::AltLeft | KeyCode::AltRight => { + self.modifiers.set_alted(pressed == KeyState::Pressed); + false + } k => { - let c = k.as_char(); + let c = k.as_char(self.modifiers); if c != '\0' { self.input_buffer.insert(self.cursor_pos, c); self.cursor_pos += 1; diff --git a/userspace/doom/.gitignore b/userspace/doom/.gitignore new file mode 100644 index 0000000..050eecb --- /dev/null +++ b/userspace/doom/.gitignore @@ -0,0 +1 @@ +/DOOM \ No newline at end of file diff --git a/userspace/doom/Cargo.toml b/userspace/doom/Cargo.toml new file mode 100644 index 0000000..66246bf --- /dev/null +++ b/userspace/doom/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "doom" +version = "0.1.0" +edition = "2024" + +[dependencies] +beskar-core = { workspace = true } +beskar-lib = { workspace = true } +hyperdrive = { workspace = true} + +[build-dependencies] +cc = "1.2.38" diff --git a/userspace/doom/README.md b/userspace/doom/README.md new file mode 100644 index 0000000..d74a47f --- /dev/null +++ b/userspace/doom/README.md @@ -0,0 +1,38 @@ +# DOOM + +Play the original, shareware, version of the great DOOM directly on BeskarOS ! + +## Requirements + +- clang +- [PureDOOM](https://github.com/Daivuk/PureDOOM) + +## Getting started + +Place the `src/DOOM` folder as well as the WAD file of [PureDOOM](https://github.com/Daivuk/PureDOOM) in this folder so your filetree looks like : + +``` +doom/ + DOOM/ + *.c + doom1.wad + src/ + main.rs +``` + +If compilation fails or the program crashes, try using the commit hash [48376dd](https://github.com/Daivuk/PureDOOM/tree/48376ddd6bbdb70085dab91feb1c6ceef80fa9b7). + +Finally, you will have to edit the root `build.rs`/`Cargo.toml` to add doom as a dependency and edit the ramdisk accordingly (temporary). + +## Usage + +Default bindings are: + +- Right: Right arrow +- Left: Left arrow +- Forward: Up arrow +- Backward: Down arrow +- Shoot: Control +- Strafe: Alt +- Run: Shift +- Interact/Use: Space diff --git a/userspace/doom/build.rs b/userspace/doom/build.rs new file mode 100644 index 0000000..347abed --- /dev/null +++ b/userspace/doom/build.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +fn main() -> Result<(), Box> { + let srcd = PathBuf::from("DOOM"); + let mut c_files = Vec::new(); + + for entry in std::fs::read_dir(&srcd)? { + let entry = entry?; + if let Some(filename) = entry.file_name().to_str() + && filename.ends_with(".c") + { + println!("cargo:rerun-if-changed={}", entry.path().display()); + c_files.push(srcd.join(filename)); + } + } + + cc::Build::new() + .compiler("clang.exe") + .files(&c_files) + .target("x86_64-unknown-none") + .flag("-ffreestanding") + .flag("-nostdlib") + .flag("-fno-builtin") + .flag("-fno-stack-protector") + .flag("-mno-red-zone") + .flag("-fPIC") + // suppress warnings from clang + .flag("-w") + // compile without simd + .flag("-mgeneral-regs-only") + .compile("puredoom"); + + Ok(()) +} diff --git a/userspace/doom/src/game.rs b/userspace/doom/src/game.rs new file mode 100644 index 0000000..eaa8620 --- /dev/null +++ b/userspace/doom/src/game.rs @@ -0,0 +1,190 @@ +use core::{ + ffi::{c_char, c_void}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +#[link(name = "puredoom", kind = "static")] +unsafe extern "C" { + unsafe fn doom_set_print(f: extern "C" fn(*const c_char)); + unsafe fn doom_set_malloc( + malloc: extern "C" fn(i32) -> *mut c_void, + free: extern "C" fn(*mut c_void), + ); + unsafe fn doom_set_file_io( + open: extern "C" fn(*const c_char, *const c_char) -> *const c_void, + close: extern "C" fn(*const c_void), + read: extern "C" fn(*const c_void, *mut c_void, i32) -> i32, + write: extern "C" fn(*const c_void, *const c_void, i32) -> i32, + seek: extern "C" fn(*const c_void, i32, DoomSeekT) -> i32, + tell: extern "C" fn(*const c_void) -> i32, + eof: extern "C" fn(*const c_void) -> i32, + ); + unsafe fn doom_set_gettime(f: extern "C" fn(*mut i32, *mut i32)); + unsafe fn doom_set_exit(f: extern "C" fn(i32)); + unsafe fn doom_set_getenv(f: extern "C" fn(*const c_char) -> *const c_char); +} + +pub fn init() { + unsafe { doom_set_print(print) }; + unsafe { doom_set_malloc(malloc, free) }; + unsafe { doom_set_file_io(open, close, read, write, seek, tell, eof) }; + unsafe { doom_set_gettime(gettime) }; + unsafe { doom_set_exit(exit) }; + unsafe { doom_set_getenv(getenv) }; +} + +extern "C" fn print(s: *const c_char) { + let s = unsafe { core::ffi::CStr::from_ptr(s) }; + if let Ok(s) = s.to_str() { + beskar_lib::println!("DOOM: {}", s); + } +} + +extern "C" fn malloc(size: i32) -> *mut c_void { + if size == 0 { + // `alloc` states the the layout size must be non-zero. + return core::ptr::dangling_mut(); + } + + let size = usize::try_from(size).unwrap(); + let layout = + core::alloc::Layout::from_size_align(size, core::mem::align_of::<*const ()>()).unwrap(); + + let ptr = unsafe { alloc::alloc::alloc(layout) }; + ptr.cast() +} + +const extern "C" fn free(_ptr: *mut c_void) { + // TODO: keep track of allocations to get the layout! +} + +extern "C" fn exit(code: i32) { + let code = if code == 0 { + beskar_lib::ExitCode::Success + } else { + beskar_lib::ExitCode::Failure + }; + beskar_lib::exit(code); +} + +extern "C" fn gettime(sec: *mut i32, usec: *mut i32) { + let now = beskar_lib::time::now(); + unsafe { + *sec = i32::try_from(now.secs()).unwrap(); + *usec = i32::try_from(now.micros()).unwrap(); + } +} + +extern "C" fn getenv(name: *const c_char) -> *const c_char { + let name = unsafe { core::ffi::CStr::from_ptr(name) }; + + if let Ok(name) = name.to_str() + && name == "HOME" + { + let dir = c"/"; + return dir.as_ptr(); + } + + core::ptr::null() +} + +const WAD_HANDLE: *const c_void = 1 as _; + +static WAD_POS: AtomicUsize = AtomicUsize::new(0); +static WAD: &[u8] = include_bytes!("../DOOM/doom1.wad"); + +extern "C" fn open(filename: *const c_char, _mode: *const c_char) -> *const c_void { + let filename = unsafe { core::ffi::CStr::from_ptr(filename) }; + + if let Ok(filename) = filename.to_str() + && filename == "./doom1.wad" + { + return WAD_HANDLE; + } + + core::ptr::null() +} + +extern "C" fn close(handle: *const c_void) { + assert_eq!(handle, WAD_HANDLE); +} + +extern "C" fn read(handle: *const c_void, buf: *mut c_void, len: i32) -> i32 { + if handle == WAD_HANDLE { + let length = usize::try_from(len).unwrap(); + let start_offset = WAD_POS.fetch_add(length, Ordering::Relaxed); + + if start_offset + length > WAD.len() { + return -1; + } + + unsafe { + core::ptr::copy_nonoverlapping(WAD.as_ptr().byte_add(start_offset), buf.cast(), length); + } + return len; + } + -1 +} + +const extern "C" fn write(_handle: *const c_void, _buf: *const c_void, _len: i32) -> i32 { + -1 +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +#[allow(dead_code)] +enum DoomSeekT { + Set = 0, + Cur = 1, + End = 2, +} + +extern "C" fn seek(handle: *const c_void, offset: i32, whence: DoomSeekT) -> i32 { + if handle != WAD_HANDLE { + return -1; + } + + match whence { + DoomSeekT::Set => { + WAD_POS.store(usize::try_from(offset).unwrap(), Ordering::Relaxed); + 0 + } + DoomSeekT::Cur => { + let res = WAD_POS.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |pos| { + Some( + usize::try_from( + isize::try_from(pos).unwrap() + isize::try_from(offset).unwrap(), + ) + .unwrap(), + ) + }); + match res { + Ok(_) => 0, + Err(_) => -1, + } + } + DoomSeekT::End => { + let end = isize::try_from(WAD.len()).unwrap(); + let pos = (end + isize::try_from(offset).unwrap()).clamp(0, end); + WAD_POS.store(usize::try_from(pos).unwrap(), Ordering::Relaxed); + 0 + } + } +} + +extern "C" fn tell(handle: *const c_void) -> i32 { + if handle != WAD_HANDLE { + return -1; + } + + i32::try_from(WAD_POS.load(Ordering::Acquire)).unwrap() +} + +extern "C" fn eof(handle: *const c_void) -> i32 { + if handle != WAD_HANDLE { + return -1; + } + + let pos = WAD_POS.load(Ordering::Acquire); + i32::from(pos >= WAD.len()) +} diff --git a/userspace/doom/src/input.rs b/userspace/doom/src/input.rs new file mode 100644 index 0000000..751e629 --- /dev/null +++ b/userspace/doom/src/input.rs @@ -0,0 +1,174 @@ +use beskar_lib::io::keyboard::{KeyCode, KeyState}; + +#[link(name = "puredoom", kind = "static")] +unsafe extern "C" { + unsafe fn doom_key_down(key: DoomKeyT); + unsafe fn doom_key_up(key: DoomKeyT); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +#[allow(dead_code)] +enum DoomKeyT { + Unknown = -1, + Tab = 9, + Enter = 13, + Escape = 27, + Space = 32, + Apostrophe = b'\'' as _, + Multiply = b'*' as _, + Comma = b',' as _, + Minus = 0x2D, + Period = b'.' as _, + Slash = b'/' as _, + Zero = b'0' as _, + One = b'1' as _, + Two = b'2' as _, + Three = b'3' as _, + Four = b'4' as _, + Five = b'5' as _, + Six = b'6' as _, + Seven = b'7' as _, + Eight = b'8' as _, + Nine = b'9' as _, + Semicolon = b';' as _, + Equals = b'=' as _, + LeftBracket = b'[' as _, + RightBracket = b']' as _, + A = b'a' as _, + B = b'b' as _, + C = b'c' as _, + D = b'd' as _, + E = b'e' as _, + F = b'f' as _, + G = b'g' as _, + H = b'h' as _, + I = b'i' as _, + J = b'j' as _, + K = b'k' as _, + L = b'l' as _, + M = b'm' as _, + N = b'n' as _, + O = b'o' as _, + P = b'p' as _, + Q = b'q' as _, + R = b'r' as _, + S = b's' as _, + T = b't' as _, + U = b'u' as _, + V = b'v' as _, + W = b'w' as _, + X = b'x' as _, + Y = b'y' as _, + Z = b'z' as _, + Backspace = 127, + Ctrl = 0x80 + 0x1D, + LeftArrow = 0xAC, + UpArrow = 0xAD, + RightArrow = 0xAE, + DownArrow = 0xAF, + Shift = 0x80 + 0x36, + Alt = 0x80 + 0x38, + F1 = 0x80 + 0x3B, + F2 = 0x80 + 0x3C, + F3 = 0x80 + 0x3D, + F4 = 0x80 + 0x3E, + F5 = 0x80 + 0x3F, + F6 = 0x80 + 0x40, + F7 = 0x80 + 0x41, + F8 = 0x80 + 0x42, + F9 = 0x80 + 0x43, + F10 = 0x80 + 0x44, + F11 = 0x80 + 0x57, + F12 = 0x80 + 0x58, + Pause = 0xFF, +} + +impl From for DoomKeyT { + fn from(key: KeyCode) -> Self { + match key { + KeyCode::Tab => Self::Tab, + KeyCode::Enter => Self::Enter, + KeyCode::Escape => Self::Escape, + KeyCode::Space => Self::Space, + KeyCode::Apostrophe => Self::Apostrophe, + KeyCode::NumpadMul => Self::Multiply, + KeyCode::Comma => Self::Comma, + KeyCode::Minus => Self::Minus, + // KeyCode::Period => Self::Period, + KeyCode::Slash => Self::Slash, + KeyCode::Num0 | KeyCode::Numpad0 => Self::Zero, + KeyCode::Num1 | KeyCode::Numpad1 => Self::One, + KeyCode::Num2 | KeyCode::Numpad2 => Self::Two, + KeyCode::Num3 | KeyCode::Numpad3 => Self::Three, + KeyCode::Num4 | KeyCode::Numpad4 => Self::Four, + KeyCode::Num5 | KeyCode::Numpad5 => Self::Five, + KeyCode::Num6 | KeyCode::Numpad6 => Self::Six, + KeyCode::Num7 | KeyCode::Numpad7 => Self::Seven, + KeyCode::Num8 | KeyCode::Numpad8 => Self::Eight, + KeyCode::Num9 | KeyCode::Numpad9 => Self::Nine, + KeyCode::Semicolon => Self::Semicolon, + // KeyCode::Equals => Self::Equals, + KeyCode::LeftBracket => Self::LeftBracket, + KeyCode::RightBracket => Self::RightBracket, + KeyCode::A => Self::A, + KeyCode::B => Self::B, + KeyCode::C => Self::C, + KeyCode::D => Self::D, + KeyCode::E => Self::E, + KeyCode::F => Self::F, + KeyCode::G => Self::G, + KeyCode::H => Self::H, + KeyCode::I => Self::I, + KeyCode::J => Self::J, + KeyCode::K => Self::K, + KeyCode::L => Self::L, + KeyCode::M => Self::M, + KeyCode::N => Self::N, + KeyCode::O => Self::O, + KeyCode::P => Self::P, + KeyCode::Q => Self::Q, + KeyCode::R => Self::R, + KeyCode::S => Self::S, + KeyCode::T => Self::T, + KeyCode::U => Self::U, + KeyCode::V => Self::V, + KeyCode::W => Self::W, + KeyCode::X => Self::X, + KeyCode::Y => Self::Y, + KeyCode::Z => Self::Z, + KeyCode::Backspace => Self::Backspace, + KeyCode::CtrlLeft | KeyCode::CtrlRight => Self::Ctrl, + KeyCode::ArrowLeft => Self::LeftArrow, + KeyCode::ArrowUp => Self::UpArrow, + KeyCode::ArrowRight => Self::RightArrow, + KeyCode::ArrowDown => Self::DownArrow, + KeyCode::ShiftLeft | KeyCode::ShiftRight => Self::Shift, + KeyCode::AltLeft | KeyCode::AltRight => Self::Alt, + KeyCode::F1 => Self::F1, + KeyCode::F2 => Self::F2, + KeyCode::F3 => Self::F3, + KeyCode::F4 => Self::F4, + KeyCode::F5 => Self::F5, + KeyCode::F6 => Self::F6, + KeyCode::F7 => Self::F7, + KeyCode::F8 => Self::F8, + KeyCode::F9 => Self::F9, + KeyCode::F10 => Self::F10, + KeyCode::F11 => Self::F11, + KeyCode::F12 => Self::F12, + KeyCode::PauseBreak => Self::Pause, + _ => Self::Unknown, + } + } +} + +pub fn poll_inputs() { + while let Some(event) = beskar_lib::io::keyboard::poll_keyboard() { + let doom_key = DoomKeyT::from(event.key()); + match event.pressed() { + KeyState::Pressed => unsafe { doom_key_down(doom_key) }, + KeyState::Released => unsafe { doom_key_up(doom_key) }, + } + } +} diff --git a/userspace/doom/src/lib.rs b/userspace/doom/src/lib.rs new file mode 100644 index 0000000..839ec98 --- /dev/null +++ b/userspace/doom/src/lib.rs @@ -0,0 +1,8 @@ +#![no_std] +#![forbid(unsafe_op_in_unsafe_fn)] +#![warn(clippy::pedantic, clippy::nursery)] +extern crate alloc; + +pub mod game; +pub mod input; +pub mod screen; diff --git a/userspace/doom/src/main.rs b/userspace/doom/src/main.rs new file mode 100644 index 0000000..de7dd23 --- /dev/null +++ b/userspace/doom/src/main.rs @@ -0,0 +1,29 @@ +#![no_std] +#![forbid(unsafe_op_in_unsafe_fn)] +#![warn(clippy::pedantic, clippy::nursery)] +#![no_main] +use core::ffi::c_char; + +#[link(name = "puredoom", kind = "static")] +unsafe extern "C" { + unsafe fn doom_init(argc: i32, argv: *const *const c_char, flags: i32); + unsafe fn doom_update(); +} + +beskar_lib::entry_point!(main); + +fn main() { + let ex_name = c"doom"; + let argv = [ex_name.as_ptr()]; + + doom::game::init(); + doom::screen::init(); + + unsafe { doom_init(1, argv.as_ptr(), 0b111) }; + + loop { + unsafe { doom_update() }; + doom::screen::draw(); + doom::input::poll_inputs(); + } +} diff --git a/userspace/doom/src/screen.rs b/userspace/doom/src/screen.rs new file mode 100644 index 0000000..84e2f01 --- /dev/null +++ b/userspace/doom/src/screen.rs @@ -0,0 +1,148 @@ +use beskar_core::video::Info; +use core::{mem::MaybeUninit, num::NonZeroU64, ops::Range}; +use hyperdrive::{locks::mcs::MUMcsLock, once::Once}; + +const SCREENWIDTH: usize = 320; +const SCREENHEIGHT: usize = 200; + +static SCREEN: MUMcsLock = MUMcsLock::uninit(); +static SCREEN_INFO: Once = Once::uninit(); + +const FB_FILE: &str = "/dev/fb"; + +#[link(name = "puredoom", kind = "static")] +unsafe extern "C" { + unsafe fn doom_get_framebuffer(channel: i32) -> *const u8; +} + +pub fn init() { + SCREEN.init(Screen::new()); + SCREEN_INFO.call_once(|| SCREEN.with_locked(|screen| screen.info)); +} + +fn read_screen_info() -> Info { + let mut info = MaybeUninit::::uninit(); + + let info_buff = unsafe { + core::slice::from_raw_parts_mut(info.as_mut_ptr().cast::(), size_of::()) + }; + + let fb_file = beskar_lib::io::File::open(FB_FILE).unwrap(); + + fb_file.read(info_buff, 0).unwrap(); + + // Safety: We just initialized the memory with a valid Info struct. + unsafe { info.assume_init() } +} + +struct Screen { + info: Info, + fb_file: beskar_lib::io::File, + internal_fb: &'static mut [u8], +} + +impl Default for Screen { + fn default() -> Self { + Self::new() + } +} + +impl Screen { + #[must_use] + /// # Panics + /// + /// Panics if the framebuffer file cannot be opened. + pub fn new() -> Self { + let info = read_screen_info(); + + let fb_file = beskar_lib::io::File::open(FB_FILE).unwrap(); + + let internal_fb_start = beskar_lib::mem::mmap( + u64::from(info.size()), + Some(NonZeroU64::new(align_of::().try_into().unwrap()).unwrap()), + ); + let internal_fb = unsafe { + core::slice::from_raw_parts_mut( + internal_fb_start.as_ptr(), + usize::try_from(info.size()).unwrap(), + ) + }; + + // Clear the internal framebuffer + { + let (prefix, large, suffix) = unsafe { internal_fb.align_to_mut::() }; + + prefix.fill(0); + large.fill(0); + suffix.fill(0); + } + + Self { + info, + fb_file, + internal_fb, + } + } + + #[inline] + #[expect(clippy::needless_pass_by_value, reason = "This is ugly otherwise")] + pub fn flush(&self, rows: Option>) { + let stride = usize::from(self.info.stride()); + let max_row = usize::from(self.info.height()); + let bpp = usize::from(self.info.bytes_per_pixel()); + + let offset_in_screen = rows + .as_ref() + .map_or(0, |r| usize::from(r.start) * stride) + .min(max_row * stride); + + let offset = offset_in_screen * bpp; + + let end = rows + .as_ref() + .map_or_else( + || usize::try_from(self.info.size()).unwrap(), + |r| usize::from(r.end) * stride * bpp, + ) + .min(max_row * stride * bpp); + + self.fb_file + .write(&self.internal_fb[offset..end], offset_in_screen) + .unwrap(); + } + + #[must_use] + #[inline] + pub const fn buffer_mut(&mut self) -> &mut [u8] { + self.internal_fb + } +} + +/// Returns the screen info. +/// +/// # Panics +/// +/// Panics if the screen info has not been initialized yet. +fn screen_info() -> &'static Info { + SCREEN_INFO.get().unwrap() +} + +fn with_screen R>(f: F) -> R { + SCREEN.with_locked(f) +} + +#[expect(clippy::missing_panics_doc, reason = "Never panics")] +pub fn draw() { + let fb_start = unsafe { doom_get_framebuffer(4) }; + let fb = unsafe { core::slice::from_raw_parts(fb_start, SCREENWIDTH * SCREENHEIGHT * 4) }; + + let stride = usize::from(screen_info().stride()); + with_screen(|screen| { + let mut buffer_mut = screen.buffer_mut(); + for row in fb.chunks_exact(SCREENWIDTH * 4) { + buffer_mut[..SCREENWIDTH * 4].copy_from_slice(row); + buffer_mut = &mut buffer_mut[stride * 4..]; + } + screen.flush(Some(0..u16::try_from(SCREENHEIGHT).unwrap())); + }); +}