diff --git a/Cargo.lock b/Cargo.lock index 7ce0163..fada7f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "driver-api", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "ascii-ui" version = "0.1.0" @@ -146,12 +152,35 @@ dependencies = [ "xmas-elf", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "find-msvc-tools" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heaperion" version = "0.1.0" @@ -286,6 +315,7 @@ name = "storage" version = "0.1.0" dependencies = [ "beskar-core", + "hashbrown", "hyperdrive", "thiserror", ] diff --git a/beskar-core/src/drivers/keyboard.rs b/beskar-core/src/drivers/keyboard.rs index b355fb5..38a47b0 100644 --- a/beskar-core/src/drivers/keyboard.rs +++ b/beskar-core/src/drivers/keyboard.rs @@ -230,6 +230,17 @@ impl KeyCode { Self::Numpad7 => '7', Self::Numpad8 => '8', Self::Numpad9 => '9', + Self::Comma => ',', + Self::Dot => '.', + Self::Minus => '-', + Self::Equal => '=', + Self::Semicolon => ';', + Self::Apostrophe => '\'', + Self::Slash => '/', + Self::Backslash => '\\', + Self::Tilde => '~', + Self::LeftBracket => '[', + Self::RightBracket => ']', _ => '\0', }; diff --git a/beskar-core/src/syscall.rs b/beskar-core/src/syscall.rs index 035eaaf..aec7214 100644 --- a/beskar-core/src/syscall.rs +++ b/beskar-core/src/syscall.rs @@ -46,6 +46,13 @@ pub enum Syscall { /// The second argument is the alignment of the memory. /// The third argument is the protection flags of the memory. MemoryMap = 5, + /// MemoryUnmap syscall. + /// + /// Frees previously allocated memory. + /// + /// The first argument is the pointer to the memory region. + /// The second argument is the size of the memory region. + MemoryUnmap = 6, /// MemoryProtect syscall. /// /// Changes the protection of a memory region. @@ -53,15 +60,15 @@ pub enum Syscall { /// The first argument is the pointer to the memory region. /// The second argument is the size of the memory region. /// The third argument is the new protection flags. - MemoryProtect = 6, + MemoryProtect = 7, /// Put the thread to sleep for a given amount of time. /// /// The first argument is the time to sleep in milliseconds. - Sleep = 7, + Sleep = 8, /// Put the thread to sleep until a given event is signalled. /// /// The first argument is the sleep handle to wait on. - WaitOnEvent = 8, + WaitOnEvent = 9, } #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] diff --git a/beskar-core/src/video.rs b/beskar-core/src/video.rs index 2854a69..6f5a6d6 100644 --- a/beskar-core/src/video.rs +++ b/beskar-core/src/video.rs @@ -155,7 +155,7 @@ impl Pixel { #[must_use] #[inline] - fn new_rgb(components: PixelComponents) -> Self { + pub fn new_rgb(components: PixelComponents) -> Self { Self( ((u32::from(components.blue)) << 16) | ((u32::from(components.green)) << 8) @@ -165,7 +165,7 @@ impl Pixel { #[must_use] #[inline] - fn new_bgr(components: PixelComponents) -> Self { + pub fn new_bgr(components: PixelComponents) -> Self { Self( ((u32::from(components.red)) << 16) | ((u32::from(components.green)) << 8) @@ -185,7 +185,7 @@ impl Pixel { #[must_use] #[inline] - fn components_bgr(self) -> PixelComponents { + pub fn components_bgr(self) -> PixelComponents { let red = u8::try_from((self.0 >> 16) & 0xFF).unwrap(); let green = u8::try_from((self.0 >> 8) & 0xFF).unwrap(); let blue = u8::try_from(self.0 & 0xFF).unwrap(); @@ -194,7 +194,7 @@ impl Pixel { #[must_use] #[inline] - fn components_rgb(self) -> PixelComponents { + pub fn components_rgb(self) -> PixelComponents { let blue = u8::try_from((self.0 >> 16) & 0xFF).unwrap(); let green = u8::try_from((self.0 >> 8) & 0xFF).unwrap(); let red = u8::try_from(self.0 & 0xFF).unwrap(); diff --git a/beskar-hal/src/x86_64/instructions.rs b/beskar-hal/src/x86_64/instructions.rs index 5cb794f..e324e56 100644 --- a/beskar-hal/src/x86_64/instructions.rs +++ b/beskar-hal/src/x86_64/instructions.rs @@ -67,5 +67,37 @@ where } } +#[inline] +/// Initialize the FPU +pub unsafe fn fpu_init() { + unsafe { + core::arch::asm!("fninit", options(nomem, nostack, preserves_flags)); + } +} + +#[inline] +/// Save the FPU state +pub unsafe fn fpu_save(dst: &mut super::structures::SseSave) { + unsafe { + core::arch::asm!( + "fxsave [{}]", + in(reg) dst, + options(nostack, preserves_flags) + ); + } +} + +#[inline] +/// Restore the FPU state +pub unsafe fn fpu_restore(src: &super::structures::SseSave) { + unsafe { + core::arch::asm!( + "fxrstor [{}]", + in(reg) src, + options(readonly, nostack, preserves_flags) + ); + } +} + /// 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/registers.rs b/beskar-hal/src/x86_64/registers.rs index 4c0137f..a5d5951 100644 --- a/beskar-hal/src/x86_64/registers.rs +++ b/beskar-hal/src/x86_64/registers.rs @@ -3,6 +3,8 @@ use beskar_core::arch::{PhysAddr, VirtAddr, paging::Frame}; pub struct Cr0; impl Cr0 { + pub const MONITOR_COPROCESSOR: u64 = 1 << 1; + pub const EMULATE_COPROCESSOR: u64 = 1 << 2; pub const TASK_SWITCHED: u64 = 1 << 3; pub const WRITE_PROTECT: u64 = 1 << 16; pub const CACHE_DISABLE: u64 = 1 << 30; @@ -28,6 +30,14 @@ impl Cr0 { } } + #[inline] + /// Clears the TS (Task Switched) flag in CR0. + pub unsafe fn clear_ts() { + unsafe { + core::arch::asm!("clts", options(nomem, nostack, preserves_flags)); + } + } + #[inline] /// # Safety /// @@ -99,6 +109,7 @@ impl Cr4 { pub const TSD: u64 = 1 << 2; pub const PAE: u64 = 1 << 5; pub const OSFXSR: u64 = 1 << 9; + pub const OSXMMEXCPT: u64 = 1 << 10; pub const SMXE: u64 = 1 << 14; pub const FSGSBASE: u64 = 1 << 16; pub const PCIDE: u64 = 1 << 17; diff --git a/beskar-hal/src/x86_64/structures.rs b/beskar-hal/src/x86_64/structures.rs index c2bb605..2391272 100644 --- a/beskar-hal/src/x86_64/structures.rs +++ b/beskar-hal/src/x86_64/structures.rs @@ -810,6 +810,32 @@ impl TaskStateSegment { } } +#[derive(Debug, Clone)] +#[repr(C, align(16))] +/// Saved SSE state (512 bytes aligned to 16 bytes) +pub struct SseSave { + pub data: [u8; 160], + pub xmm: [core::arch::x86_64::__m128; 16], + _res: [core::arch::x86_64::__m128; 3], + pub available: [core::arch::x86_64::__m128; 3], +} +beskar_core::static_assert!(size_of::() == 512); + +impl Default for SseSave { + fn default() -> Self { + Self::new() + } +} + +impl SseSave { + #[must_use] + #[inline] + pub const fn new() -> Self { + // Safety: All-zeroed state is a valid initial state for SSE save area. + unsafe { core::mem::zeroed() } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/beskar-lib/src/lib.rs b/beskar-lib/src/lib.rs index 8ebda35..b89ec32 100644 --- a/beskar-lib/src/lib.rs +++ b/beskar-lib/src/lib.rs @@ -84,9 +84,10 @@ pub fn __init() { #[inline] /// In debug builds, triggers a breakpoint interrupt (`int3`). pub fn debug_break() { - #[cfg(debug_assertions)] - unsafe { - core::arch::asm!("int3", options(nomem, nostack, preserves_flags)); + if cfg!(debug_assertions) { + unsafe { + core::arch::asm!("int3", options(nomem, nostack, preserves_flags)); + } } } @@ -95,8 +96,9 @@ pub fn debug_break() { /// /// The provided value `x` is placed in the `RAX` register before triggering the interrupt. pub fn debug_break_value(x: u64) { - #[cfg(debug_assertions)] - unsafe { - core::arch::asm!("int3", in("rax") x, options(nomem, nostack, preserves_flags)); + if cfg!(debug_assertions) { + unsafe { + core::arch::asm!("int3", in("rax") x, options(nomem, nostack, preserves_flags)); + } } } diff --git a/beskar-lib/src/mem.rs b/beskar-lib/src/mem.rs index 3f35035..36cf4ae 100644 --- a/beskar-lib/src/mem.rs +++ b/beskar-lib/src/mem.rs @@ -53,6 +53,19 @@ pub fn mmap( NonNull::new(ptr).ok_or_else(|| MemoryError::new(MemoryErrorKind::OutOfMemory)) } +/// Unmap memory from the address space +/// +/// Returns true if the operation was successful, false otherwise. +/// +/// # Safety +/// +/// The pointer and size must be valid and correspond to a previously mapped region +/// that will no longer be used after this call. +pub unsafe fn munmap(ptr: *mut u8, size: u64) -> bool { + let res = crate::sys::sc_munmap(ptr, size); + res.is_success() +} + /// Change the protection of a memory region /// /// Returns true if the operation was successful, false otherwise. diff --git a/beskar-lib/src/sys.rs b/beskar-lib/src/sys.rs index 999f101..eaf36d5 100644 --- a/beskar-lib/src/sys.rs +++ b/beskar-lib/src/sys.rs @@ -52,6 +52,12 @@ pub fn sc_mmap(size: u64, alignment: u64, flags: u64) -> *mut u8 { res as _ } +#[inline] +pub fn sc_munmap(ptr: *mut u8, size: u64) -> SyscallExitCode { + let res = syscalls::syscall_2(Syscall::MemoryUnmap, ptr as u64, size); + SyscallExitCode::try_from(res).unwrap() +} + #[inline] pub fn sc_mprotect(ptr: *mut u8, size: u64, flags: u64) -> SyscallExitCode { let res = syscalls::syscall_3(Syscall::MemoryProtect, ptr as u64, size, flags); diff --git a/kernel/foundry/storage/Cargo.toml b/kernel/foundry/storage/Cargo.toml index a17ffa5..4897dbc 100644 --- a/kernel/foundry/storage/Cargo.toml +++ b/kernel/foundry/storage/Cargo.toml @@ -5,5 +5,6 @@ edition = "2024" [dependencies] beskar-core = { workspace = true } +hashbrown = { version = "0.16.1" } hyperdrive = { workspace = true } thiserror = { workspace = true } diff --git a/kernel/foundry/storage/src/fs.rs b/kernel/foundry/storage/src/fs.rs index 412756e..46e5f3b 100644 --- a/kernel/foundry/storage/src/fs.rs +++ b/kernel/foundry/storage/src/fs.rs @@ -84,10 +84,10 @@ pub trait FileSystem { fn read_dir(&mut self, path: Path) -> FileResult>; } -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct PathBuf(String); -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Path<'a>(&'a str); impl PathBuf { diff --git a/kernel/foundry/storage/src/vfs.rs b/kernel/foundry/storage/src/vfs.rs index d8fd039..1a5490a 100644 --- a/kernel/foundry/storage/src/vfs.rs +++ b/kernel/foundry/storage/src/vfs.rs @@ -1,10 +1,11 @@ use super::fs::{FileError, FileResult, FileSystem, PathBuf}; use crate::fs::Path; -use alloc::{boxed::Box, collections::BTreeMap}; +use alloc::{boxed::Box, vec::Vec}; use core::{ marker::PhantomData, sync::atomic::{AtomicI64, Ordering}, }; +use hashbrown::HashMap; use hyperdrive::locks::rw::RwLock; pub trait VfsHelper { @@ -13,7 +14,7 @@ pub trait VfsHelper { fn get_current_process_id() -> u64; } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Handle { id: i64, } @@ -31,14 +32,10 @@ impl Handle { #[must_use] #[inline] - #[expect(clippy::missing_panics_doc, reason = "Never panics")] pub fn new() -> Self { - let id = HANDLE_COUNTER.fetch_add(1, Ordering::Relaxed); - // By opening 1 000 files a second, it would take 3 000 000 centuries to overflow, // so we can deliberately not handle the overflow. - assert!(id >= 0, "Handle ID overflow"); - + let id = HANDLE_COUNTER.fetch_add(1, Ordering::Relaxed); Self { id } } @@ -53,7 +50,7 @@ impl Handle { /// /// The given ID should be positive. pub const unsafe fn from_raw(id: i64) -> Self { - debug_assert!(id >= 0); + debug_assert!(id >= 1); Self { id } } @@ -64,12 +61,11 @@ impl Handle { } } -type Mounts = BTreeMap>>; -type OpenFiles = BTreeMap; +type OpenFiles = HashMap; #[derive(Default)] pub struct Vfs { - mounts: RwLock, + mounts: RwLock, open_handles: RwLock, _helper: PhantomData, } @@ -79,37 +75,69 @@ struct OpenFileInfo { path: PathBuf, } +/// Cached sorted mount list for efficient path matching. +/// Stored as `(path_length, path, filesystem)` sorted by length descending. +#[derive(Default)] +struct MountIndex { + /// Sorted list of mounts by path length (longest first) for prefix matching + sorted_mounts: Vec<(usize, PathBuf)>, + /// `HashMap` for filesystem lookup + filesystems: HashMap>>, +} + impl Vfs { #[must_use] - #[inline] /// Creates a new VFS instance. - pub const fn new() -> Self { + pub fn new() -> Self { Self { - mounts: RwLock::new(BTreeMap::new()), - open_handles: RwLock::new(BTreeMap::new()), + mounts: RwLock::new(MountIndex::default()), + open_handles: RwLock::new(HashMap::new()), _helper: PhantomData, } } /// Mounts a filesystem at the given path. pub fn mount(&self, path: PathBuf, fs: Box) { - self.mounts.write().insert(path, RwLock::new(fs)); + let mut mounts = self.mounts.write(); + let path_len = path.as_path().len(); + + // Insert into hashmap + mounts.filesystems.insert(path.clone(), RwLock::new(fs)); + + // Insert into sorted list maintaining descending order by length + match mounts + .sorted_mounts + .binary_search_by(|&(len, _)| len.cmp(&path_len).reverse()) + { + Ok(idx) => { + // Find the correct position (may have duplicates) + mounts.sorted_mounts.insert(idx, (path_len, path)); + } + Err(idx) => mounts.sorted_mounts.insert(idx, (path_len, path)), + } } /// Unmounts the filesystem at the given path. - pub fn unmount(&self, path: &str) -> FileResult> { - self.mounts - .write() - .remove(path) - .map(RwLock::into_inner) - .ok_or(FileError::NotFound) - } - - /// Checks if a file is opened. - fn check_file_opened(&self, path: Path) -> bool { - let current_pid = H::get_current_process_id(); - self.open_handles.read().values().any(|open_file| { - open_file.path.as_path() == path && open_file.process_id == current_pid + pub fn unmount(&self, path: Path) -> FileResult> { + let mut mounts = self.mounts.write(); + + // Remove from sorted list + mounts.sorted_mounts.retain(|(_, p)| p.as_path() != path); + + // Remove from hashmap and return the filesystem + // Try to find matching PathBuf by string comparison + let path_buf = mounts + .filesystems + .iter() + .find(|(p, _)| p.as_path() == path) + .map(|(p, _)| p.clone()); + + path_buf.map_or(Err(FileError::NotFound), |path_buf| { + mounts + .filesystems + .remove(&path_buf) + .map(RwLock::into_inner) + .ok_or(FileError::NotFound) }) } @@ -117,13 +145,22 @@ impl Vfs { /// /// This function performs checks and adds the handle to the open handles list. fn new_handle(&self, path: Path) -> FileResult { - if self.check_file_opened(path) { - return Err(FileError::PermissionDenied); + let current_pid = H::get_current_process_id(); + + // Check if already opened by this process + { + let open_handles = self.open_handles.read(); + if open_handles.values().any(|open_file| { + open_file.path.as_path() == path && open_file.process_id == current_pid + }) { + return Err(FileError::PermissionDenied); + } } + let handle = Handle::new(); let open_file_info = OpenFileInfo { path: path.to_owned(), - process_id: H::get_current_process_id(), + process_id: current_pid, }; self.open_handles.write().insert(handle, open_file_info); Ok(handle) @@ -156,37 +193,33 @@ impl Vfs { f: impl FnOnce(&mut (dyn FileSystem + Send + Sync), Path) -> FileResult, ) -> FileResult { let mounts = self.mounts.read(); - - let mut best_match: Option<(&RwLock>, usize)> = None; - let mut best_len = 0; - let path_str = path.as_str(); - for (mount_path, fs) in mounts.iter() { - let mount_len = mount_path.as_path().len(); - if mount_len <= best_len { + for &(mount_len, ref mount_path) in &mounts.sorted_mounts { + if mount_len > path_str.len() { continue; } // Check if path starts with mount point - if path_str.len() >= mount_len - && &path_str[..mount_len] == mount_path.as_path().as_str() - { + if &path_str[..mount_len] == mount_path.as_path().as_str() { // Ensure we match at path boundaries to avoid partial matches // e.g., /dev should not match /device if mount_len == path_str.len() || path_str.as_bytes().get(mount_len) == Some(&b'/') || mount_path.as_path().as_str().ends_with('/') { - best_match = Some((fs, mount_len)); - best_len = mount_len; + // Found match - get filesystem from hashmap for O(1) lookup + let fs = mounts + .filesystems + .get(mount_path) + .ok_or(FileError::InvalidPath)?; + let rel_path = Path::from(&path_str[mount_len..]); + return f(&mut **fs.write(), rel_path); } } } - let (fs, mount_len) = best_match.ok_or(FileError::InvalidPath)?; - let rel_path = Path::from(&path_str[mount_len..]); - f(&mut **fs.write(), rel_path) + Err(FileError::InvalidPath) } #[inline] @@ -220,8 +253,9 @@ impl Vfs { 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(); + let res = + self.path_to_fs(open_file.path.as_path(), |fs, rel_path| fs.close(rel_path)); + debug_assert!(res.is_ok(), "Failed to close file during process cleanup"); } retained }); @@ -229,10 +263,19 @@ impl Vfs { /// Deletes a file at the given path. pub fn delete(&self, path: Path) -> FileResult<()> { - if self.check_file_opened(path) { - return Err(FileError::PermissionDenied); + let current_pid = H::get_current_process_id(); + + // Check if file is opened with lock to prevent TOCTOU issues + { + let open_handles = self.open_handles.read(); + if open_handles.values().any(|open_file| { + open_file.path.as_path() == path && open_file.process_id == current_pid + }) { + return Err(FileError::PermissionDenied); + } } - // FIXME: TOCTOU vulnerability? + + // Delete the file from the filesystem self.path_to_fs(path, |fs, rel_path| fs.delete(rel_path)) } @@ -261,7 +304,7 @@ impl Vfs { self.path_to_fs(path, |fs, rel_path| fs.metadata(rel_path)) } - pub fn read_dir(&self, path: Path) -> FileResult> { + pub fn read_dir(&self, path: Path) -> FileResult> { self.path_to_fs(path, |fs, rel_path| fs.read_dir(rel_path)) } } diff --git a/kernel/foundry/storage/tests/mock.rs b/kernel/foundry/storage/tests/mock.rs index 149a8fb..8ef8b08 100644 --- a/kernel/foundry/storage/tests/mock.rs +++ b/kernel/foundry/storage/tests/mock.rs @@ -204,57 +204,57 @@ impl VfsHelper for MockVFSHelper { #[test] fn mock() { - static VFS: Vfs = Vfs::new(); + let vfs = Vfs::::new(); // Initialize the VFS with a mock filesystem. let device = MockBlockDevice::new(1024); let fs = MockFS::new(device); - VFS.mount(PathBuf::new("/"), Box::new(fs)); + vfs.mount(PathBuf::new("/"), Box::new(fs)); // Create files. - VFS.create(Path::from("/test.txt")).unwrap(); - VFS.create(Path::from("/sw.txt")).unwrap(); + vfs.create(Path::from("/test.txt")).unwrap(); + vfs.create(Path::from("/sw.txt")).unwrap(); // Check if the files exist. - assert!(VFS.exists(Path::from("/test.txt")).unwrap()); - assert!(VFS.exists(Path::from("/sw.txt")).unwrap()); - assert!(!VFS.exists(Path::from("/nonexistent.txt")).unwrap()); + assert!(vfs.exists(Path::from("/test.txt")).unwrap()); + assert!(vfs.exists(Path::from("/sw.txt")).unwrap()); + assert!(!vfs.exists(Path::from("/nonexistent.txt")).unwrap()); // Open the files. - let handle1 = VFS.open(Path::from("/test.txt")).unwrap(); - assert!(VFS.open(Path::from("/test.txt")).is_err()); - let handle2 = VFS.open(Path::from("/sw.txt")).unwrap(); + let handle1 = vfs.open(Path::from("/test.txt")).unwrap(); + assert!(vfs.open(Path::from("/test.txt")).is_err()); + let handle2 = vfs.open(Path::from("/sw.txt")).unwrap(); // Write to the files. let data1 = b"Hello, world!"; - assert_eq!(VFS.write(handle1, data1, 0).unwrap(), 13); + assert_eq!(vfs.write(handle1, data1, 0).unwrap(), 13); let data2 = b"May the force be with you!"; - assert_eq!(VFS.write(handle2, data2, 0).unwrap(), 26); + assert_eq!(vfs.write(handle2, data2, 0).unwrap(), 26); // Read from the files. let mut buffer1 = [0; 13]; - assert_eq!(VFS.read(handle1, &mut buffer1, 0).unwrap(), 13); + assert_eq!(vfs.read(handle1, &mut buffer1, 0).unwrap(), 13); assert_eq!(&buffer1, data1); let mut buffer2 = [0; 26]; - assert_eq!(VFS.read(handle2, &mut buffer2, 0).unwrap(), 26); + assert_eq!(vfs.read(handle2, &mut buffer2, 0).unwrap(), 26); assert_eq!(&buffer2, data2); // Try to delete the files. - assert!(VFS.delete(Path::from("/test.txt")).is_err()); - assert!(VFS.delete(Path::from("/sw.txt")).is_err()); + assert!(vfs.delete(Path::from("/test.txt")).is_err()); + assert!(vfs.delete(Path::from("/sw.txt")).is_err()); // Close the files. - VFS.close(handle1).unwrap(); - assert!(VFS.close(handle1).is_err()); - VFS.close(handle2).unwrap(); + vfs.close(handle1).unwrap(); + assert!(vfs.close(handle1).is_err()); + vfs.close(handle2).unwrap(); // Check if the files exist. - assert!(VFS.exists(Path::from("/test.txt")).unwrap()); - assert!(VFS.exists(Path::from("/sw.txt")).unwrap()); + assert!(vfs.exists(Path::from("/test.txt")).unwrap()); + assert!(vfs.exists(Path::from("/sw.txt")).unwrap()); // Delete the files. - VFS.delete(Path::from("/test.txt")).unwrap(); - VFS.delete(Path::from("/sw.txt")).unwrap(); - assert!(!VFS.exists(Path::from("/test.txt")).unwrap()); - assert!(VFS.delete(Path::from("/test.txt")).is_err()); + vfs.delete(Path::from("/test.txt")).unwrap(); + vfs.delete(Path::from("/sw.txt")).unwrap(); + assert!(!vfs.exists(Path::from("/test.txt")).unwrap()); + assert!(vfs.delete(Path::from("/test.txt")).is_err()); } diff --git a/kernel/foundry/video/src/screen.rs b/kernel/foundry/video/src/screen.rs index 1b0820f..102fd2e 100644 --- a/kernel/foundry/video/src/screen.rs +++ b/kernel/foundry/video/src/screen.rs @@ -112,7 +112,7 @@ impl KernelDevice for ScreenDevice { fn write(&mut self, src: &[u8], offset: usize) -> Result<(), BlockDeviceError> { let (prefix, src, suffix) = unsafe { src.align_to::() }; - if !prefix.is_empty() || !suffix.is_empty() { + if !prefix.is_empty() || !suffix.is_empty() || !offset.is_multiple_of(size_of::()) { return Err(BlockDeviceError::UnalignedAccess); } @@ -127,9 +127,34 @@ impl KernelDevice for ScreenDevice { let pixel_format = screen.info().pixel_format(); let screen_buffer = screen.buffer_mut(); - for (&pc, d) in src.iter().zip(screen_buffer[pixel_offset..].iter_mut()) { - let pixel = Pixel::from_format(pixel_format, pc.0); - *d = pixel; + let dst_ptr = unsafe { screen_buffer.as_mut_ptr().add(pixel_offset) }; + let src_ptr = src.as_ptr(); + let len = src.len(); + + // Safety: Bounds were validated above with pixel_offset + src.len() <= pixel_limit. + // We only access indices within [pixel_offset, pixel_offset + src.len()). + match pixel_format { + beskar_core::video::PixelFormat::Rgb => { + for i in 0..len { + let pc = unsafe { *src_ptr.add(i) }.0; + let px = Pixel::new_rgb(pc); + unsafe { dst_ptr.add(i).write(px) }; + } + } + beskar_core::video::PixelFormat::Bgr => { + for i in 0..len { + let pc = unsafe { *src_ptr.add(i) }.0; + let px = Pixel::new_bgr(pc); + unsafe { dst_ptr.add(i).write(px) }; + } + } + _ => { + for i in 0..len { + let pc = unsafe { *src_ptr.add(i) }.0; + let pixel = Pixel::from_format(pixel_format, pc); + unsafe { dst_ptr.add(i).write(pixel) }; + } + } } Ok(()) diff --git a/kernel/src/arch/x86_64.rs b/kernel/src/arch/x86_64.rs index f29d706..8ef9220 100644 --- a/kernel/src/arch/x86_64.rs +++ b/kernel/src/arch/x86_64.rs @@ -2,6 +2,7 @@ pub mod ap; pub mod apic; pub mod context; pub mod cpuid; +pub mod fpu; pub mod gdt; pub mod interrupts; pub mod locals; @@ -12,6 +13,26 @@ pub mod userspace; pub fn init() { cpuid::check_cpuid(); video::debug!("CPU Vendor: {:?}", cpuid::get_cpu_vendor()); + + prepare_sse(); +} + +fn prepare_sse() { + use beskar_hal::registers::{Cr0, Cr4}; + + // Prepare CR0 + let mut cr0 = Cr0::read(); + cr0 |= Cr0::MONITOR_COPROCESSOR; + cr0 &= !Cr0::EMULATE_COPROCESSOR; + unsafe { Cr0::write(cr0) }; + + // Prepare CR4 + let mut cr4 = Cr4::read(); + cr4 |= Cr4::OSFXSR; + cr4 |= Cr4::OSXMMEXCPT; + unsafe { Cr4::write(cr4) }; + + unsafe { beskar_hal::instructions::fpu_init() }; } #[inline] diff --git a/kernel/src/arch/x86_64/cpuid.rs b/kernel/src/arch/x86_64/cpuid.rs index 2ce23ae..110cdca 100644 --- a/kernel/src/arch/x86_64/cpuid.rs +++ b/kernel/src/arch/x86_64/cpuid.rs @@ -113,6 +113,12 @@ impl CpuFeature { bit: 0, name: "FPU", }; + pub const DE: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Edx, + bit: 2, + name: "DE", + }; pub const PSE: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Edx, @@ -131,18 +137,42 @@ impl CpuFeature { bit: 5, name: "MSR", }; + pub const PAE: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Edx, + bit: 6, + name: "PAE", + }; + pub const MCE: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Edx, + bit: 7, + name: "MCE", + }; pub const APIC_ONBOARD: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Edx, bit: 9, name: "APIC", }; + pub const PGE: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Edx, + bit: 13, + name: "PGE", + }; pub const PAT: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Edx, bit: 16, name: "PAT", }; + pub const MMX: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Edx, + bit: 23, + name: "MMX", + }; pub const FXSR: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Edx, @@ -168,12 +198,30 @@ impl CpuFeature { bit: 0, name: "SSE3", }; + pub const SSSE3: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Ecx, + bit: 9, + name: "SSSE3", + }; pub const PCID: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Ecx, bit: 17, name: "PCID", }; + pub const SSE41: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Ecx, + bit: 19, + name: "SSE4.1", + }; + pub const SSE42: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Ecx, + bit: 20, + name: "SSE4.2", + }; pub const X2APIC: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Ecx, @@ -186,12 +234,24 @@ impl CpuFeature { bit: 26, name: "XSAVE", }; + pub const OSXSAVE: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Ecx, + bit: 27, + name: "OSXSAVE", + }; pub const RDRAND: Self = Self { leaf: Leaf::new(1), reg: CpuidReg::Ecx, bit: 30, name: "RDRAND", }; + pub const HYPERVISOR: Self = Self { + leaf: Leaf::new(1), + reg: CpuidReg::Ecx, + bit: 31, + name: "HYPERVISOR", + }; // LEAF 7 diff --git a/kernel/src/arch/x86_64/fpu.rs b/kernel/src/arch/x86_64/fpu.rs new file mode 100644 index 0000000..ee8cf2a --- /dev/null +++ b/kernel/src/arch/x86_64/fpu.rs @@ -0,0 +1,66 @@ +use beskar_hal::structures::SseSave; + +#[derive(Debug, Clone)] +pub struct FpuState(Option); + +impl Default for FpuState { + fn default() -> Self { + Self::new() + } +} + +impl FpuState { + #[must_use] + #[inline] + pub const fn new() -> Self { + Self(None) + } + + #[inline] + /// Saves the current FPU state into this structure. + /// + /// # Safety + /// + /// The caller must ensure that the FPU is in a valid state. + pub unsafe fn save(&mut self) { + let mut state = SseSave::new(); + unsafe { beskar_hal::instructions::fpu_save(&mut state) }; + self.0 = Some(state); + } + + #[inline] + /// Restores the FPU state from this structure. + /// + /// If the state has not been initialized, initializes the FPU instead. + /// + /// # Safety + /// + /// The caller must ensure that no other thread is using the FPU. + pub unsafe fn restore(&self) { + if let Some(state) = &self.0 { + unsafe { beskar_hal::instructions::fpu_restore(state) }; + } else { + unsafe { beskar_hal::instructions::fpu_init() }; + } + } +} + +#[expect( + unreachable_code, + reason = "FPU/SIMD state saving/restoring is not implemented yet" +)] +/// Handles the Device Not Available (#NM) exception. +/// +/// This is called when a thread tries to use the FPU/SSE but the TS bit in CR0 is set. +/// We save the previous thread's FPU state (if any) and restore the current thread's state. +/// +/// # Safety +/// +/// This function should only be called from the #NM exception handler. +pub unsafe fn handle_device_not_available() { + use beskar_hal::registers::Cr0; + + todo!("FPU/SIMD state saving/restoring"); + + unsafe { Cr0::clear_ts() }; +} diff --git a/kernel/src/arch/x86_64/gdt.rs b/kernel/src/arch/x86_64/gdt.rs index 55097a9..8c0eb24 100644 --- a/kernel/src/arch/x86_64/gdt.rs +++ b/kernel/src/arch/x86_64/gdt.rs @@ -112,7 +112,6 @@ impl Gdt { let mut tss = TaskStateSegment::new(); tss.interrupt_stack_table[DOUBLE_FAULT_IST as usize] = alloc_stack(4); tss.interrupt_stack_table[PAGE_FAULT_IST as usize] = alloc_stack(4); - tss.privilege_stack_table[0] = alloc_stack(4); tss } diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index c77277e..f0ffc60 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -3,7 +3,7 @@ use crate::locals; use beskar_core::arch::VirtAddr; use beskar_hal::{ instructions::int_enable, - registers::{CS, Cr0, Cr2}, + registers::{CS, Cr2}, structures::{GateType, InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, userspace::Ring, }; @@ -110,16 +110,17 @@ extern "x86-interrupt" fn double_fault_handler( } extern "x86-interrupt" fn page_fault_handler( - _stack_frame: InterruptStackFrame, + stack_frame: InterruptStackFrame, error_code: PageFaultErrorCode, ) { let faulting_address = Cr2::read(); let thread_id = crate::process::scheduler::current_thread_id(); video::error!( - "EXCEPTION: PAGE FAULT ({:b}) at {:#x} in Thread {}", - error_code, + "EXCEPTION: PAGE FAULT (Accessed {:#x} - {:b}) at {:#x} in Thread {}", faulting_address.as_u64(), + error_code, + stack_frame.instruction_pointer().as_u64(), thread_id.as_u64() ); @@ -291,22 +292,9 @@ impl core::fmt::Debug for ThreadRegisters { } } -#[expect( - unreachable_code, - reason = "FPU/SIMD state saving/restoring is not implemented yet" -)] extern "x86-interrupt" fn device_not_available_handler(_stack_frame: InterruptStackFrame) { - let cr0 = Cr0::read(); - if cr0 & Cr0::TASK_SWITCHED != 0 { - panic!("EXCEPTION: DEVICE NOT AVAILABLE"); - } else { - // TODO: Save FPU/SIMD state - // Choose between FXSAVE/FXRSTOR and XSAVE/XRSTOR - // Maybe set MP flag in CR0 and keep the Thread ID of the last FPU user? - todo!("Save FPU/SIMD state"); - todo!("Restore FPU/SIMD state"); - unsafe { Cr0::write(cr0 & !Cr0::TASK_SWITCHED) }; - } + // Handle lazy FPU context switching + unsafe { crate::arch::fpu::handle_device_not_available() }; } extern "x86-interrupt" fn non_maskable_interrupt_handler(_stack_frame: InterruptStackFrame) { diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs index 72cf3fd..6a0aea2 100644 --- a/kernel/src/drivers/ps2.rs +++ b/kernel/src/drivers/ps2.rs @@ -1,20 +1,24 @@ -use crate::drivers::acpi::ACPI; -use beskar_core::drivers::{ - DriverResult, - keyboard::{KeyCode, KeyEvent, KeyState}, -}; -use beskar_hal::port::{Port, ReadWrite}; -use core::sync::atomic::{AtomicBool, Ordering}; -use hyperdrive::{locks::ticket::TicketLock, once::Once}; -use thiserror::Error; +//! PS/2 Controller and Keyboard Driver +use beskar_core::drivers::DriverResult; +use core::sync::atomic::AtomicBool; +use hyperdrive::once::Once; + +mod controller; +use controller::Ps2Controller; +mod error; +mod keyboard; +use keyboard::Ps2Keyboard; static PS2_AVAILABLE: AtomicBool = AtomicBool::new(false); static PS2_CONTROLLER: Ps2Controller = Ps2Controller::new(); static PS2_KEYBOARD: Once = Once::uninit(); -const PS2_RETRIES: u32 = 1 << 17; - +/// Initialize the PS/2 controller and keyboard. +/// +/// # Errors +/// +/// Returns an error if controller initialization or keyboard setup fails. pub fn init() -> DriverResult<()> { PS2_CONTROLLER.initialize()?; let ps2_keyboard = Ps2Keyboard::new(&PS2_CONTROLLER)?; @@ -23,513 +27,16 @@ pub fn init() -> DriverResult<()> { Ok(()) } -#[derive(Error, Debug, Clone, Copy)] -pub enum Ps2Error { - #[error("PS/2 controller self-test failed")] - SelfTest, - #[error("PS/2 controller first port test failed")] - FirstPortTest, - // #[error("PS/2 controller second port test failed")] - // SecondPortTest, - #[error("PS/2 keyboard reset failed")] - KeyboardReset, - #[error("PS/2 controller does not support keyboard")] - KeyboardUnsupported, - - #[error("PS/2 controller data send failed")] - Sending, - #[error("PS/2 controller data receive failed")] - 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: TicketLock>, - cmd_sts_port: TicketLock>, - has_two_ports: AtomicBool, -} - -enum Ps2Command { - DisableFirstPort = 0xAD, - DisableSecondPort = 0xA7, - EnableFirstPort = 0xAE, - // EnableSecondPort = 0xA8, - // TestSecondPort = 0xA9, - TestFirstPort = 0xAB, - ReadConfigByte = 0x20, - WriteConfigByte = 0x60, - SelfTest = 0xAA, - - KeyboardScancodeSet = 0xF0, - KeyboardResetAndSelfTest = 0xFF, -} - -impl Default for Ps2Controller { - fn default() -> Self { - Self::new() - } -} - -impl Ps2Controller { - const DATA_PORT: u16 = 0x60; - const CMD_STS_PORT: u16 = 0x64; - - #[must_use] - #[inline] - pub const fn new() -> Self { - Self { - 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), - } - } - - #[inline] - fn write_config(&self, config: u8) { - self.write_command(Ps2Command::WriteConfigByte); - self.write_data(config); - } - - #[must_use] - #[inline] - fn read_config(&self) -> u8 { - self.write_command(Ps2Command::ReadConfigByte); - self.read_data() - } - - #[must_use] - #[inline] - fn status_register(&self) -> u8 { - unsafe { self.cmd_sts_port.lock().read() } - } - - #[inline] - fn flush_buffer(&self) { - let _ = self.read_data(); - } - - 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(Ps2Error::KeyboardUnsupported); - } - - self.write_command(Ps2Command::DisableFirstPort); - self.write_command(Ps2Command::DisableSecondPort); - - self.flush_buffer(); - - // Set up the controller configuration byte - let mut config = self.read_config(); - config &= !0b11; // Disable interrupts for both ports - config &= !0b100_0000; // Disable scancode translation - let has_two_ports = config & 0b10_000 == 0; - self.write_config(config); - - // Perform self-test - self.write_command(Ps2Command::SelfTest); - { - let mut has_passed = false; - for _ in 0..PS2_RETRIES { - if self.read_data() == 0x55 { - has_passed = true; - break; - } - } - if !has_passed { - video::warn!("PS/2 controller self-test failed"); - return Err(Ps2Error::SelfTest); - } - } - self.write_config(config); - - // Perform first port test - self.write_command(Ps2Command::TestFirstPort); - { - let mut has_passed = false; - for _ in 0..PS2_RETRIES { - if self.read_data() == 0 { - has_passed = true; - break; - } - } - if !has_passed { - video::warn!("PS/2 controller first port test failed"); - return Err(Ps2Error::FirstPortTest); - } - } - - // Enable the first port - self.write_command(Ps2Command::EnableFirstPort); - self.write_config((config | 1) & !0b1_0000); // Enable interrupts/clock for the first port - - self.has_two_ports.store(has_two_ports, Ordering::Relaxed); - - Ok(()) - } - - #[inline] - fn write_command(&self, command: Ps2Command) { - unsafe { self.cmd_sts_port.lock().write(command as u8) }; - } - - #[must_use] - #[inline] - fn read_status(&self) -> u8 { - unsafe { self.cmd_sts_port.lock().read() } - } - - #[inline] - fn write_data(&self, data: u8) { - unsafe { self.data_port.lock().write(data) }; - } - - #[must_use] - #[inline] - fn read_data(&self) -> u8 { - unsafe { self.data_port.lock().read() } - } - - /// Send a Host to Device command to the PS/2 controller. - fn send(&self, value: u8) -> Ps2Result<()> { - for _ in 0..PS2_RETRIES { - if self.status_register() & 0b10 == 0 { - self.write_data(value); - return Ok(()); - } - } - Err(Ps2Error::Sending) - } - - /// Receive a Device to Host command from the PS/2 controller. - fn recv(&self) -> Ps2Result { - for _ in 0..PS2_RETRIES { - if self.status_register() & 1 != 0 { - return Ok(self.read_data()); - } - } - Err(Ps2Error::Receiving) - } - - #[inline] - /// Send a command to the PS/2 controller and receive a response. - fn send_recv(&self, value: u8) -> Ps2Result { - self.send(value)?; - self.recv() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[expect(dead_code, reason = "Not all scancode sets are implemented yet")] -enum ScancodeSet { - Set1 = 1, - Set2 = 2, - Set3 = 3, -} - -pub struct Ps2Keyboard<'c> { - controller: &'c Ps2Controller, - scancode_set: ScancodeSet, -} - -impl<'a> Ps2Keyboard<'a> { - #[inline] - pub fn new(controller: &'a Ps2Controller) -> DriverResult { - const DEFAULT_SCANCODE_SET: ScancodeSet = ScancodeSet::Set1; - - let keyboard = Ps2Keyboard { - controller, - scancode_set: DEFAULT_SCANCODE_SET, - }; - - // Reset the keyboard - let Ok(value) = keyboard.send_command(Ps2Command::KeyboardResetAndSelfTest as u8) else { - video::warn!("PS/2 keyboard failed to receive reset command"); - return Err(beskar_core::drivers::DriverError::Invalid); - }; - if value != 0xFA { - video::warn!("PS/2 keyboard didn't acknowledge"); - return Err(beskar_core::drivers::DriverError::Invalid); - } - { - let mut has_passed = false; - for _ in 0..PS2_RETRIES { - let value = controller.read_data(); - if value == 0xAA { - has_passed = true; - break; - } - if value == 0xFC || value == 0xFD { - video::warn!("PS/2 keyboard reset failed"); - return Err(beskar_core::drivers::DriverError::Invalid); - } - } - if !has_passed { - video::warn!("PS/2 keyboard reset failed with unexpected value"); - return Err(beskar_core::drivers::DriverError::Unknown); - } - } - - // Set the scancode set - let res = keyboard - .send_command(Ps2Command::KeyboardScancodeSet as u8) - .unwrap(); - if res != 0xFA { - video::warn!("PS/2 keyboard didn't acknowledge scancode set command"); - return Err(beskar_core::drivers::DriverError::Invalid); - } - let res = keyboard.send_command(DEFAULT_SCANCODE_SET as u8).unwrap(); - if res != 0xFA { - video::warn!("PS/2 keyboard didn't acknowledge scancode set command"); - return Err(beskar_core::drivers::DriverError::Invalid); - } - - Ok(keyboard) - } - - fn send_command(&self, command: u8) -> Ps2Result { - const TRIES: u8 = 4; - for _ in 0..TRIES { - let Ok(value) = self.controller.send_recv(command) else { - continue; - }; - if value == 0xFA || value == 0xAA { - // Acknowledge or SelfTestPass - return Ok(value); - } else if value == 0xFE { - // Resend - } else if value == 0xFC || value == 0xFD { - // SelfTestFail - return Err(Ps2Error::KeyboardReset); - } - } - // Try one last time to propagate error - self.controller.send_recv(command) - } - - #[must_use] - fn read_scancode(&self) -> Option { - // Check if data is available - let status = self.controller.read_status(); - if status & 1 != 0 { - Some(self.controller.read_data()) - } else { - None - } - } - - #[must_use] - pub fn poll_key(&self) -> Option { - self.read_scancode() - } - - #[must_use] - #[inline] - pub fn scancode_to_keycode(&self, extended: bool, scancode: u8) -> Option { - match self.scancode_set { - ScancodeSet::Set1 => Self::scancode_set1_to_keycode(extended, scancode), - // 2 => self.scancode_set2_to_char(scancode), - // 3 => self.scancode_set3_to_char(scancode), - _ => None, - } - } - - #[must_use] - fn scancode_set1_to_keycode(extended: bool, mut scancode: u8) -> Option { - let pressed = if scancode & 0x80 == 0 { - KeyState::Pressed - } else { - scancode &= 0x7F; - KeyState::Released - }; - - let keycode = match (extended, scancode) { - (false, 0x01) => Some(KeyCode::Escape), - - (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, 0x1D) => Some(KeyCode::CtrlRight), - - (true, 0x48) => Some(KeyCode::ArrowUp), - (true, 0x50) => Some(KeyCode::ArrowDown), - (true, 0x4B) => Some(KeyCode::ArrowLeft), - (true, 0x4D) => Some(KeyCode::ArrowRight), - - // TODO: Modifier keys - _ => None, - }?; - - Some(KeyEvent::new(keycode, pressed)) - } -} - -// pub struct Ps2Mouse<'c> { -// controller: &'c Ps2Controller, -// } - -// impl<'a> Ps2Mouse<'a> { -// #[must_use] -// #[inline] -// pub const fn new(controller: &'a Ps2Controller) -> Self { -// Ps2Mouse { controller } -// } - -// pub fn initialize(&self) { -// // Enable mouse device -// self.controller.write_command(0xD4); // Send command to mouse -// self.controller.write_data(0xF4); // Enable data reporting -// } - -// #[must_use] -// pub fn read_packet(&self) -> Option<[u8; 3]> { -// // Check if data is available -// let status = self.controller.read_command(); -// if status & 1 != 0 { -// let mut packet = [0u8; 3]; -// for byte in &mut packet { -// *byte = self.controller.read_data(); -// } -// Some(packet) -// } else { -// None -// } -// } -// } - +/// Handle a keyboard interrupt. +/// +/// This function is called from the keyboard IRQ handler. +/// It polls for key events and pushes them to the keyboard event queue. pub fn handle_keyboard_interrupt() { - static EXTENDED: AtomicBool = AtomicBool::new(false); - let Some(keyboard) = PS2_KEYBOARD.get() else { return; }; - let Some(scan_code) = keyboard.poll_key() else { - return; - }; - - if scan_code == 0xE0 { - let previous = EXTENDED.swap(true, Ordering::Release); - assert!(!previous); - // Do nothing, wait for the next byte - } else if scan_code == 0xE1 { - // TODO: Handle pause/break key - EXTENDED.store(true, Ordering::Release); - } else { - let extended = EXTENDED.swap(false, Ordering::Release); - if scan_code != 0 && scan_code != 0xFA { - handle_real_key(extended, scan_code); - } - } -} - -fn handle_real_key(extended: bool, key: u8) { - let keyboard = PS2_KEYBOARD.get().unwrap(); - - let Some(key_event) = keyboard.scancode_to_keycode(extended, key) else { - video::warn!("Unknown key: {:#X} (extended: {})", key, extended); + let Some(key_event) = keyboard.poll_key_event() else { return; }; diff --git a/kernel/src/drivers/ps2/controller.rs b/kernel/src/drivers/ps2/controller.rs new file mode 100644 index 0000000..187c212 --- /dev/null +++ b/kernel/src/drivers/ps2/controller.rs @@ -0,0 +1,206 @@ +use crate::drivers::acpi::ACPI; +use beskar_hal::port::{Port, ReadWrite}; +use core::sync::atomic::{AtomicBool, Ordering}; +use hyperdrive::locks::ticket::TicketLock; + +use super::error::{Ps2Error, Ps2Result}; + +/// Commands for PS/2 controller communication +#[derive(Debug, Clone, Copy)] +enum Ps2Command { + DisableFirstPort = 0xAD, + DisableSecondPort = 0xA7, + EnableFirstPort = 0xAE, + TestFirstPort = 0xAB, + ReadConfigByte = 0x20, + WriteConfigByte = 0x60, + SelfTest = 0xAA, + + KeyboardScancodeSet = 0xF0, + KeyboardEnableScanning = 0xF4, + KeyboardResend = 0xFE, + KeyboardResetAndSelfTest = 0xFF, +} + +/// Special response bytes from PS/2 devices +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SpecialBytes; + +impl SpecialBytes { + pub const ERROR: u8 = 0x00; + pub const SELF_TEST_PASSED: u8 = 0xAA; + pub const ECHO: u8 = 0xEE; + pub const ACK: u8 = 0xFA; + pub const SELF_TEST_FAIL: u8 = 0xFC; + pub const SELF_TEST_FAIL2: u8 = 0xFD; + pub const RESEND: u8 = 0xFE; + pub const KEY_ERROR: u8 = 0xFF; +} + +const PS2_RETRIES: u32 = 1 << 17; + +/// PS/2 controller managing port I/O and low-level communication. +pub struct Ps2Controller { + data_port: TicketLock>, + cmd_sts_port: TicketLock>, + has_two_ports: AtomicBool, +} + +impl Ps2Controller { + const DATA_PORT: u16 = 0x60; + const CMD_STS_PORT: u16 = 0x64; + + /// Create a new PS/2 controller instance. + #[must_use] + #[inline] + pub const fn new() -> Self { + Self { + 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), + } + } + + /// Initialize the PS/2 controller and validate keyboard support. + pub fn initialize(&self) -> Ps2Result<()> { + let keyboard_support = ACPI.get().unwrap().fadt().ps2_keyboard(); + if !keyboard_support { + video::warn!("PS/2 controller not supported by ACPI"); + return Err(Ps2Error::KeyboardUnsupported); + } + + self.write_command(Ps2Command::DisableFirstPort); + self.write_command(Ps2Command::DisableSecondPort); + self.flush_buffer(); + + // Read and configure the controller: + // Bit[1:0]: Interrupt enables (1st port, 2nd port) + // Bit[6]: Scancode translation (1=Set1, 0=raw set) + // Bit[4]: Dual-port indicator (1=one port, 0=two ports) + let mut config = self.read_config(); + config &= !0b11; // Disable interrupts for both ports + config &= !0b100_0000; // Disable scancode translation (keep raw scancode set) + let has_two_ports = config & 0b10_000 == 0; + self.write_config(config); + + // Controller self-test: must respond with 0x55 to pass + self.write_command(Ps2Command::SelfTest); + let mut has_passed = false; + for _ in 0..PS2_RETRIES { + if self.read_data() == 0x55 { + has_passed = true; + break; + } + } + if !has_passed { + video::warn!("PS/2 controller self-test failed"); + return Err(Ps2Error::SelfTest); + } + self.write_config(config); + + // First port test: must respond with 0x00 to pass + self.write_command(Ps2Command::TestFirstPort); + has_passed = false; + for _ in 0..PS2_RETRIES { + if self.read_data() == 0 { + has_passed = true; + break; + } + } + if !has_passed { + video::warn!("PS/2 controller first port test failed"); + return Err(Ps2Error::FirstPortTest); + } + + // Enable the first port: set interrupt and clock bits for port 1 + self.write_command(Ps2Command::EnableFirstPort); + self.write_config((config | 0b1) & !0b1_0000); + + self.has_two_ports.store(has_two_ports, Ordering::Relaxed); + + Ok(()) + } + + /// Read a byte from the data port. + #[must_use] + #[inline] + pub fn read_data(&self) -> u8 { + unsafe { self.data_port.lock().read() } + } + + /// Write a byte to the data port. + #[inline] + pub fn write_data(&self, data: u8) { + unsafe { self.data_port.lock().write(data) }; + } + + /// Read the status register. + #[must_use] + #[inline] + pub fn read_status(&self) -> u8 { + unsafe { self.cmd_sts_port.lock().read() } + } + + /// Send data to a PS/2 device, waiting for the controller's input buffer to be ready. + /// + /// Polls the status register's "input buffer full" bit (0x02). When clear, + /// the input buffer is empty and ready to accept the next command/data byte. + pub fn send(&self, value: u8) -> Ps2Result<()> { + for _ in 0..PS2_RETRIES { + if self.read_status() & 0b10 == 0 { + self.write_data(value); + return Ok(()); + } + } + Err(Ps2Error::Sending) + } + + /// Receive data from a PS/2 device, waiting for the controller's output buffer to contain data. + /// + /// Polls the status register's "output buffer full" bit (0x01). When set, + /// the output buffer contains data ready to be read. + pub fn recv(&self) -> Ps2Result { + for _ in 0..PS2_RETRIES { + if self.read_status() & 1 != 0 { + return Ok(self.read_data()); + } + } + Err(Ps2Error::Receiving) + } + + /// Send a command and receive a response. + #[inline] + pub fn send_recv(&self, value: u8) -> Ps2Result { + self.send(value)?; + self.recv() + } + + #[inline] + fn write_command(&self, command: Ps2Command) { + unsafe { self.cmd_sts_port.lock().write(command as u8) }; + } + + #[must_use] + #[inline] + fn read_config(&self) -> u8 { + self.write_command(Ps2Command::ReadConfigByte); + self.read_data() + } + + #[inline] + fn write_config(&self, config: u8) { + self.write_command(Ps2Command::WriteConfigByte); + self.write_data(config); + } + + #[inline] + fn flush_buffer(&self) { + let _ = self.read_data(); + } +} + +impl Default for Ps2Controller { + fn default() -> Self { + Self::new() + } +} diff --git a/kernel/src/drivers/ps2/error.rs b/kernel/src/drivers/ps2/error.rs new file mode 100644 index 0000000..ef00649 --- /dev/null +++ b/kernel/src/drivers/ps2/error.rs @@ -0,0 +1,35 @@ +use thiserror::Error; + +/// Errors that may occur during PS/2 controller or keyboard operations. +#[derive(Error, Debug, Clone, Copy)] +pub enum Ps2Error { + #[error("PS/2 controller self-test failed")] + SelfTest, + + #[error("PS/2 controller first port test failed")] + FirstPortTest, + + #[error("PS/2 keyboard reset failed")] + KeyboardReset, + + #[error("PS/2 controller does not support keyboard")] + KeyboardUnsupported, + + #[error("PS/2 controller data send failed")] + Sending, + + #[error("PS/2 controller data receive failed")] + Receiving, +} + +impl From for beskar_core::drivers::DriverError { + fn from(error: Ps2Error) -> Self { + match error { + Ps2Error::KeyboardUnsupported => Self::Absent, + Ps2Error::FirstPortTest | Ps2Error::KeyboardReset | Ps2Error::SelfTest => Self::Invalid, + Ps2Error::Sending | Ps2Error::Receiving => Self::Unknown, + } + } +} + +pub type Ps2Result = Result; diff --git a/kernel/src/drivers/ps2/keyboard.rs b/kernel/src/drivers/ps2/keyboard.rs new file mode 100644 index 0000000..1b31025 --- /dev/null +++ b/kernel/src/drivers/ps2/keyboard.rs @@ -0,0 +1,297 @@ +use super::controller::{Ps2Controller, SpecialBytes}; +use super::error::{Ps2Error, Ps2Result}; +use beskar_core::drivers::{DriverResult, keyboard::KeyEvent}; +use core::sync::atomic::{AtomicU16, Ordering}; + +mod decoder; +use decoder::{ScancodeDecoder, ScancodeSet}; +mod keycodes; + +const PS2_RETRIES: u32 = 1 << 17; + +/// PS/2 keyboard device driver. +pub struct Ps2Keyboard<'c> { + controller: &'c Ps2Controller, + scancode_set: ScancodeSet, + state: AtomicU16, +} + +impl<'a> Ps2Keyboard<'a> { + /// Create and initialize a new PS/2 keyboard device. + pub fn new(controller: &'a Ps2Controller) -> DriverResult { + const DEFAULT_SCANCODE_SET: ScancodeSet = ScancodeSet::Set2; + + let mut keyboard = Ps2Keyboard { + controller, + scancode_set: DEFAULT_SCANCODE_SET, + state: AtomicU16::new(0), + }; + + keyboard.reset_device()?; + keyboard.flush_output_buffer(); + keyboard.scancode_set = keyboard.ensure_scancode_set(DEFAULT_SCANCODE_SET); + keyboard.enable_scanning()?; + + video::debug!("PS/2 keyboard scancode set: {:?}", keyboard.scancode_set); + + Ok(keyboard) + } + + /// Poll for a key event from the keyboard. + #[must_use] + pub fn poll_key_event(&self) -> Option { + let scancode = self.read_scancode()?; + let mut state = self.state.load(Ordering::Acquire); + let event = ScancodeDecoder::decode(scancode, &mut state, self.scancode_set); + self.state.store(state, Ordering::Release); + event + } + + /// Reset and self-test the keyboard device. + fn reset_device(&self) -> DriverResult<()> { + let Ok(value) = self.send_command(0xFF) else { + video::warn!("PS/2 keyboard failed to receive reset command"); + return Err(beskar_core::drivers::DriverError::Invalid); + }; + + if value != SpecialBytes::ACK { + video::warn!("PS/2 keyboard didn't acknowledge"); + return Err(beskar_core::drivers::DriverError::Invalid); + } + + let mut has_passed = false; + for _ in 0..PS2_RETRIES { + let value = self.controller.read_data(); + if value == SpecialBytes::SELF_TEST_PASSED { + has_passed = true; + break; + } + if value == SpecialBytes::SELF_TEST_FAIL || value == SpecialBytes::SELF_TEST_FAIL2 { + video::warn!("PS/2 keyboard reset failed"); + return Err(beskar_core::drivers::DriverError::Invalid); + } + } + + if !has_passed { + video::warn!("PS/2 keyboard reset failed with unexpected value"); + return Err(beskar_core::drivers::DriverError::Unknown); + } + + Ok(()) + } + + /// Enable keyboard scanning. + fn enable_scanning(&self) -> DriverResult<()> { + let res = self.send_command(0xF4)?; + if res != SpecialBytes::ACK { + video::warn!("PS/2 keyboard didn't acknowledge scanning enable command"); + return Err(beskar_core::drivers::DriverError::Invalid); + } + Ok(()) + } + + /// Attempt to use the preferred scancode set, falling back gracefully if unsupported. + /// + /// Some older or non-standard keyboards may not support all three sets. + /// This queries the current set, tries to switch if needed, then validates the result. + /// Returns the successfully active set or the preferred default if switching fails. + fn ensure_scancode_set(&self, preferred: ScancodeSet) -> ScancodeSet { + let detected = self.query_scancode_set().ok(); + if detected == Some(preferred) { + return preferred; + } + + if self.set_scancode_set(preferred).is_ok() { + return self.query_scancode_set().unwrap_or(preferred); + } + + detected.unwrap_or(preferred) + } + + /// Query the current scancode set. + fn query_scancode_set(&self) -> Ps2Result { + let res = self.send_command(0xF0)?; + if res != 0xFA { + return Err(Ps2Error::Receiving); + } + + let res = self.send_command(0x00)?; + if res != 0xFA { + return Err(Ps2Error::Receiving); + } + + let value = self.controller.recv()?; + ScancodeSet::try_from(value).map_err(|()| Ps2Error::Receiving) + } + + /// Switch to a new scancode set. + fn set_scancode_set(&self, set: ScancodeSet) -> Ps2Result<()> { + let res = self.send_command(0xF0)?; + if res != 0xFA { + return Err(Ps2Error::Sending); + } + + let res = self.send_command(set as u8)?; + if res != 0xFA { + return Err(Ps2Error::Sending); + } + + Ok(()) + } + + /// Send a command to the keyboard and retrieve the response, with retries on resend requests. + fn send_command(&self, command: u8) -> Ps2Result { + const TRIES: u8 = 4; + for _ in 0..TRIES { + let value = self.controller.send_recv(command); + match value { + Ok(SpecialBytes::ACK | SpecialBytes::SELF_TEST_PASSED) => { + return value; + } + Ok(SpecialBytes::SELF_TEST_FAIL | SpecialBytes::SELF_TEST_FAIL2) => { + return Err(Ps2Error::KeyboardReset); + } + _ => {} + } + } + self.controller.send_recv(command) + } + + /// Poll for a scancode from the keyboard, filtering out controller-generated artifacts. + #[must_use] + fn read_scancode(&self) -> Option { + let status = self.controller.read_status(); + if status & 1 == 0 { + return None; + } + + let value = self.controller.read_data(); + match value { + SpecialBytes::ACK + | SpecialBytes::RESEND + | SpecialBytes::SELF_TEST_PASSED + | SpecialBytes::SELF_TEST_FAIL + | SpecialBytes::SELF_TEST_FAIL2 + | SpecialBytes::ECHO + | SpecialBytes::ERROR + | SpecialBytes::KEY_ERROR => None, + _ => Some(value), + } + } + + /// Flush the keyboard output buffer. + fn flush_output_buffer(&self) { + while self.controller.read_status() & 1 != 0 { + let _ = self.controller.read_data(); + } + } +} + +/// State machine flags for multi-byte PS/2 scancode sequences. +/// +/// All state is packed into a single `u16` to fit in an AtomicU16: +/// - Bits [0:3]: Transient flags (extended, release, print_make, print_break) +/// - Bits [8:15]: Pause sequence remaining byte counter +/// +/// These flags track intermediate states in multi-byte sequences (Pause/Break: 6-8 bytes, +/// PrintScreen: 4-6 bytes depending on set) and are cleared after each complete key event +/// to prevent cross-contamination between unrelated keypresses. +pub struct KeyboardState; + +impl KeyboardState { + /// State flag: next scancode is part of an extended sequence (0xE0 prefix) + pub const STATE_EXTENDED: u16 = 0b0000_0001; + /// State flag: next scancode is a release (break) event (0xF0 prefix in Set 2/3) + pub const STATE_RELEASE: u16 = 0b0000_0010; + /// State flag: building PrintScreen make sequence + pub const STATE_PRINT_MAKE: u16 = 0b0000_0100; + /// State flag: building PrintScreen release sequence + pub const STATE_PRINT_BREAK: u16 = 0b0000_1000; + /// Mask for all transient state flags (should be cleared after each decoded key) + pub const FLAGS_MASK: u16 = Self::STATE_EXTENDED + | Self::STATE_RELEASE + | Self::STATE_PRINT_MAKE + | Self::STATE_PRINT_BREAK; + /// Bit position where pause counter starts + pub const PAUSE_SHIFT: u16 = 8; + /// Mask to extract/clear pause counter + pub const PAUSE_MASK: u16 = 0xFF << Self::PAUSE_SHIFT; + + #[must_use] + #[inline] + /// Get the number of remaining bytes in the Pause/Break sequence. + pub const fn pause_remaining(state: u16) -> u8 { + (state >> Self::PAUSE_SHIFT) as u8 + } + + #[must_use] + #[inline] + /// Set the pause sequence counter. + pub fn set_pause_remaining(state: u16, remaining: u8) -> u16 { + (state & !Self::PAUSE_MASK) | (u16::from(remaining) << Self::PAUSE_SHIFT) + } + + #[must_use] + #[inline] + /// Check if the extended flag is set. + pub const fn is_extended(state: u16) -> bool { + (state & Self::STATE_EXTENDED) != 0 + } + + #[must_use] + #[inline] + /// Check if the release flag is set. + pub const fn is_released(state: u16) -> bool { + (state & Self::STATE_RELEASE) != 0 + } + + #[must_use] + #[inline] + /// Check if PrintScreen make flag is set. + pub const fn is_print_make(state: u16) -> bool { + (state & Self::STATE_PRINT_MAKE) != 0 + } + + #[must_use] + #[inline] + /// Check if PrintScreen break flag is set. + pub const fn is_print_break(state: u16) -> bool { + (state & Self::STATE_PRINT_BREAK) != 0 + } + + #[must_use] + #[inline] + /// Clear all state flags except the pause counter. + pub const fn clear_flags(state: u16) -> u16 { + state & !0xF + } + + #[must_use] + #[inline] + /// Clear extended and print flags, preserving pause counter. + pub const fn clear_extended_print(state: u16) -> u16 { + state & !(Self::STATE_EXTENDED | Self::STATE_PRINT_MAKE | Self::STATE_PRINT_BREAK) + } + + #[must_use] + #[inline] + /// Clear extended and release flags, preserving pause counter. + pub const fn clear_extended_release(state: u16) -> u16 { + state & !(Self::STATE_EXTENDED | Self::STATE_RELEASE) + } + + #[must_use] + #[inline] + /// Clear all special flags after processing a key. + pub const fn clear_all_flags(state: u16) -> u16 { + state & !Self::FLAGS_MASK + } + + #[must_use] + #[inline] + /// Abort incomplete PrintScreen/PrintBreak sequence and restore clean state. + /// Used when unexpected bytes are received during sequence decoding. + pub const fn abort_incomplete_sequence(state: u16) -> u16 { + state & !(Self::STATE_EXTENDED | Self::STATE_PRINT_MAKE | Self::STATE_PRINT_BREAK) + } +} diff --git a/kernel/src/drivers/ps2/keyboard/decoder.rs b/kernel/src/drivers/ps2/keyboard/decoder.rs new file mode 100644 index 0000000..9e8aee8 --- /dev/null +++ b/kernel/src/drivers/ps2/keyboard/decoder.rs @@ -0,0 +1,220 @@ +use super::KeyboardState; +use super::keycodes::{Ps2Set1Keycodes, Ps2Set2Keycodes}; +use beskar_core::drivers::keyboard::{KeyCode, KeyEvent, KeyState}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ScancodeSet { + Set1 = 1, + Set2 = 2, + Set3 = 3, +} + +impl TryFrom for ScancodeSet { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Self::Set1), + 2 => Ok(Self::Set2), + 3 => Ok(Self::Set3), + _ => Err(()), + } + } +} + +/// Scancode decoder for PS/2 keyboards supporting all three scancode sets. +/// +/// Handles multi-byte sequences (Pause/Break: 6 or 8 bytes, PrintScreen: 4 or 6 bytes) +/// and implicit state machines triggered by prefix bytes (0xE0, 0xF0, 0xE1). +pub struct ScancodeDecoder; + +impl ScancodeDecoder { + /// Decode a single scancode into an optional KeyEvent, advancing multi-byte sequence state. + /// + /// Returns `None` if: + /// - The byte is a prefix (0xE0, 0xF0, 0xE1) that sets state for the next byte(s) + /// - We are consuming intermediate bytes of a Pause/Break sequence + /// - We are building a PrintScreen make/break sequence + /// + /// Returns `Some(KeyEvent)` when a complete key event is decoded, after which state is reset. + #[must_use] + pub fn decode(scancode: u8, state: &mut u16, scancode_set: ScancodeSet) -> Option { + // Handle Pause/Break multi-byte sequence state machine. + // Pause/Break sequences have fixed length (6 bytes in Set 1, 8 bytes in Sets 2/3). + // No other key is transmitted during this sequence; all intermediate bytes are consumed. + // Only the final byte triggers a KeyEvent emit (PauseBreak press with no release). + let pause_remaining = KeyboardState::pause_remaining(*state); + if pause_remaining != 0 { + let new_remaining = pause_remaining - 1; + // Clear all flags while consuming Pause sequence, preserving only pause counter. + *state = KeyboardState::set_pause_remaining( + *state & !KeyboardState::FLAGS_MASK, + new_remaining, + ); + + let event = if new_remaining == 0 { + Some(KeyEvent::new(KeyCode::PauseBreak, KeyState::Pressed)) + } else { + None + }; + + return event; + } + + // Detect Pause/Break sequence starter (0xE1 in all sets). + // This is the only multi-byte sequence initiated by 0xE1; all sets recognize it. + // If we see 0xE1, consume the remaining bytes before emitting the key event. + if scancode == 0xE1 { + let remaining = match scancode_set { + ScancodeSet::Set1 => 5, + ScancodeSet::Set2 | ScancodeSet::Set3 => 7, + }; + *state = KeyboardState::set_pause_remaining(*state, remaining); + return None; + } + + // Handle extended sequence prefix (0xE0). + // Sets the STATE_EXTENDED flag so that the next scancode byte is interpreted + // as an extended key. + if scancode == 0xE0 { + *state |= KeyboardState::STATE_EXTENDED; + return None; + } + + // Handle release prefix for Set 2 and Set 3 (0xF0). + // Sets the STATE_RELEASE flag so that the next scancode byte is interpreted + // as a key release. + // Set 1 does not use this prefix; release is encoded in bit 7 instead. + if scancode == 0xF0 && scancode_set != ScancodeSet::Set1 { + *state |= KeyboardState::STATE_RELEASE; + return None; + } + + // Decode the scancode based on the active set. + match scancode_set { + ScancodeSet::Set1 => Self::decode_set1(state, scancode), + ScancodeSet::Set2 | ScancodeSet::Set3 => Self::decode_set2_like(state, scancode), + } + } + + #[must_use] + /// Decode Set 1 scancodes with hardware-specific handling. + fn decode_set1(state: &mut u16, mut scancode: u8) -> Option { + let extended = KeyboardState::is_extended(*state); + let in_print_make = KeyboardState::is_print_make(*state); + let in_print_break = KeyboardState::is_print_break(*state); + + // Handle PrintScreen make sequence: E0 2A E0 37 + // STATE_PRINT_MAKE tracks the intermediate step (E0 2A detected, waiting for E0 37). + // If we see E0 2A, switch to PRINT_MAKE state and consume this pair without emitting. + if extended && scancode == 0x2A && !in_print_make && !in_print_break { + *state = (*state & !KeyboardState::STATE_EXTENDED) | KeyboardState::STATE_PRINT_MAKE; + return None; + } + + // Emit PrintScreen press when we complete the make sequence. + // We have consumed all 4 bytes: E0 2A E0 37. Emit the key event and clear all flags. + if extended && scancode == 0x37 && in_print_make { + *state = KeyboardState::clear_all_flags(*state); + return Some(KeyEvent::new(KeyCode::PrintScreen, KeyState::Pressed)); + } + + // Handle PrintScreen break sequence: E0 B7 E0 AA + // STATE_PRINT_BREAK tracks the intermediate step (E0 B7 detected, waiting for E0 AA). + // If we see E0 B7, switch to PRINT_BREAK state and consume this pair without emitting. + if extended && scancode == 0xB7 && !in_print_make && !in_print_break { + *state = (*state & !KeyboardState::STATE_EXTENDED) | KeyboardState::STATE_PRINT_BREAK; + return None; + } + + // Emit PrintScreen release when we complete the break sequence. + // We have consumed all 4 bytes: E0 B7 E0 AA. Emit the key event and clear all flags. + if extended && scancode == 0xAA && in_print_break { + *state = KeyboardState::clear_all_flags(*state); + return Some(KeyEvent::new(KeyCode::PrintScreen, KeyState::Released)); + } + + // If we were building a PrintScreen sequence but got an unexpected byte, + // abort the sequence to prevent state corruption. + if in_print_make || in_print_break { + *state = KeyboardState::abort_incomplete_sequence(*state); + return None; + } + + // Determine key state from bit 7 of the scancode. + // Set 1 encodes release by setting bit 7: scancode = 0x80 | make_code. + let pressed = if scancode & 0x80 == 0 { + KeyState::Pressed + } else { + scancode &= 0x7F; + KeyState::Released + }; + + *state = KeyboardState::clear_all_flags(*state); + + let keycode = Ps2Set1Keycodes::map(extended, scancode)?; + Some(KeyEvent::new(keycode, pressed)) + } + + /// Decode Set 2 or Set 3 scancodes (functionally identical for decode purposes). + #[must_use] + fn decode_set2_like(state: &mut u16, scancode: u8) -> Option { + let extended = KeyboardState::is_extended(*state); + let released = KeyboardState::is_released(*state); + let in_print_make = KeyboardState::is_print_make(*state); + let in_print_break = KeyboardState::is_print_break(*state); + + // Handle PrintScreen make sequence: E0 12 E0 7C + // STATE_PRINT_MAKE tracks the intermediate step (E0 12 detected, waiting for E0 7C). + // If we see E0 12, switch to PRINT_MAKE state and consume this pair without emitting. + if extended && !released && scancode == 0x12 && !in_print_make && !in_print_break { + *state = (*state & !(KeyboardState::STATE_EXTENDED | KeyboardState::STATE_RELEASE)) + | KeyboardState::STATE_PRINT_MAKE; + return None; + } + + // Emit PrintScreen press when we complete the make sequence. + // We have consumed all 4 bytes: E0 12 E0 7C. Emit the key event and clear all flags. + if extended && !released && scancode == 0x7C && in_print_make { + *state = KeyboardState::clear_all_flags(*state); + return Some(KeyEvent::new(KeyCode::PrintScreen, KeyState::Pressed)); + } + + // Handle PrintScreen break sequence: E0 F0 7C E0 F0 12 + // STATE_PRINT_BREAK tracks the first intermediate step (E0 F0 7C detected, waiting for E0 F0 12). + // If we see E0 F0 7C, switch to PRINT_BREAK state and consume this pair without emitting. + if extended && released && scancode == 0x7C && !in_print_make && !in_print_break { + *state = (*state & !(KeyboardState::STATE_EXTENDED | KeyboardState::STATE_RELEASE)) + | KeyboardState::STATE_PRINT_BREAK; + return None; + } + + // Emit PrintScreen release when we complete the break sequence. + // We have consumed all 6 bytes: E0 F0 7C E0 F0 12. Emit the key event and clear all flags. + if extended && released && scancode == 0x12 && in_print_break { + *state = KeyboardState::clear_all_flags(*state); + return Some(KeyEvent::new(KeyCode::PrintScreen, KeyState::Released)); + } + + // If we were building a PrintScreen sequence but got an unexpected byte, + // abort the sequence to prevent state corruption. + if in_print_make || in_print_break { + *state = KeyboardState::abort_incomplete_sequence(*state); + return None; + } + + // Determine key state from the release flag. + // Sets 2 and 3 use an 0xF0 prefix to indicate release; without it, the key is pressed. + let pressed = if released { + KeyState::Released + } else { + KeyState::Pressed + }; + + *state = KeyboardState::clear_all_flags(*state); + + let keycode = Ps2Set2Keycodes::map(extended, scancode)?; + Some(KeyEvent::new(keycode, pressed)) + } +} diff --git a/kernel/src/drivers/ps2/keyboard/keycodes.rs b/kernel/src/drivers/ps2/keyboard/keycodes.rs new file mode 100644 index 0000000..cb7fad5 --- /dev/null +++ b/kernel/src/drivers/ps2/keyboard/keycodes.rs @@ -0,0 +1,249 @@ +use beskar_core::drivers::keyboard::KeyCode; + +/// Scancode to KeyCode mapping for PS/2 Set 1. +pub struct Ps2Set1Keycodes; + +impl Ps2Set1Keycodes { + #[must_use] + #[expect(clippy::too_many_lines, reason = "Mapping tables are inherently long")] + pub fn map(extended: bool, scancode: u8) -> Option { + let keycode = match (extended, scancode) { + (false, 0x01) => Some(KeyCode::Escape), + + (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, 0x45) => Some(KeyCode::NumLock), + (false, 0x46) => Some(KeyCode::ScrollLock), + (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, 0x53) => Some(KeyCode::NumpadDot), + + (true, 0x1C) => Some(KeyCode::NumpadEnter), + (true, 0x35) => Some(KeyCode::NumpadDiv), + + (true, 0x1D) => Some(KeyCode::CtrlRight), + (true, 0x38) => Some(KeyCode::AltRight), + + (true, 0x47) => Some(KeyCode::Home), + (true, 0x49) => Some(KeyCode::PageUp), + (true, 0x4F) => Some(KeyCode::End), + (true, 0x51) => Some(KeyCode::PageDown), + (true, 0x52) => Some(KeyCode::Insert), + (true, 0x53) => Some(KeyCode::Delete), + + (true, 0x48) => Some(KeyCode::ArrowUp), + (true, 0x50) => Some(KeyCode::ArrowDown), + (true, 0x4B) => Some(KeyCode::ArrowLeft), + (true, 0x4D) => Some(KeyCode::ArrowRight), + + (true, 0x5B) => Some(KeyCode::WindowsLeft), + (true, 0x5C) => Some(KeyCode::WindowsRight), + (true, 0x5D) => Some(KeyCode::Menu), + + _ => None, + }?; + + Some(keycode) + } +} + +/// Scancode to KeyCode mapping for PS/2 Set 2 and Set 3. +pub struct Ps2Set2Keycodes; + +impl Ps2Set2Keycodes { + #[must_use] + #[expect(clippy::too_many_lines, reason = "Mapping tables are inherently long")] + pub fn map(extended: bool, scancode: u8) -> Option { + let keycode = match (extended, scancode) { + (false, 0x01) => Some(KeyCode::F9), + (false, 0x03) => Some(KeyCode::F5), + (false, 0x04) => Some(KeyCode::F3), + (false, 0x05) => Some(KeyCode::F1), + (false, 0x06) => Some(KeyCode::F2), + (false, 0x07) => Some(KeyCode::F12), + (false, 0x09) => Some(KeyCode::F10), + (false, 0x0A) => Some(KeyCode::F8), + (false, 0x0B) => Some(KeyCode::F6), + (false, 0x0C) => Some(KeyCode::F4), + (false, 0x0D) => Some(KeyCode::Tab), + (false, 0x0E) => Some(KeyCode::Tilde), + (false, 0x11) => Some(KeyCode::AltLeft), + (false, 0x12) => Some(KeyCode::ShiftLeft), + (false, 0x14) => Some(KeyCode::CtrlLeft), + (false, 0x15) => Some(KeyCode::Q), + (false, 0x16) => Some(KeyCode::Num1), + (false, 0x1A) => Some(KeyCode::Z), + (false, 0x1B) => Some(KeyCode::S), + (false, 0x1C) => Some(KeyCode::A), + (false, 0x1D) => Some(KeyCode::W), + (false, 0x1E) => Some(KeyCode::Num2), + (false, 0x21) => Some(KeyCode::C), + (false, 0x22) => Some(KeyCode::X), + (false, 0x23) => Some(KeyCode::D), + (false, 0x24) => Some(KeyCode::E), + (false, 0x25) => Some(KeyCode::Num4), + (false, 0x26) => Some(KeyCode::Num3), + (false, 0x29) => Some(KeyCode::Space), + (false, 0x2A) => Some(KeyCode::V), + (false, 0x2B) => Some(KeyCode::F), + (false, 0x2C) => Some(KeyCode::T), + (false, 0x2D) => Some(KeyCode::R), + (false, 0x2E) => Some(KeyCode::Num5), + (false, 0x31) => Some(KeyCode::N), + (false, 0x32) => Some(KeyCode::B), + (false, 0x33) => Some(KeyCode::H), + (false, 0x34) => Some(KeyCode::G), + (false, 0x35) => Some(KeyCode::Y), + (false, 0x36) => Some(KeyCode::Num6), + (false, 0x3A) => Some(KeyCode::M), + (false, 0x3B) => Some(KeyCode::J), + (false, 0x3C) => Some(KeyCode::U), + (false, 0x3D) => Some(KeyCode::Num7), + (false, 0x3E) => Some(KeyCode::Num8), + (false, 0x41) => Some(KeyCode::Comma), + (false, 0x42) => Some(KeyCode::K), + (false, 0x43) => Some(KeyCode::I), + (false, 0x44) => Some(KeyCode::O), + (false, 0x45) => Some(KeyCode::Num0), + (false, 0x46) => Some(KeyCode::Num9), + (false, 0x49) => Some(KeyCode::Dot), + (false, 0x4A) => Some(KeyCode::Slash), + (false, 0x4B) => Some(KeyCode::L), + (false, 0x4C) => Some(KeyCode::Semicolon), + (false, 0x4D) => Some(KeyCode::P), + (false, 0x4E) => Some(KeyCode::Minus), + (false, 0x52) => Some(KeyCode::Apostrophe), + (false, 0x54) => Some(KeyCode::LeftBracket), + (false, 0x55) => Some(KeyCode::Equal), + (false, 0x58) => Some(KeyCode::CapsLock), + (false, 0x59) => Some(KeyCode::ShiftRight), + (false, 0x5A) => Some(KeyCode::Enter), + (false, 0x5B) => Some(KeyCode::RightBracket), + (false, 0x5D) => Some(KeyCode::Backslash), + (false, 0x66) => Some(KeyCode::Backspace), + (false, 0x69) => Some(KeyCode::Numpad1), + (false, 0x6B) => Some(KeyCode::Numpad4), + (false, 0x6C) => Some(KeyCode::Numpad7), + (false, 0x70) => Some(KeyCode::Numpad0), + (false, 0x71) => Some(KeyCode::NumpadDot), + (false, 0x72) => Some(KeyCode::Numpad2), + (false, 0x73) => Some(KeyCode::Numpad5), + (false, 0x74) => Some(KeyCode::Numpad6), + (false, 0x75) => Some(KeyCode::Numpad8), + (false, 0x76) => Some(KeyCode::Escape), + (false, 0x77) => Some(KeyCode::NumLock), + (false, 0x78) => Some(KeyCode::F11), + (false, 0x79) => Some(KeyCode::NumpadAdd), + (false, 0x7A) => Some(KeyCode::Numpad3), + (false, 0x7B) => Some(KeyCode::NumpadSub), + (false, 0x7C) => Some(KeyCode::NumpadMul), + (false, 0x7D) => Some(KeyCode::Numpad9), + (false, 0x7E) => Some(KeyCode::ScrollLock), + (false, 0x83) => Some(KeyCode::F7), + + (true, 0x11) => Some(KeyCode::AltRight), + (true, 0x14) => Some(KeyCode::CtrlRight), + (true, 0x1F) => Some(KeyCode::WindowsLeft), + (true, 0x27) => Some(KeyCode::WindowsRight), + (true, 0x2F) => Some(KeyCode::Menu), + (true, 0x4A) => Some(KeyCode::NumpadDiv), + (true, 0x5A) => Some(KeyCode::NumpadEnter), + (true, 0x69) => Some(KeyCode::End), + (true, 0x6B) => Some(KeyCode::ArrowLeft), + (true, 0x6C) => Some(KeyCode::Home), + (true, 0x70) => Some(KeyCode::Insert), + (true, 0x71) => Some(KeyCode::Delete), + (true, 0x72) => Some(KeyCode::ArrowDown), + (true, 0x74) => Some(KeyCode::ArrowRight), + (true, 0x75) => Some(KeyCode::ArrowUp), + (true, 0x7A) => Some(KeyCode::PageDown), + (true, 0x7D) => Some(KeyCode::PageUp), + + _ => None, + }?; + + Some(keycode) + } +} diff --git a/kernel/src/process/binary/elf/src/lib.rs b/kernel/src/process/binary/elf/src/lib.rs index a1ad0a3..650d344 100644 --- a/kernel/src/process/binary/elf/src/lib.rs +++ b/kernel/src/process/binary/elf/src/lib.rs @@ -74,8 +74,6 @@ #![warn(clippy::pedantic, clippy::nursery)] #![no_std] -extern crate alloc; - mod error; mod loader; pub mod mapper; diff --git a/kernel/src/process/binary/elf/src/loader.rs b/kernel/src/process/binary/elf/src/loader.rs index 9b8dc86..4230f27 100644 --- a/kernel/src/process/binary/elf/src/loader.rs +++ b/kernel/src/process/binary/elf/src/loader.rs @@ -73,6 +73,7 @@ impl ElfLoader { Ok(LoadedBinary { entry_point, tls_template, + image_size: addr_range.size(), }) } diff --git a/kernel/src/process/binary/elf/src/segments.rs b/kernel/src/process/binary/elf/src/segments.rs index 135b650..9d76d16 100644 --- a/kernel/src/process/binary/elf/src/segments.rs +++ b/kernel/src/process/binary/elf/src/segments.rs @@ -20,4 +20,6 @@ pub struct LoadedBinary { pub entry_point: extern "C" fn(), /// TLS template (if present) pub tls_template: Option, + /// Image size in memory + pub image_size: u64, } diff --git a/kernel/src/process/scheduler.rs b/kernel/src/process/scheduler.rs index d8a5664..eca82ba 100644 --- a/kernel/src/process/scheduler.rs +++ b/kernel/src/process/scheduler.rs @@ -6,6 +6,7 @@ use crate::{locals, time::Duration}; use alloc::{boxed::Box, sync::Arc}; use beskar_core::{ + arch::VirtAddr, process::{AtomicSleepReason, SleepHandle, SleepReason}, time::Instant, }; @@ -181,6 +182,12 @@ impl Scheduler { let old_stack = Self::old_stack_pointer(&action, &mut old_thread); let new_stack = thread.last_stack_ptr(); + if let Some(rsp0) = thread.snapshot().kernel_stack_top() + && let Some(tss) = unsafe { locals!().gdt().force_lock() }.tss_mut() + { + tss.privilege_stack_table[0] = VirtAddr::from_ptr(rsp0.as_ptr()); + } + let cr3 = thread.process().address_space().cr3_raw(); if let Some(tls) = thread.tls() { crate::arch::locals::store_thread_locals(tls); diff --git a/kernel/src/process/scheduler/thread.rs b/kernel/src/process/scheduler/thread.rs index ad71ed9..9b73574 100644 --- a/kernel/src/process/scheduler/thread.rs +++ b/kernel/src/process/scheduler/thread.rs @@ -1,5 +1,5 @@ use crate::{ - arch::context::ThreadRegisters, + arch::{context::ThreadRegisters, fpu::FpuState}, mem::frame_alloc, process::binary::{Binary, BinaryType, LoadedBinary}, storage::vfs, @@ -13,6 +13,7 @@ use beskar_core::arch::{ use beskar_hal::instructions::STACK_DEBUG_INSTR; use beskar_hal::paging::page_table::Flags; use core::{ + cell::UnsafeCell, mem::offset_of, ptr::NonNull, sync::atomic::{AtomicPtr, AtomicU64, Ordering}, @@ -69,6 +70,8 @@ pub struct Thread { tls: Once, /// Thread statistics for scheduling stats: ThreadStats, + /// FPU/SSE state for lazy context switching + fpu_state: UnsafeCell, /// Link to the next thread in the queue. link: Link, @@ -124,6 +127,7 @@ impl Thread { link: Link::new(), tls: Once::uninit(), stats: ThreadStats::new(), + fpu_state: UnsafeCell::new(FpuState::new()), } } @@ -150,6 +154,7 @@ impl Thread { link: Link::new(), tls: Once::uninit(), stats: ThreadStats::new(), + fpu_state: UnsafeCell::new(FpuState::new()), } } @@ -170,11 +175,6 @@ impl Thread { "Stack too small" ); - // Push the return address - let entry_point_bytes = (entry_point as usize).to_ne_bytes(); - stack[stack_bottom - size_of::()..stack_bottom].copy_from_slice(&entry_point_bytes); - stack_bottom -= size_of::(); - // Push the thread registers let thread_regs = ThreadRegisters::new(entry_point, stack_ptr); let thread_regs_bytes = unsafe { @@ -200,6 +200,7 @@ impl Thread { link: Link::new(), tls: Once::uninit(), stats: ThreadStats::new(), + fpu_state: UnsafeCell::new(FpuState::new()), } } @@ -274,6 +275,13 @@ impl Thread { self.tls.get().copied() } + #[must_use] + #[inline] + /// Returns a pointer to the FPU state. + pub const fn fpu_state_ptr(&self) -> *mut FpuState { + self.fpu_state.get() + } + #[must_use] /// Get a snapshot of the thread's state. pub fn snapshot(&self) -> ThreadSnapshot { diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 1694fbf..e001210 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -3,6 +3,7 @@ use ::storage::{ vfs::{Vfs, VfsHelper}, }; use alloc::boxed::Box; +use hyperdrive::once::Once; struct VfsHelperStruct; @@ -13,9 +14,10 @@ impl VfsHelper for VfsHelperStruct { } } -static VFS: Vfs = Vfs::new(); +static VFS: Once> = Once::uninit(); pub fn init() { + let vfs = Vfs::new(); let mut device_fs = DeviceFS::new(); device_fs.add_device( PathBuf::new("/keyboard"), @@ -28,12 +30,14 @@ pub fn init() { Box::new(crate::process::SeedFile), ); device_fs.add_device(PathBuf::new("/fb"), Box::new(video::screen::ScreenDevice)); - VFS.mount(PathBuf::new("/dev"), Box::new(device_fs)); + vfs.mount(PathBuf::new("/dev"), Box::new(device_fs)); + + VFS.call_once(|| vfs); } #[must_use] #[inline] /// Returns a reference to the global VFS instance. pub fn vfs() -> &'static Vfs { - &VFS + VFS.get().expect("VFS not initialized") } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 9582cb6..2e70cfb 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -36,6 +36,7 @@ pub fn syscall(syscall: Syscall, args: &Arguments) -> SyscallReturnValue { match syscall { Syscall::Exit => sc_exit(args), Syscall::MemoryMap => SyscallReturnValue::ValueU(sc_mmap(args)), + Syscall::MemoryUnmap => SyscallReturnValue::Code(sc_munmap(args)), Syscall::MemoryProtect => SyscallReturnValue::Code(sc_mprotect(args)), Syscall::Read => SyscallReturnValue::ValueI(sc_read(args)), Syscall::Write => SyscallReturnValue::ValueI(sc_write(args)), @@ -106,6 +107,36 @@ fn sc_mmap(args: &Arguments) -> u64 { page_range.start().start_address().as_u64() } +fn sc_munmap(args: &Arguments) -> SyscallExitCode { + let ptr = args.one; + let size = args.two; + + if size == 0 { + return SyscallExitCode::Success; + } + + let Some(va) = VirtAddr::try_new(ptr) else { + return SyscallExitCode::Failure; + }; + let end = va + (size - 1); + + if !va.is_aligned(beskar_core::arch::Alignment::Align4K) + && !size.is_multiple_of(M4KiB::SIZE) + && !probe(va, end) + { + return SyscallExitCode::Failure; + } + + let page_start = va.page::(); + let page_end = end.page::(); + + let page_range = Page::range_inclusive(page_start, page_end); + + unsafe { process::current().address_space().unmap_free(page_range) }; + + SyscallExitCode::Success +} + #[must_use] fn sc_mprotect(args: &Arguments) -> SyscallExitCode { let ptr = args.one; diff --git a/userspace/bashkar/src/video/ui.rs b/userspace/bashkar/src/video/ui.rs index 2a90478..d3eea40 100644 --- a/userspace/bashkar/src/video/ui.rs +++ b/userspace/bashkar/src/video/ui.rs @@ -149,7 +149,7 @@ pub fn draw() { } const DS_ART: &[&str] = &[ - " ,_~\"\"\"~-, ", + " ,_~\"\"\"~-,", " .'(_)------`,", " |===========|", " `,---------,'", diff --git a/userspace/doom/build.rs b/userspace/doom/build.rs index bb44c9a..4e62025 100644 --- a/userspace/doom/build.rs +++ b/userspace/doom/build.rs @@ -29,6 +29,7 @@ fn main() -> Result<(), Box> { .flag("-w") // compile without simd .flag("-mgeneral-regs-only") + .flag("-flto") .compile("puredoom"); Ok(()) diff --git a/userspace/doom/src/game.rs b/userspace/doom/src/game.rs index 4aabc89..f1713d2 100644 --- a/userspace/doom/src/game.rs +++ b/userspace/doom/src/game.rs @@ -1,5 +1,5 @@ use core::{ - ffi::{c_char, c_void}, + ffi::{CStr, c_char, c_void}, sync::atomic::{AtomicUsize, Ordering}, }; @@ -33,15 +33,14 @@ pub fn init() { unsafe { doom_set_getenv(getenv) }; } -#[cfg(debug_assertions)] 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); + if !cfg!(debug_assertions) { + let s = unsafe { CStr::from_ptr(s) }; + if let Ok(s) = s.to_str() { + beskar_lib::println!("DOOM: {}", s); + } } } -#[cfg(not(debug_assertions))] -extern "C" fn print(_s: *const c_char) {} extern "C" fn malloc(size: i32) -> *mut c_void { // `alloc` states the layout size must be non-zero. @@ -57,8 +56,7 @@ extern "C" fn malloc(size: i32) -> *mut c_void { assert!( !ptr.is_null(), - "malloc failed: out of memory (requested {} bytes)", - size + "malloc failed: out of memory (requested {size} bytes)", ); ptr.cast() diff --git a/userspace/doom/src/input.rs b/userspace/doom/src/input.rs index cf7dbb9..795a978 100644 --- a/userspace/doom/src/input.rs +++ b/userspace/doom/src/input.rs @@ -1,4 +1,4 @@ -use beskar_lib::io::keyboard::{KeyCode, KeyEvent, KeyState, KeyboardReader}; +use beskar_lib::io::keyboard::{KeyCode, KeyState, KeyboardReader}; use hyperdrive::{locks::ticket::TicketLock, once::Once}; #[link(name = "puredoom", kind = "static")] @@ -164,23 +164,18 @@ impl From for DoomKeyT { } } -#[must_use] -#[inline] +/// Polls the keyboard and redistributes events to Doom. +/// /// # Panics /// -/// This function panics if opening the keyboard file fails (only once). -fn poll_keyboard() -> Option { +/// This function will panic if the keyboard reader cannot be initialized. +pub fn poll_inputs() { static KEYBOARD_READER: Once> = Once::uninit(); KEYBOARD_READER.call_once(|| TicketLock::new(KeyboardReader::new().unwrap())); let mut reader = KEYBOARD_READER.get().unwrap().lock(); - reader.next_event().unwrap_or(None) -} - -/// Polls the keyboard and redistributes events to Doom. -pub fn poll_inputs() { - while let Some(event) = poll_keyboard() { + while let Ok(Some(event)) = reader.next_event() { let doom_key = DoomKeyT::from(event.key()); match event.pressed() { KeyState::Pressed => unsafe { doom_key_down(doom_key) }, diff --git a/userspace/doom/src/main.rs b/userspace/doom/src/main.rs index de7dd23..4d01d97 100644 --- a/userspace/doom/src/main.rs +++ b/userspace/doom/src/main.rs @@ -22,8 +22,8 @@ fn main() { unsafe { doom_init(1, argv.as_ptr(), 0b111) }; loop { + doom::input::poll_inputs(); unsafe { doom_update() }; doom::screen::draw(); - doom::input::poll_inputs(); } } diff --git a/userspace/doom/src/screen.rs b/userspace/doom/src/screen.rs index 1119761..73d2976 100644 --- a/userspace/doom/src/screen.rs +++ b/userspace/doom/src/screen.rs @@ -24,6 +24,8 @@ pub fn init() { SCREEN_INFO.call_once(|| SCREEN.with_locked(|screen| *screen.info())); } +#[must_use] +#[inline] /// Returns the screen info. /// /// # Panics @@ -33,29 +35,33 @@ fn screen_info() -> &'static Info { SCREEN_INFO.get().unwrap() } +#[inline] fn with_screen R>(f: F) -> R { SCREEN.with_locked(f) } /// Draw the Doom framebuffer to the screen pub fn draw() { - let fb_start = unsafe { doom_get_framebuffer(CHANNELS.try_into().unwrap()) }; - let fb_raw = core::ptr::slice_from_raw_parts(fb_start, SCREENWIDTH * SCREENHEIGHT * CHANNELS); + let fb_start = unsafe { doom_get_framebuffer(CHANNELS as i32) }; - let Some(fb) = (unsafe { fb_raw.as_ref() }) else { - beskar_lib::println!("Warning: Doom framebuffer is not initialized"); - return; - }; + let info = screen_info(); + let stride_bytes = usize::from(info.stride()) * usize::from(info.bytes_per_pixel()); + let row_size = SCREENWIDTH * CHANNELS; - let stride = usize::from(screen_info().stride()); - let bpp = usize::from(screen_info().bytes_per_pixel()); - let stride_bytes = stride * bpp; with_screen(|screen| { - let mut buffer_mut = screen.buffer_mut(); - for row in fb.chunks_exact(SCREENWIDTH * CHANNELS) { - buffer_mut[..SCREENWIDTH * CHANNELS].copy_from_slice(row); - buffer_mut = &mut buffer_mut[stride_bytes..]; + let dst = screen.buffer_mut().as_mut_ptr(); + let src = fb_start; + + for y in 0..SCREENHEIGHT { + unsafe { + core::ptr::copy_nonoverlapping( + src.add(y * row_size), + dst.add(y * stride_bytes), + row_size, + ); + } } - let _ = screen.flush_rows(0..u16::try_from(SCREENHEIGHT).unwrap()); + + let _ = screen.flush_rows(0..SCREENHEIGHT as u16); }); }