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/.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/Cargo.lock b/Cargo.lock index 985c454..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.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +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.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +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" @@ -152,18 +178,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", @@ -183,9 +210,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -219,6 +246,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +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" @@ -230,9 +269,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -241,18 +280,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -313,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 2b4b81d..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,10 +27,12 @@ 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" -lto = true +lto = "fat" +strip = "symbols" [profile.dev] 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/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/arch/addrs.rs b/beskar-core/src/arch/addrs.rs index 2d995dd..a28c7ea 100644 --- a/beskar-core/src/arch/addrs.rs +++ b/beskar-core/src/arch/addrs.rs @@ -12,7 +12,11 @@ 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] + #[track_caller] #[inline] pub const fn new(addr: u64) -> Self { Self::try_new(addr).expect("Invalid virtual address") @@ -50,6 +54,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,9 +156,11 @@ 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] + #[track_caller] #[inline] pub const fn new(addr: u64) -> Self { Self::try_new(addr).expect("Invalid physical address") @@ -153,7 +179,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 +332,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-core/src/drivers/keyboard.rs b/beskar-core/src/drivers/keyboard.rs index ed49143..0e46eb4 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 { @@ -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] @@ -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 { @@ -52,18 +43,13 @@ 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::try_from(u8::try_from(value & 0xFF).unwrap()).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 }) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum KeyCode { A, @@ -178,13 +164,38 @@ pub enum KeyCode { WindowsLeft, WindowsRight, + #[num_enum(default)] Unknown, } 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', @@ -212,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 } } } @@ -224,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-core/src/syscall.rs b/beskar-core/src/syscall.rs index d1f534f..3bb6f77 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,10 +65,12 @@ pub enum SyscallExitCode { Failure = 1, /// Any other (invalid) exit code. + #[num_enum(default)] Other, } impl SyscallExitCode { + #[track_caller] #[inline] /// Unwraps the syscall exit code, panicking if it is a failure. /// @@ -75,7 +78,11 @@ 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-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/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-hal/src/x86_64/instructions.rs b/beskar-hal/src/x86_64/instructions.rs index efe5476..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)); } } @@ -34,5 +34,38 @@ 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)); + } +} + +#[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/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-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/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.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 2e5bb9f..7a0bbbe 100644 --- a/beskar-lib/src/io/keyboard.rs +++ b/beskar-lib/src/io/keyboard.rs @@ -1,5 +1,8 @@ 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::()]); #[must_use] #[inline] @@ -13,15 +16,15 @@ 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 file = File::open(KEYBOARD_FILE).ok()?; - 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 86e82af..0283633 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -7,14 +7,17 @@ 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; pub mod mem; pub mod rand; +pub mod time; #[panic_handler] -fn panic(_info: &::core::panic::PanicInfo) -> ! { +fn panic(info: &::core::panic::PanicInfo) -> ! { + println!("Panic occurred: {}", info); exit(ExitCode::Failure); } @@ -33,21 +36,22 @@ 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] /// Sets the entry point for the program. macro_rules! entry_point { ($path:path) => { + #[macro_use] extern crate alloc; #[unsafe(export_name = "_start")] - pub extern "C" fn __program_entry() { - unsafe { $crate::__init() }; + /// # Safety + /// + /// Do not call this function. + unsafe extern "C" fn __program_entry() { + $crate::__init(); ($path)(); $crate::exit($crate::ExitCode::Success); } @@ -55,12 +59,14 @@ 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()) }; + + 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/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/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/bootloader/bootloader-api/src/lib.rs b/bootloader/bootloader-api/src/lib.rs index c2cdcd7..e5d4eac 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) + extern "C" fn __kernel_entry(boot_info: &'static mut $crate::BootInfo) -> ! { + ($path)(boot_info $(, $arg)*) } }; } 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/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/build.rs b/build.rs index 6b30136..68db698 100644 --- a/build.rs +++ b/build.rs @@ -1,29 +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-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(); @@ -35,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"); + let bashkar = var("CARGO_BIN_FILE_BASHKAR").unwrap(); + fs::copy(&bashkar, "efi_disk/efi/ramdisk.img").expect("Failed to copy bashkar"); + // 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 - let hello_world = var("CARGO_BIN_FILE_BASHKAR").unwrap(); - fs::copy(&hello_world, "efi_disk/efi/ramdisk.img").expect("Failed to copy userspace"); - 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 - } + // 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/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/hyperdrive/src/locks/mcs.rs b/hyperdrive/src/locks/mcs.rs index e52dcfe..56d1508 100644 --- a/hyperdrive/src/locks/mcs.rs +++ b/hyperdrive/src/locks/mcs.rs @@ -195,7 +195,7 @@ 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 @@ -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(move || 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 5694e62..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)] @@ -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 } } @@ -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 44de507..de8ffab 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; @@ -67,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. @@ -93,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. @@ -119,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/once.rs b/hyperdrive/src/once.rs index 81885c7..81269ef 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 @@ -47,12 +51,11 @@ 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, + Initialized = 0, Initializing, - Initialized, + Uninitialized, Poisoned, } @@ -60,11 +63,11 @@ 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::Uninitialized => 0, + Self::Initialized => 0, Self::Initializing => 1, - Self::Initialized => 2, + Self::Uninitialized => 2, Self::Poisoned => 3, } } @@ -73,11 +76,11 @@ 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::Uninitialized), + 0 => Some(Self::Initialized), 1 => Some(Self::Initializing), - 2 => Some(Self::Initialized), + 2 => Some(Self::Uninitialized), 3 => Some(Self::Poisoned), _ => None, } @@ -90,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() } } } @@ -102,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] @@ -234,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: @@ -258,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(), } } } @@ -410,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/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.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..f9422ad --- /dev/null +++ b/hyperdrive/src/queues/mpmc.rs @@ -0,0 +1,381 @@ +//! 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()), + } + } + + #[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 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); + } + + #[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) -> 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. +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. + unsafe { + slot.write(value, pos); + }; + break Ok(()); // Successfully pushed the value + } + Err(current) => { + pos = current; // Retry with the current position + } + } + } + core::cmp::Ordering::Less => { + break Err(MpmcQueueFullError(value)); // Queue is full + } + core::cmp::Ordering::Greater => { + pos = self.write_index.load(Ordering::Relaxed); // Retry + } + } + } + } + + #[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 accessing this slot. + let value = unsafe { slot.read(pos) }; + break Some(value); // Successfully pushed the value + } + Err(current) => { + pos = current; // Retry with the current position + } + } + } + core::cmp::Ordering::Less => { + break 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)); + + let popped1 = mpmc.pop().unwrap(); + let popped2 = mpmc.pop().unwrap(); + + assert_eq!(*popped1, 1); + assert_eq!(*popped2, 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 b48890d..cc5749d 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() //! # } //! # } //! # @@ -107,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. /// @@ -145,6 +147,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, @@ -162,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 {} @@ -179,6 +192,8 @@ pub enum DequeueResult { impl DequeueResult { #[must_use] + #[track_caller] + #[inline] /// Unwraps the result. /// /// ## Panics @@ -193,6 +208,7 @@ impl DequeueResult { } #[must_use] + #[inline] /// Unwraps the result without checking its value. /// /// ## Safety @@ -214,6 +230,7 @@ impl Default for MpscQueue where T::Handle: Default, { + #[inline] fn default() -> Self { Self::new(T::Handle::default()) } @@ -221,6 +238,7 @@ where impl MpscQueue { #[must_use] + #[inline] pub fn new(stub: T::Handle) -> Self { let stub_ptr = ::release(stub); Self { @@ -228,6 +246,7 @@ impl MpscQueue { tail: AtomicPtr::new(stub_ptr.as_ptr()), being_dequeued: AtomicBool::new(false), stub: stub_ptr, + _marker: core::marker::PhantomData, } } @@ -383,9 +402,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 +419,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() } } 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/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/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/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/foundry/storage/src/fs.rs b/kernel/foundry/storage/src/fs.rs index 95ba081..0df1769 100644 --- a/kernel/foundry/storage/src/fs.rs +++ b/kernel/foundry/storage/src/fs.rs @@ -4,8 +4,9 @@ use thiserror::Error; pub mod dev; pub mod ext2; pub mod fat; +pub mod in_mem; -#[derive(Debug, Error)] +#[derive(Debug, Error, Clone, Copy, Eq, PartialEq)] pub enum FileError { #[error("I/O error")] Io, @@ -102,7 +103,7 @@ impl PathBuf { #[must_use] #[inline] - pub fn as_path(&self) -> Path { + pub fn as_path(&self) -> Path<'_> { Path(&self.0) } } @@ -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/foundry/storage/src/vfs.rs b/kernel/foundry/storage/src/vfs.rs index b0a3fa0..e9de0cc 100644 --- a/kernel/foundry/storage/src/vfs.rs +++ b/kernel/foundry/storage/src/vfs.rs @@ -197,12 +197,16 @@ 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) { - let mut open_handles = self.open_handles.write(); - open_handles.retain(|_handle, open_file| open_file.process_id != pid); + 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 { + 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/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..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 @@ -120,8 +124,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/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/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/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/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/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index d1e2ba0..08ce393 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -1,10 +1,15 @@ use super::gdt::{DOUBLE_FAULT_IST, PAGE_FAULT_IST}; use crate::locals; +use beskar_core::arch::VirtAddr; use beskar_hal::{ + 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(); @@ -59,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(); @@ -125,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), @@ -139,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), @@ -196,20 +203,6 @@ 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)); - } -} - #[inline] /// Allocates a new IRQ handler in the IDT and return its index. /// @@ -226,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/arch/x86_64/syscall.rs b/kernel/src/arch/x86_64/syscall.rs index 88048a7..eac056c 100644 --- a/kernel/src/arch/x86_64/syscall.rs +++ b/kernel/src/arch/x86_64/syscall.rs @@ -7,6 +7,10 @@ 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, rdi: u64, @@ -57,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. @@ -96,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/boot.rs b/kernel/src/boot.rs index d2acb9a..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 @@ -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,11 +139,12 @@ 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() -> ! { - KERNEL_MAIN_FENCE.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + /// 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, 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(); } @@ -158,12 +157,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/drivers/keyboard.rs b/kernel/src/drivers/keyboard.rs index de33326..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::KeyEvent; -use core::mem::offset_of; -use hyperdrive::{ - once::Once, - queues::mpsc::{Link, MpscQueue, Queueable}, -}; +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 fn new(event: KeyEvent) -> Self { - Self { - event, - _link: Link::default(), - } - } - - #[must_use] - #[inline] - pub const fn event(&self) -> KeyEvent { - self.event - } -} - pub struct KeyboardManager { - event_queue: MpscQueue, + event_queue: MpmcQueue, } impl Default for KeyboardManager { @@ -66,19 +24,24 @@ impl KeyboardManager { #[inline] pub fn new() -> Self { Self { - event_queue: MpscQueue::new(Box::new(QueuedKeyEvent::new(KeyEvent::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/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 7b5d75d..490b367 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); @@ -24,27 +24,42 @@ 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; pub struct Ps2Controller { - data_port: McsLock>, - cmd_sts_port: McsLock>, + data_port: TicketLock>, + cmd_sts_port: TicketLock>, has_two_ports: AtomicBool, } @@ -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, @@ -77,8 +92,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 +114,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] @@ -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); } } @@ -171,25 +186,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. @@ -200,7 +214,7 @@ impl Ps2Controller { return Ok(()); } } - Err(Ps2Error::SendingFailed) + Err(Ps2Error::Sending) } /// Receive a Device to Host command from the PS/2 controller. @@ -210,7 +224,7 @@ impl Ps2Controller { return Ok(self.read_data()); } } - Err(Ps2Error::ReceivingFailed) + Err(Ps2Error::Receiving) } #[inline] @@ -221,6 +235,7 @@ impl Ps2Controller { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ScancodeSet { Set1 = 1, Set2 = 2, @@ -300,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 @@ -324,9 +339,10 @@ impl<'a> Ps2Keyboard<'a> { } #[must_use] - pub fn scancode_to_keycode(&self, scancode: u8) -> Option { + #[inline] + pub fn scancode_to_keycode(&self, extended: bool, scancode: u8) -> Option { 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, @@ -334,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 { @@ -342,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, @@ -502,8 +523,8 @@ 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 { - video::warn!("Unknown key: {:#X}", key); + let Some(key_event) = keyboard.scancode_to_keycode(extended, key) else { + video::warn!("Unknown key: {:#X} (extended: {})", key, extended); 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/lib.rs b/kernel/src/lib.rs index 4b206c7..3ba2645 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -25,19 +25,15 @@ 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); + 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::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 b5e756e..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 { @@ -28,18 +27,16 @@ 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)); - - 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/main.rs b/kernel/src/main.rs index 595622a..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,8 +55,8 @@ fn kmain() -> ! { )), )); - scheduler::spawn_thread(alloc::boxed::Box::pin(Thread::new_from_binary( - user_proc.clone(), + 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..794f81d 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() }; @@ -149,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) @@ -178,7 +176,7 @@ 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"); + video::warn!("`AddressSpace::is_addr_owned` called without PGALLOC PML4 index"); return false; }; 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/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.rs b/kernel/src/process.rs index 6aba975..a92969d 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; @@ -137,6 +136,17 @@ impl Process { } } +impl Drop for Process { + fn drop(&mut self) { + video::debug!( + "Process \"{}\" (PID: {}) is being dropped", + self.name, + self.pid.as_u64() + ); + crate::storage::vfs().close_all_from_process(self.pid.as_u64()); + } +} + struct BinaryData<'a> { input: binary::Binary<'a>, loaded: Once, @@ -180,8 +190,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() @@ -192,6 +200,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/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 7d35684..ee1d975 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -1,9 +1,10 @@ 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; @@ -52,7 +53,7 @@ pub unsafe fn init(kernel_thread: thread::Thread) { guard_thread, ); - spawn_thread(Box::pin(clean_thread)); + spawn_thread(Box::new(clean_thread)); }); } @@ -76,40 +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); - } - - #[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] @@ -118,82 +120,77 @@ 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| { - crate::arch::interrupts::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 { - crate::arch::interrupts::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, + + beskar_hal::instructions::int_disable(); + + Some(ContextSwitch { + old_stack, + new_stack, + cr3, + }) }) - })? + .flatten() } } -#[must_use] #[inline] -fn get_scheduler() -> &'static Scheduler { - locals!().scheduler().get().unwrap() +/// 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(); + f(scheduler) + }) } /// A thread should be spawned with this function. @@ -222,44 +219,46 @@ 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 { - // 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() + with_scheduler(|scheduler| { + // Safety: + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current.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() + with_scheduler(|scheduler| { + // Safety: + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current.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() + with_scheduler(|scheduler| { + // Safety: + // Interrupts are disabled, so the current thread cannot change. + unsafe { scheduler.current.force_lock() }.process() + }) } #[inline] -pub fn spawn_thread(thread: Pin>) { - QUEUE.get().unwrap().append(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. @@ -284,11 +283,6 @@ pub fn set_scheduling(enable: bool) { }); } -#[inline] -pub fn change_current_thread_priority(priority: priority::Priority) { - get_scheduler().change_current_thread_priority(priority); -} - /// Exits the current thread. /// /// This function will enable interrupts, otherwise the system would halt. @@ -298,18 +292,19 @@ 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::set_exit); // Try to reschedule the thread. thread_yield(); // If no thread is waiting, loop. - crate::arch::interrupts::int_enable(); + beskar_hal::instructions::int_enable(); loop { crate::arch::halt(); } } +#[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. @@ -340,9 +335,8 @@ 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::set_sleep); + if !thread_yield() { // TODO: What to do if the thread was not rescheduled? // Maybe push the thread in the sleeping queue and @@ -353,11 +347,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| { - QUEUE.get().unwrap().append(Pin::new(thread)); + spawn_thread(thread); true }) }) diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index a9f3864..1dfd90f 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, @@ -54,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 { @@ -92,7 +90,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 +115,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(), } } @@ -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) }; @@ -180,7 +178,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,21 +186,11 @@ impl Thread { state: ThreadState::Ready, stack: None, last_stack_ptr: core::ptr::null_mut(), - link: Link::default(), + link: Link::new(), tls: Once::uninit(), } } - /// 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. /// @@ -266,14 +254,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 { @@ -352,35 +332,33 @@ 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| { - t.stack.as_mut().map(|ts| { + let rsp = super::with_scheduler(|scheduler| { + scheduler.current.with_locked(|thread| { + thread.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(); 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(); @@ -407,12 +385,15 @@ 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.with_locked(|thread| { + thread.tls.call_once(|| tls); + }); + }); 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) }; } @@ -452,9 +433,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] @@ -497,14 +476,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/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. 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/commands.rs b/userspace/bashkar/src/commands.rs index 0265974..854418f 100644 --- a/userspace/bashkar/src/commands.rs +++ b/userspace/bashkar/src/commands.rs @@ -25,20 +25,18 @@ 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), } } /// 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) } @@ -60,15 +58,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/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); diff --git a/userspace/bashkar/src/video/screen.rs b/userspace/bashkar/src/video/screen.rs index 3d342b9..d276153 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. @@ -128,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())); + }); +}