diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 2e4691cf..54355de8 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -372,6 +372,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ps2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfef03bb1362e6a23a211efe8c72e2eeb6888f51fb329cdfa07bb470986ea7d0" +dependencies = [ + "bitflags 1.3.2", + "x86_64 0.14.13", +] + [[package]] name = "quote" version = "1.0.40" @@ -527,13 +537,14 @@ dependencies = [ "limine", "log", "pc-keyboard", + "ps2", "rand", "raw-cpuid 11.5.0", "smoltcp", "spin", "talc", "uart_16550", - "x86_64", + "x86_64 0.15.2", "zerocopy", ] @@ -597,6 +608,18 @@ dependencies = [ "raw-cpuid 10.7.0", ] +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags 2.9.0", + "rustversion", + "volatile", +] + [[package]] name = "x86_64" version = "0.15.2" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 65de2f92..52d9527d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -56,3 +56,4 @@ futures-util = { version = "0.3.31", default-features = false, features = [ "async-await-macro", "futures-macro", ] } +ps2 = "0.2.0" diff --git a/kernel/limage_config.toml b/kernel/limage_config.toml index c971a2d8..4a25dab9 100644 --- a/kernel/limage_config.toml +++ b/kernel/limage_config.toml @@ -29,7 +29,7 @@ base_args = [ # Graphics "-vga", "std", - +"-serial", "stdio", # Debugging traces # USB # "-trace", "usb*", @@ -44,7 +44,7 @@ base_args = [ [modes] terminal = { args = ["-nographic"] } -gui = { args = [] } +gui = { args = ["-serial", "stdio"] } gdb-terminal = { args = ["-nographic", "-s", "-S"] } gdb-gui = { args = ["-s", "-S"] } @@ -54,6 +54,5 @@ success_exit_code = 33 no_reboot = true extra_args = [ "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-serial", "stdio", "-display", "none" ] diff --git a/kernel/src/constants/syscalls.rs b/kernel/src/constants/syscalls.rs index a73c032c..3e407ede 100644 --- a/kernel/src/constants/syscalls.rs +++ b/kernel/src/constants/syscalls.rs @@ -8,6 +8,9 @@ pub const SYSCALL_MPROTECT: u32 = 10; pub const SYSCALL_MUNMAP: u32 = 11; pub const SYSCALL_FORK: u32 = 5; pub const SYSCALL_WAIT: u32 = 6; +pub const SYSCALL_READ: u32 = 0; +pub const SYSCALL_WRITE: u32 = 1; +pub const SYSCALL_EXECVE: u32 = 59; // Mmap pub const START_MMAP_ADDRESS: u64 = 0x0900_0000_0000; diff --git a/kernel/src/devices/mod.rs b/kernel/src/devices/mod.rs index 4840b9c8..b6fd0488 100644 --- a/kernel/src/devices/mod.rs +++ b/kernel/src/devices/mod.rs @@ -11,10 +11,9 @@ use sd_card::{find_sd_card, initalize_sd_card}; use xhci::{find_xhci_inferface, initalize_xhci_hub}; pub mod graphics; use graphics::framebuffer::{self, colors}; -pub mod keyboard; pub mod mmio; -pub mod mouse; pub mod pci; +pub mod ps2_dev; pub mod sd_card; pub mod serial; pub mod xhci; @@ -74,7 +73,6 @@ pub fn init(cpu_id: u32) { find_xhci_inferface(&devices).expect("Build system currently sets up xhci device"); initalize_xhci_hub(&xhci_device).unwrap(); - keyboard::init().expect("Failed to initialize keyboard"); - mouse::init().expect("Failed to initialize mouse"); + ps2_dev::init(); } } diff --git a/kernel/src/devices/mouse.rs b/kernel/src/devices/mouse.rs deleted file mode 100644 index 156fb7f0..00000000 --- a/kernel/src/devices/mouse.rs +++ /dev/null @@ -1,632 +0,0 @@ -//! Mouse management for PS/2 compatible mice -//! -//! Handles mouse initialization, event processing, and provides both -//! synchronous and asynchronous interfaces for mouse events - -use crate::{ - events::schedule_kernel, - interrupts::{idt::without_interrupts, x2apic}, - serial_println, -}; -use core::{ - fmt, - pin::Pin, - sync::atomic::{AtomicBool, AtomicU64, Ordering}, - task::{Context, Poll, Waker}, -}; -use futures_util::stream::{Stream, StreamExt}; -use spin::Mutex; -use x86_64::{instructions::port::Port, structures::idt::InterruptStackFrame}; - -/// Maximum number of mouse events to store in the buffer -const MOUSE_BUFFER_SIZE: usize = 32; - -/// The global mouse state -pub static MOUSE: Mutex = Mutex::new(MouseState::new()); - -/// Has the mouse been initialized -static MOUSE_INITIALIZED: AtomicBool = AtomicBool::new(false); - -/// The number of mouse interrupts received -static MOUSE_INTERRUPT_COUNT: AtomicU64 = AtomicU64::new(0); - -/// Wake task waiting for mouse input -static MOUSE_WAKER: Mutex> = Mutex::new(None); - -/// PS/2 mouse error types -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MouseError { - /// PS/2 controller initialization error - ControllerInitError, - /// Timeout waiting for PS/2 controller to be ready - ControllerTimeout, - /// Mouse failed to acknowledge a command - CommandAckFailed, - /// Invalid packet data - InvalidPacketData, - /// Buffer overflow - BufferOverflow, - /// Mouse already initialized - AlreadyInitialized, -} - -/// Result type for mouse operations -pub type MouseResult = core::result::Result; - -impl fmt::Display for MouseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ControllerInitError => write!(f, "PS/2 controller initialization error"), - Self::ControllerTimeout => write!(f, "Timeout waiting for PS/2 controller"), - Self::CommandAckFailed => write!(f, "Mouse did not acknowledge command"), - Self::InvalidPacketData => write!(f, "Invalid mouse packet data"), - Self::BufferOverflow => write!(f, "Mouse event buffer overflow"), - Self::AlreadyInitialized => write!(f, "Mouse already initialized"), - } - } -} - -/// PS/2 mouse packet structure -struct MousePacket { - flags: u8, - x_movement: i8, - y_movement: i8, - z_movement: i8, // Scroll wheel -} - -/// Button state for mouse buttons -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ButtonState { - /// Button is pressed - Pressed, - /// Button is released - Released, -} - -/// Mouse button identifiers -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MouseButton { - /// Left mouse button - Left, - /// Right mouse button - Right, - /// Middle mouse button (scroll wheel click) - Middle, -} - -/// Represents a mouse event with additional metadata -#[derive(Debug, Clone)] -pub struct MouseEvent { - /// X movement delta - pub dx: i8, - /// Y movement delta - pub dy: i8, - /// Z movement delta (scroll wheel) - pub dz: i8, - /// Left button state - pub left_button: ButtonState, - /// Right button state - pub right_button: ButtonState, - /// Middle button state - pub middle_button: ButtonState, - /// Absolute X position at time of event - pub x: i16, - /// Absolute Y position at time of event - pub y: i16, -} - -/// Structure to track mouse state -#[derive(Debug)] -pub struct MouseState { - /// Circular buffer for mouse events - buffer: [Option; MOUSE_BUFFER_SIZE], - /// Read position in the buffer - read_pos: usize, - /// Write position in the buffer - write_pos: usize, - /// Is the buffer full - full: bool, - /// Current packet being assembled - packet_bytes: [u8; 4], - /// Current position in the packet - packet_index: usize, - /// Previous button states for tracking changes - prev_left_button: ButtonState, - prev_right_button: ButtonState, - prev_middle_button: ButtonState, - /// Current mouse X position - mouse_x: i16, - /// Current mouse Y position - mouse_y: i16, - /// Maximum X position (screen width - 1) - max_x: i16, - /// Maximum Y position (screen height - 1) - max_y: i16, -} - -/// Stream that yields mouse events -pub struct MouseStream; - -impl Default for MouseState { - fn default() -> Self { - Self::new() - } -} - -impl MouseState { - /// Create a new mouse state - pub const fn new() -> Self { - const NONE_OPTION: Option = None; - Self { - buffer: [NONE_OPTION; MOUSE_BUFFER_SIZE], - read_pos: 0, - write_pos: 0, - full: false, - packet_bytes: [0; 4], - packet_index: 0, - prev_left_button: ButtonState::Released, - prev_right_button: ButtonState::Released, - prev_middle_button: ButtonState::Released, - mouse_x: 0, - mouse_y: 0, - // Default to VGA text mode dimensions, can be updated with set_bounds - max_x: 79, - max_y: 24, - } - } - - /// Set the screen boundaries for mouse movement - pub fn set_bounds(&mut self, width: i16, height: i16) { - self.max_x = width - 1; - self.max_y = height - 1; - - self.clamp_position(); - } - - /// Ensure mouse position stays within bounds - fn clamp_position(&mut self) { - if self.mouse_x < 0 { - self.mouse_x = 0; - } else if self.mouse_x > self.max_x { - self.mouse_x = self.max_x; - } - - if self.mouse_y < 0 { - self.mouse_y = 0; - } else if self.mouse_y > self.max_y { - self.mouse_y = self.max_y; - } - } - - /// Get current mouse position - pub fn get_position(&self) -> (i16, i16) { - (self.mouse_x, self.mouse_y) - } - - /// Set mouse position - pub fn set_position(&mut self, x: i16, y: i16) { - self.mouse_x = x; - self.mouse_y = y; - self.clamp_position(); - } - - /// Check if the buffer is empty - pub fn is_empty(&self) -> bool { - !self.full && self.read_pos == self.write_pos - } - - /// Process a mouse data byte - pub fn process_mouse_byte(&mut self, data: u8) -> MouseResult<()> { - self.packet_bytes[self.packet_index] = data; - self.packet_index += 1; - - // Standard PS/2 mouse packets are 3 bytes - // With scroll wheel, we need 4 bytes - if self.packet_index >= 3 { - let packet = MousePacket { - flags: self.packet_bytes[0], - x_movement: self.packet_bytes[1] as i8, - y_movement: self.packet_bytes[2] as i8, - z_movement: if self.packet_index >= 4 { - self.packet_bytes[3] as i8 - } else { - 0 - }, - }; - - self.process_packet(packet)?; - self.packet_index = 0; - } - - Ok(()) - } - - /// Process a complete mouse packet and create an event - fn process_packet(&mut self, packet: MousePacket) -> MouseResult<()> { - // Extract button states - let left_button = if packet.flags & 0x01 != 0 { - ButtonState::Pressed - } else { - ButtonState::Released - }; - - let right_button = if packet.flags & 0x02 != 0 { - ButtonState::Pressed - } else { - ButtonState::Released - }; - - let middle_button = if packet.flags & 0x04 != 0 { - ButtonState::Pressed - } else { - ButtonState::Released - }; - - // Handle movement - let mut dx = packet.x_movement; - let mut dy = packet.y_movement; - - // PS/2 mouse protocol: Overflow bits - if packet.flags & 0x40 != 0 { - // X overflow - dx = if dx > 0 { 127 } else { -128 }; - } - if packet.flags & 0x80 != 0 { - // Y overflow - dy = if dy > 0 { 127 } else { -128 }; - } - - // PS/2 Y-axis is inverted - dy = if dy == -128 { 127 } else { -dy }; - - // Update absolute position - let new_x = self.mouse_x + dx as i16; - let new_y = self.mouse_y + dy as i16; - - self.mouse_x = new_x; - self.mouse_y = new_y; - - // Ensure position stays within bounds - self.clamp_position(); - - // Create event only if there's actual movement or button state change - if dx != 0 - || dy != 0 - || packet.z_movement != 0 - || left_button != self.prev_left_button - || right_button != self.prev_right_button - || middle_button != self.prev_middle_button - { - // Create event - let event = MouseEvent { - dx, - dy, - dz: packet.z_movement, - left_button, - right_button, - middle_button, - x: self.mouse_x, - y: self.mouse_y, - }; - - if self.push_event(event).is_err() { - // Buffer overflow, but we can continue processing - serial_println!("Warning: Mouse event buffer overflow"); - } - - // Update previous button states - self.prev_left_button = left_button; - self.prev_right_button = right_button; - self.prev_middle_button = middle_button; - } - - Ok(()) - } - - /// Push an event to the buffer - fn push_event(&mut self, event: MouseEvent) -> MouseResult<()> { - if self.full { - self.read_pos = (self.read_pos + 1) % MOUSE_BUFFER_SIZE; - } - - self.buffer[self.write_pos] = Some(event); - - self.write_pos = (self.write_pos + 1) % MOUSE_BUFFER_SIZE; - - if self.write_pos == self.read_pos { - self.full = true; - } - - without_interrupts(|| { - let mut waker = MOUSE_WAKER.lock(); - if let Some(w) = waker.take() { - w.wake(); - } - }); - - Ok(()) - } - - /// Read a mouse event from the buffer - pub fn read_event(&mut self) -> Option { - if self.is_empty() { - return None; - } - - let event = self.buffer[self.read_pos].clone(); - - self.buffer[self.read_pos] = None; - self.read_pos = (self.read_pos + 1) % MOUSE_BUFFER_SIZE; - - self.full = false; - - event - } - - /// Clear mouse buffer - pub fn clear_buffer(&mut self) { - const NONE_OPTION: Option = None; - self.buffer = [NONE_OPTION; MOUSE_BUFFER_SIZE]; - self.read_pos = 0; - self.write_pos = 0; - self.full = false; - } -} - -/// PS/2 mouse commands -#[repr(u8)] -#[allow(dead_code)] -enum PS2Command { - /// Reset the mouse - Reset = 0xFF, - /// Set defaults - SetDefaults = 0xF6, - /// Disable data reporting - DisableReporting = 0xF5, - /// Enable data reporting - EnableReporting = 0xF4, - /// Set sample rate - SetSampleRate = 0xF3, - /// Get device ID - GetDeviceId = 0xF2, - /// Set resolution - SetResolution = 0xE8, - /// Status request - StatusRequest = 0xE9, -} - -/// PS/2 controller commands -#[repr(u8)] -#[allow(dead_code)] -enum PS2ControllerCommand { - /// Read controller configuration byte - ReadConfig = 0x20, - /// Write controller configuration byte - WriteConfig = 0x60, - /// Disable second PS/2 port - DisablePort2 = 0xA7, - /// Enable second PS/2 port - EnablePort2 = 0xA8, - /// Test second PS/2 port - TestPort2 = 0xA9, - /// Test PS/2 controller - TestController = 0xAA, - /// Test first PS/2 port - TestPort1 = 0xAB, - /// Disable first PS/2 port - DisablePort1 = 0xAD, - /// Enable first PS/2 port - EnablePort1 = 0xAE, - /// Write to second PS/2 port - WritePort2 = 0xD4, -} - -/// PS/2 port addresses -struct PS2Port; - -impl PS2Port { - /// Data port (read/write) - const DATA: u16 = 0x60; - /// Status register (read), command register (write) - const CMD_STATUS: u16 = 0x64; -} - -/// PS/2 controller response bytes -struct PS2Response; - -#[allow(dead_code)] -impl PS2Response { - /// Command acknowledgement - const ACK: u8 = 0xFA; - /// Command not recognized - const RESEND: u8 = 0xFE; - /// Self-test passed - const TEST_PASSED: u8 = 0xAA; -} - -/// Initialize the mouse -pub fn init() -> MouseResult<()> { - if MOUSE_INITIALIZED.load(Ordering::SeqCst) { - return Err(MouseError::AlreadyInitialized); - } - - wait_write_ready()?; - - let mut command_port = Port::new(PS2Port::CMD_STATUS); - unsafe { - command_port.write(PS2ControllerCommand::WritePort2 as u8); - } - - wait_write_ready()?; - - // Tell the mouse to enable data reporting - let mut data_port = Port::new(PS2Port::DATA); - unsafe { - data_port.write(PS2Command::EnableReporting as u8); - } - - wait_read_ready()?; - let response: u8 = unsafe { data_port.read() }; - - if response != PS2Response::ACK { - return Err(MouseError::CommandAckFailed); - } - - MOUSE_INITIALIZED.store(true, Ordering::SeqCst); - - Ok(()) -} - -/// Wait for the PS/2 controller to be ready for reading -fn wait_read_ready() -> MouseResult<()> { - let mut status_port = Port::new(PS2Port::CMD_STATUS); - - for _ in 0..1000 { - let status: u8 = unsafe { status_port.read() }; - if status & 0x01 != 0 { - return Ok(()); - } - } - - Err(MouseError::ControllerTimeout) -} - -/// Wait for the PS/2 controller to be ready for writing -fn wait_write_ready() -> MouseResult<()> { - let mut status_port = Port::::new(PS2Port::CMD_STATUS); - - for _ in 0..1000 { - let status: u8 = unsafe { status_port.read() }; - if status & 0x02 == 0 { - return Ok(()); - } - } - - Err(MouseError::ControllerTimeout) -} - -/// Send a command to the PS/2 mouse -fn mouse_send_command(command: PS2Command) -> MouseResult { - wait_write_ready()?; - - let mut command_port = Port::new(PS2Port::CMD_STATUS); - unsafe { - command_port.write(PS2ControllerCommand::WritePort2 as u8); - } - - wait_write_ready()?; - - let mut data_port = Port::new(PS2Port::DATA); - unsafe { - data_port.write(command as u8); - } - - wait_read_ready()?; - let response: u8 = unsafe { data_port.read() }; - - Ok(response) -} - -/// Get a stream of mouse events -pub fn get_stream() -> MouseStream { - MouseStream -} - -/// Wait for and return the next mouse event -pub async fn next_event() -> MouseEvent { - MouseStream.next().await.unwrap() -} - -/// Read a mouse event without waiting -pub fn try_read_event() -> Option { - without_interrupts(|| MOUSE.lock().read_event()) -} - -/// Get current mouse position -pub fn get_position() -> (i16, i16) { - without_interrupts(|| { - let mouse = MOUSE.lock(); - mouse.get_position() - }) -} - -/// Set mouse position -pub fn set_position(x: i16, y: i16) { - without_interrupts(|| { - let mut mouse = MOUSE.lock(); - mouse.set_position(x, y); - }) -} - -/// Set screen boundaries for mouse movement -pub fn set_bounds(width: i16, height: i16) { - without_interrupts(|| { - let mut mouse = MOUSE.lock(); - mouse.set_bounds(width, height); - }) -} - -/// Reset the mouse to default settings -pub fn reset() -> MouseResult<()> { - if !MOUSE_INITIALIZED.load(Ordering::SeqCst) { - return Err(MouseError::ControllerInitError); - } - - let response = mouse_send_command(PS2Command::Reset)?; - if response != PS2Response::ACK { - return Err(MouseError::CommandAckFailed); - } - - without_interrupts(|| { - let mut mouse = MOUSE.lock(); - mouse.clear_buffer(); - mouse.set_position(0, 0); - }); - - Ok(()) -} - -/// Get mouse interrupt count -pub fn get_interrupt_count() -> u64 { - MOUSE_INTERRUPT_COUNT.load(Ordering::SeqCst) -} - -/// Mouse interrupt handler -pub extern "x86-interrupt" fn mouse_handler(_frame: InterruptStackFrame) { - MOUSE_INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); - - let mut data_port = Port::new(PS2Port::DATA); - let data: u8 = unsafe { data_port.read() }; - - schedule_kernel( - async move { - let mut mouse = MOUSE.lock(); - if let Err(e) = mouse.process_mouse_byte(data) { - serial_println!("Error processing mouse data: {:?}", e); - } - }, - 0, - ); - - x2apic::send_eoi(); -} - -impl Stream for MouseStream { - type Item = MouseEvent; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let mut mouse = MOUSE.lock(); - - if let Some(event) = mouse.read_event() { - return Poll::Ready(Some(event)); - } - - // No event available, register waker for notification - without_interrupts(|| { - let mut waker = MOUSE_WAKER.lock(); - *waker = Some(cx.waker().clone()); - }); - - Poll::Pending - } -} diff --git a/kernel/src/devices/ps2_dev/controller.rs b/kernel/src/devices/ps2_dev/controller.rs new file mode 100644 index 00000000..bd4e84b5 --- /dev/null +++ b/kernel/src/devices/ps2_dev/controller.rs @@ -0,0 +1,123 @@ +//! PS/2 Controller management +//! +//! This module handles the low-level interaction with the PS/2 controller +//! using the ps2 crate to interface with the hardware. + +use crate::serial_println; +use core::sync::atomic::{AtomicBool, Ordering}; +use ps2::{error::ControllerError, flags::ControllerConfigFlags, Controller}; +use spin::Mutex; + +/// The global PS/2 controller +static PS2_CONTROLLER: Mutex> = Mutex::new(None); + +/// Has the controller been initialized +static CONTROLLER_INITIALIZED: AtomicBool = AtomicBool::new(false); + +/// Initialize the PS/2 controller +pub fn init() -> Result<(), &'static str> { + if CONTROLLER_INITIALIZED.load(Ordering::SeqCst) { + return Ok(()); + } + + // Create a new controller instance and configure it + match initialize_controller() { + Ok(controller) => { + let mut controller_lock = PS2_CONTROLLER.lock(); + *controller_lock = Some(controller); + CONTROLLER_INITIALIZED.store(true, Ordering::SeqCst); + Ok(()) + } + Err(e) => { + serial_println!("PS/2 controller initialization failed: {:?}", e); + Err("PS/2 controller initialization failed") + } + } +} + +/// Initialize and configure the PS/2 controller +fn initialize_controller() -> Result { + // Create a new controller instance + let mut controller = unsafe { Controller::new() }; + + // Step 1: Disable devices during configuration + controller.disable_keyboard()?; + controller.disable_mouse()?; + + // Step 2: Flush any pending data + let _ = controller.read_data(); + + // Step 3: Set controller configuration + let mut config = controller.read_config()?; + + // Disable interrupts and scancode translation during setup + config.set( + ControllerConfigFlags::ENABLE_KEYBOARD_INTERRUPT + | ControllerConfigFlags::ENABLE_MOUSE_INTERRUPT + | ControllerConfigFlags::ENABLE_TRANSLATE, + false, + ); + + controller.write_config(config)?; + + // Step 4: Perform controller self-test + controller.test_controller()?; + + // Step 5: Test PS/2 ports + let keyboard_works = controller.test_keyboard().is_ok(); + let mouse_works = controller.test_mouse().is_ok(); + + // Step 6: Enable devices + config = controller.read_config()?; + + if keyboard_works { + controller.enable_keyboard()?; + config.set(ControllerConfigFlags::DISABLE_KEYBOARD, false); + config.set(ControllerConfigFlags::ENABLE_KEYBOARD_INTERRUPT, true); + } + + if mouse_works { + controller.enable_mouse()?; + config.set(ControllerConfigFlags::DISABLE_MOUSE, false); + config.set(ControllerConfigFlags::ENABLE_MOUSE_INTERRUPT, true); + } + + // Step 7: Write the final configuration + controller.write_config(config)?; + + Ok(controller) +} + +/// Perform an operation with the PS/2 controller +/// +/// This function takes a closure that is given a mutable reference to the controller +/// and returns the result of that closure. This ensures the controller is only +/// accessed while the mutex is held. +pub fn with_controller(f: F) -> Option +where + F: FnOnce(&mut Controller) -> R, +{ + let mut lock = PS2_CONTROLLER.lock(); + (*lock).as_mut().map(f) +} + +/// Check if the PS/2 controller is initialized +pub fn is_initialized() -> bool { + CONTROLLER_INITIALIZED.load(Ordering::SeqCst) +} + +/// Reset the PS/2 controller +pub fn reset() -> Result<(), &'static str> { + if !is_initialized() { + return Err("PS/2 controller not initialized"); + } + + match initialize_controller() { + Ok(controller) => { + let mut controller_lock = PS2_CONTROLLER.lock(); + *controller_lock = Some(controller); + Ok(()) + } + Err(_) => Err("Failed to reset PS/2 controller"), + } +} diff --git a/kernel/src/devices/keyboard.rs b/kernel/src/devices/ps2_dev/keyboard.rs similarity index 52% rename from kernel/src/devices/keyboard.rs rename to kernel/src/devices/ps2_dev/keyboard.rs index d065d5ad..5e636930 100644 --- a/kernel/src/devices/keyboard.rs +++ b/kernel/src/devices/ps2_dev/keyboard.rs @@ -1,21 +1,19 @@ -//! Keyboard management +//! PS/2 Keyboard management //! -//! Currently does not support fancy stuff like key repeats -use crate::{ - interrupts::{idt::without_interrupts, x2apic}, - serial_println, -}; +//! This module handles keyboard initialization, event processing, +//! and provides both synchronous and asynchronous interfaces for keyboard events. + +use crate::{devices::ps2_dev::controller, interrupts::idt::without_interrupts, serial_println}; use core::{ pin::Pin, - sync::atomic::{AtomicBool, AtomicU64, Ordering}, + sync::atomic::{AtomicU64, Ordering}, task::{Context, Poll, Waker}, }; -use futures_util::stream::{Stream, StreamExt}; // StreamExt trait for .next() method +use futures_util::stream::{Stream, StreamExt}; use pc_keyboard::{ - layouts, DecodedKey, Error, HandleControl, KeyCode, KeyState, Keyboard, Modifiers, ScancodeSet1, + layouts, DecodedKey, Error, HandleControl, KeyCode, KeyState, Keyboard, Modifiers, ScancodeSet2, }; use spin::Mutex; -use x86_64::{instructions::port::Port, structures::idt::InterruptStackFrame}; /// Maximum number of keyboard events to store in the buffer const KEYBOARD_BUFFER_SIZE: usize = 32; @@ -23,32 +21,50 @@ const KEYBOARD_BUFFER_SIZE: usize = 32; /// The global keyboard state pub static KEYBOARD: Mutex = Mutex::new(KeyboardState::new()); -/// Has the keyboard been initialized -static KEYBOARD_INITIALIZED: AtomicBool = AtomicBool::new(false); - /// The number of keyboard interrupts received static KEYBOARD_INTERRUPT_COUNT: AtomicU64 = AtomicU64::new(0); /// Wake task waiting for keyboard input static KEYBOARD_WAKER: Mutex> = Mutex::new(None); +/// Keyboard error types +#[derive(Debug, Clone, Copy)] +pub enum KeyboardError { + /// PS/2 controller initialization error + ControllerError, + /// Keyboard command error + CommandError, + /// Invalid scancode + InvalidScancode, + /// PC Keyboard error + PCKeyboardError(Error), +} + +impl From for KeyboardError { + fn from(error: Error) -> Self { + KeyboardError::PCKeyboardError(error) + } +} + /// Represents a key event with additional metadata #[derive(Debug, Clone)] -pub struct BufferKeyEvent { +pub struct KeyboardEvent { /// The key code from the event pub key_code: KeyCode, /// The key state (up or down) pub state: KeyState, /// The decoded key if applicable pub decoded: Option, + /// The raw scancode + pub scancode: u8, } -/// Structure to track keyboard state +/// Keyboard state structure pub struct KeyboardState { /// The pc_keyboard handler - keyboard: Keyboard, - /// Circular buffer for key events - buffer: [Option; KEYBOARD_BUFFER_SIZE], + keyboard: Keyboard, + /// Circular buffer for keyboard events + buffer: [Option; KEYBOARD_BUFFER_SIZE], /// Read position in the buffer read_pos: usize, /// Write position in the buffer @@ -69,10 +85,10 @@ impl Default for KeyboardState { impl KeyboardState { /// Create a new keyboard state pub const fn new() -> Self { - const NONE_OPTION: Option = None; + const NONE_OPTION: Option = None; Self { keyboard: Keyboard::new( - ScancodeSet1::new(), + ScancodeSet2::new(), layouts::Us104Key, HandleControl::Ignore, ), @@ -88,31 +104,31 @@ impl KeyboardState { !self.full && self.read_pos == self.write_pos } - /// Process a scancode and add resulting key events to the buffer - pub fn process_scancode(&mut self, scancode: u8) -> Result<(), Error> { + /// Process a scancode + pub fn process_scancode(&mut self, scancode: u8) -> Result<(), KeyboardError> { if let Some(key_event) = self.keyboard.add_byte(scancode)? { let decoded = self.keyboard.process_keyevent(key_event.clone()); - let buff_event = BufferKeyEvent { + let event = KeyboardEvent { key_code: key_event.code, state: key_event.state, decoded, + scancode, }; - self.push_event(buff_event); + self.push_event(event)?; } Ok(()) } /// Push an event to the buffer - fn push_event(&mut self, event: BufferKeyEvent) { + fn push_event(&mut self, event: KeyboardEvent) -> Result<(), KeyboardError> { if self.full { self.read_pos = (self.read_pos + 1) % KEYBOARD_BUFFER_SIZE; } self.buffer[self.write_pos] = Some(event); - self.write_pos = (self.write_pos + 1) % KEYBOARD_BUFFER_SIZE; if self.write_pos == self.read_pos { @@ -125,19 +141,19 @@ impl KeyboardState { w.wake(); } }); + + Ok(()) } - /// Read a key event from the buffer - pub fn read_event(&mut self) -> Option { + /// Read a keyboard event from the buffer + pub fn read_event(&mut self) -> Option { if self.is_empty() { return None; } let event = self.buffer[self.read_pos].clone(); - self.buffer[self.read_pos] = None; self.read_pos = (self.read_pos + 1) % KEYBOARD_BUFFER_SIZE; - self.full = false; event @@ -150,7 +166,7 @@ impl KeyboardState { /// Clear keyboard buffer pub fn clear_buffer(&mut self) { - const NONE_OPTION: Option = None; + const NONE_OPTION: Option = None; self.buffer = [NONE_OPTION; KEYBOARD_BUFFER_SIZE]; self.read_pos = 0; self.write_pos = 0; @@ -158,15 +174,22 @@ impl KeyboardState { } } -/// Initialize the keyboard system -pub fn init() -> Result<(), &'static str> { - if KEYBOARD_INITIALIZED.load(Ordering::SeqCst) { - return Ok(()); - } +/// Initialize the keyboard +pub fn init() { + controller::with_controller(initialize_keyboard); +} - KEYBOARD_INITIALIZED.store(true, Ordering::SeqCst); +/// Initialize and reset the keyboard +fn initialize_keyboard(controller: &mut ps2::Controller) { + let mut keyboard = controller.keyboard(); - Ok(()) + keyboard + .reset_and_self_test() + .expect("Failed keyboard reset test"); + + keyboard + .enable_scanning() + .expect("Failed to enable scanning for keyboard"); } /// Get a stream of keyboard events @@ -174,32 +197,65 @@ pub fn get_stream() -> KeyboardStream { KeyboardStream } -/// Wait for and return the next key event -pub async fn next_key() -> BufferKeyEvent { +/// Wait for and return the next keyboard event +pub async fn next_event() -> KeyboardEvent { KeyboardStream.next().await.unwrap() } -/// Read a key without waiting -pub fn try_read_key() -> Option { +/// Try to read a keyboard event without waiting +pub fn try_read_event() -> Option { without_interrupts(|| KEYBOARD.lock().read_event()) } -pub extern "x86-interrupt" fn keyboard_handler(_frame: InterruptStackFrame) { - KEYBOARD_INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); +/// Get keyboard interrupt count +pub fn get_interrupt_count() -> u64 { + KEYBOARD_INTERRUPT_COUNT.load(Ordering::SeqCst) +} - let mut port = Port::new(0x60); - let scancode: u8 = unsafe { port.read() }; +/// Keyboard interrupt handler +pub fn keyboard_handler() { + KEYBOARD_INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); - let mut keyboard = KEYBOARD.lock(); - if let Err(e) = keyboard.process_scancode(scancode) { - serial_println!("Error processing keyboard scancode: {:?}", e); - } + controller::with_controller(|controller| { + // Read from the controller as long as the OUTPUT_FULL bit is set + loop { + let status = controller.read_status(); + if !status.contains(ps2::flags::ControllerStatusFlags::OUTPUT_FULL) { + // No more data available + break; + } - x2apic::send_eoi(); + match controller.read_data() { + Ok(scancode) => { + let mut keyboard = KEYBOARD.lock(); + if let Err(e) = keyboard.process_scancode(scancode) { + serial_println!("Error processing keyboard scancode: {:?}", e); + } + } + Err(_) => { + // If we can't read data despite OUTPUT_FULL being set + serial_println!("Keyboard: Full bit set but got error while reading"); + break; + } + } + } + }); +} +pub fn flush_buffer() { + let mut state = KEYBOARD.lock(); + state.clear_buffer(); + // controller::with_controller(|ctrl| { + // while ctrl + // .read_status() + // .contains(ControllerStatusFlags::OUTPUT_FULL) + // { + // let _ = ctrl.read_data(); // Discard any pending scancodes + // } + // }); } impl Stream for KeyboardStream { - type Item = BufferKeyEvent; + type Item = KeyboardEvent; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut keyboard = KEYBOARD.lock(); diff --git a/kernel/src/devices/ps2_dev/mod.rs b/kernel/src/devices/ps2_dev/mod.rs new file mode 100644 index 00000000..c10422a0 --- /dev/null +++ b/kernel/src/devices/ps2_dev/mod.rs @@ -0,0 +1,35 @@ +//! PS/2 device management module +//! +//! This module provides interfaces for PS/2 devices including +//! keyboard and mouse through the PS/2 controller. + +pub mod controller; +pub mod keyboard; +pub mod mouse; + +use crate::interrupts::x2apic; + +/// Initialize the PS/2 subsystem +pub fn init() { + // Initialize controller first + controller::init().expect("Failed to initialize controller"); + + keyboard::init(); + mouse::init(); +} + +/// PS/2 keyboard interrupt handler +pub extern "x86-interrupt" fn keyboard_interrupt_handler( + _frame: x86_64::structures::idt::InterruptStackFrame, +) { + keyboard::keyboard_handler(); + x2apic::send_eoi(); +} + +/// PS/2 mouse interrupt handler +pub extern "x86-interrupt" fn mouse_interrupt_handler( + _frame: x86_64::structures::idt::InterruptStackFrame, +) { + mouse::mouse_handler(); + x2apic::send_eoi(); +} diff --git a/kernel/src/devices/ps2_dev/mouse.rs b/kernel/src/devices/ps2_dev/mouse.rs new file mode 100644 index 00000000..7f212939 --- /dev/null +++ b/kernel/src/devices/ps2_dev/mouse.rs @@ -0,0 +1,393 @@ +//! PS/2 Mouse management +//! +//! This module handles mouse initialization, event processing, +//! and provides both synchronous and asynchronous interfaces for mouse events. + +use crate::{devices::ps2_dev::controller, interrupts::idt::without_interrupts, serial_println}; +use core::{ + fmt, + pin::Pin, + sync::atomic::{AtomicU64, Ordering}, + task::{Context, Poll, Waker}, +}; +use futures_util::stream::{Stream, StreamExt}; +use ps2::flags::MouseMovementFlags; +use spin::Mutex; + +/// Maximum number of mouse events to store in the buffer +const MOUSE_BUFFER_SIZE: usize = 32; + +/// The global mouse state +pub static MOUSE: Mutex = Mutex::new(MouseState::new()); + +/// The number of mouse interrupts received +static MOUSE_INTERRUPT_COUNT: AtomicU64 = AtomicU64::new(0); + +/// Wake task waiting for mouse input +static MOUSE_WAKER: Mutex> = Mutex::new(None); + +/// PS/2 mouse error types +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MouseError { + /// PS/2 controller initialization error + ControllerError, + /// Mouse command error + CommandError, + /// Invalid packet data + InvalidPacketData, +} + +impl fmt::Display for MouseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ControllerError => write!(f, "PS/2 controller error"), + Self::CommandError => write!(f, "Mouse command error"), + Self::InvalidPacketData => write!(f, "Invalid mouse packet data"), + } + } +} + +/// Button state for mouse buttons +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ButtonState { + /// Button is pressed + Pressed, + /// Button is released + Released, +} + +/// Represents a mouse event with additional metadata +#[derive(Debug, Clone)] +pub struct MouseEvent { + /// X movement delta + pub dx: i8, + /// Y movement delta + pub dy: i8, + /// Z movement delta (scroll wheel) + pub dz: i8, + /// Left button state + pub left_button: ButtonState, + /// Right button state + pub right_button: ButtonState, + /// Middle button state + pub middle_button: ButtonState, + /// Absolute X position at time of event + pub x: i16, + /// Absolute Y position at time of event + pub y: i16, +} + +/// Structure to track mouse state +pub struct MouseState { + /// Circular buffer for mouse events + buffer: [Option; MOUSE_BUFFER_SIZE], + /// Read position in the buffer + read_pos: usize, + /// Write position in the buffer + write_pos: usize, + /// Is the buffer full + full: bool, + /// Previous button states for tracking changes + prev_left_button: ButtonState, + prev_right_button: ButtonState, + prev_middle_button: ButtonState, + /// Current mouse X position + mouse_x: i16, + /// Current mouse Y position + mouse_y: i16, + /// Maximum X position (screen width - 1) + max_x: i16, + /// Maximum Y position (screen height - 1) + max_y: i16, +} + +/// Stream that yields mouse events +pub struct MouseStream; + +impl Default for MouseState { + fn default() -> Self { + Self::new() + } +} + +impl MouseState { + /// Create a new mouse state + pub const fn new() -> Self { + const NONE_OPTION: Option = None; + Self { + buffer: [NONE_OPTION; MOUSE_BUFFER_SIZE], + read_pos: 0, + write_pos: 0, + full: false, + prev_left_button: ButtonState::Released, + prev_right_button: ButtonState::Released, + prev_middle_button: ButtonState::Released, + mouse_x: 0, + mouse_y: 0, + // Default to VGA text mode dimensions, can be updated with set_bounds + max_x: 79, + max_y: 24, + } + } + + /// Set the screen boundaries for mouse movement + pub fn set_bounds(&mut self, width: i16, height: i16) { + self.max_x = width - 1; + self.max_y = height - 1; + self.clamp_position(); + } + + /// Ensure mouse position stays within bounds + fn clamp_position(&mut self) { + if self.mouse_x < 0 { + self.mouse_x = 0; + } else if self.mouse_x > self.max_x { + self.mouse_x = self.max_x; + } + + if self.mouse_y < 0 { + self.mouse_y = 0; + } else if self.mouse_y > self.max_y { + self.mouse_y = self.max_y; + } + } + + /// Get current mouse position + pub fn get_position(&self) -> (i16, i16) { + (self.mouse_x, self.mouse_y) + } + + /// Set mouse position + pub fn set_position(&mut self, x: i16, y: i16) { + self.mouse_x = x; + self.mouse_y = y; + self.clamp_position(); + } + + /// Check if the buffer is empty + pub fn is_empty(&self) -> bool { + !self.full && self.read_pos == self.write_pos + } + + /// Process a complete mouse packet + pub fn process_packet( + &mut self, + flags: MouseMovementFlags, + dx: i16, + dy: i16, + ) -> Result<(), MouseError> { + // Extract button states + let left_button = if flags.contains(MouseMovementFlags::LEFT_BUTTON_PRESSED) { + ButtonState::Pressed + } else { + ButtonState::Released + }; + + let right_button = if flags.contains(MouseMovementFlags::RIGHT_BUTTON_PRESSED) { + ButtonState::Pressed + } else { + ButtonState::Released + }; + + let middle_button = if flags.contains(MouseMovementFlags::MIDDLE_BUTTON_PRESSED) { + ButtonState::Pressed + } else { + ButtonState::Released + }; + + // Update absolute position + self.mouse_x += dx; + self.mouse_y += dy; + + // Ensure position stays within bounds + self.clamp_position(); + + // Create event only if there's actual movement or button state change + if dx != 0 + || dy != 0 + || left_button != self.prev_left_button + || right_button != self.prev_right_button + || middle_button != self.prev_middle_button + { + let event = MouseEvent { + dx: dx as i8, + dy: dy as i8, + dz: 0, // No scroll wheel support in basic PS/2 mouse + left_button, + right_button, + middle_button, + x: self.mouse_x, + y: self.mouse_y, + }; + + self.push_event(event)?; + + // Update previous button states + self.prev_left_button = left_button; + self.prev_right_button = right_button; + self.prev_middle_button = middle_button; + } + + Ok(()) + } + + /// Push an event to the buffer + fn push_event(&mut self, event: MouseEvent) -> Result<(), MouseError> { + if self.full { + self.read_pos = (self.read_pos + 1) % MOUSE_BUFFER_SIZE; + } + + self.buffer[self.write_pos] = Some(event); + self.write_pos = (self.write_pos + 1) % MOUSE_BUFFER_SIZE; + + if self.write_pos == self.read_pos { + self.full = true; + } + + without_interrupts(|| { + let mut waker = MOUSE_WAKER.lock(); + if let Some(w) = waker.take() { + w.wake(); + } + }); + + Ok(()) + } + + /// Read a mouse event from the buffer + pub fn read_event(&mut self) -> Option { + if self.is_empty() { + return None; + } + + let event = self.buffer[self.read_pos].clone(); + self.buffer[self.read_pos] = None; + self.read_pos = (self.read_pos + 1) % MOUSE_BUFFER_SIZE; + self.full = false; + + event + } + + /// Clear mouse buffer + pub fn clear_buffer(&mut self) { + const NONE_OPTION: Option = None; + self.buffer = [NONE_OPTION; MOUSE_BUFFER_SIZE]; + self.read_pos = 0; + self.write_pos = 0; + self.full = false; + } +} + +/// Initialize the mouse +pub fn init() { + controller::with_controller(initialize_mouse); +} + +/// Initialize and reset the mouse +fn initialize_mouse(controller: &mut ps2::Controller) { + let mut mouse = controller.mouse(); + + mouse + .reset_and_self_test() + .expect("Failed to self-test mouse"); + + mouse.set_defaults().expect("Failed to set mouse defaults"); + + mouse + .enable_data_reporting() + .expect("Failed to enable data reporting"); +} + +/// Get a stream of mouse events +pub fn get_stream() -> MouseStream { + MouseStream +} + +/// Wait for and return the next mouse event +pub async fn next_event() -> MouseEvent { + MouseStream.next().await.unwrap() +} + +/// Try to read a mouse event without waiting +pub fn try_read_event() -> Option { + without_interrupts(|| MOUSE.lock().read_event()) +} + +/// Get current mouse position +pub fn get_position() -> (i16, i16) { + without_interrupts(|| { + let mouse = MOUSE.lock(); + mouse.get_position() + }) +} + +/// Set mouse position +pub fn set_position(x: i16, y: i16) { + without_interrupts(|| { + let mut mouse = MOUSE.lock(); + mouse.set_position(x, y); + }) +} + +/// Set screen boundaries for mouse movement +pub fn set_bounds(width: i16, height: i16) { + without_interrupts(|| { + let mut mouse = MOUSE.lock(); + mouse.set_bounds(width, height); + }) +} + +/// Get mouse interrupt count +pub fn get_interrupt_count() -> u64 { + MOUSE_INTERRUPT_COUNT.load(Ordering::SeqCst) +} + +/// Mouse interrupt handler +pub fn mouse_handler() { + MOUSE_INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); + + controller::with_controller(|controller| { + // Continue reading as long as data is available + loop { + let status = controller.read_status(); + if !status.contains(ps2::flags::ControllerStatusFlags::OUTPUT_FULL) { + // No more data available + break; + } + let mut mouse_dev = controller.mouse(); + + match mouse_dev.read_data_packet() { + Ok((flags, dx, dy)) => { + let mut mouse = MOUSE.lock(); + if let Err(e) = mouse.process_packet(flags, dx, dy) { + serial_println!("Error processing mouse packet: {:?}", e); + } + } + Err(_) => { + // If we can't read a complete packet, break the loop + break; + } + } + } + }); +} + +impl Stream for MouseStream { + type Item = MouseEvent; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut mouse = MOUSE.lock(); + + if let Some(event) = mouse.read_event() { + return Poll::Ready(Some(event)); + } + + // No event available, register waker for notification + without_interrupts(|| { + let mut waker = MOUSE_WAKER.lock(); + *waker = Some(cx.waker().clone()); + }); + + Poll::Pending + } +} diff --git a/kernel/src/filesys/mod.rs b/kernel/src/filesys/mod.rs index ee081996..d46a778a 100644 --- a/kernel/src/filesys/mod.rs +++ b/kernel/src/filesys/mod.rs @@ -21,7 +21,10 @@ use alloc::{ sync::Arc, vec::Vec, }; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::{ + cmp, + sync::atomic::{AtomicBool, Ordering}, +}; use ext2::{ filesystem::{Ext2, FilesystemError, FilesystemResult}, node::{DirEntry, NodeError}, @@ -106,7 +109,7 @@ bitflags::bitflags! { /// A file object in the filesystem trait pub struct File { /// The pathname in the filesystem - pathname: String, + pub pathname: String, /// File descriptor pub fd: usize, /// Position we are seeking from in the file @@ -257,7 +260,7 @@ pub struct Ext2Wrapper { pub page_cache: PageCache, // Wrapper for Ext2 Filesystem - filesystem: Mutex, + pub filesystem: Mutex, // Maps inode number to number of processes refcount: Mutex>, @@ -596,7 +599,11 @@ impl FileSystem for Ext2Wrapper { // Do raw pointer write *after* .await to avoid Send violation unsafe { let buf_ptr = kernel_va.as_mut_ptr(); - core::ptr::copy_nonoverlapping(file_buf.as_ptr(), buf_ptr, file_buf.len()); + core::ptr::copy_nonoverlapping( + file_buf.as_ptr(), + buf_ptr, + cmp::min(PAGE_SIZE, file_buf.len()), + ); } file_mappings.insert(offset, Page::containing_address(kernel_va)); @@ -780,7 +787,8 @@ mod tests { { let meta = user_fs.metadata(fd).await.unwrap(); assert_eq!(meta.pathname, "./temp/meta.txt"); - assert_eq!(meta.fd, 0); + // 0 and 1 are stdin/out + assert_eq!(meta.fd, 2); assert_eq!(meta.flags, OpenFlags::O_WRONLY | OpenFlags::O_CREAT); } diff --git a/kernel/src/init.rs b/kernel/src/init.rs index ad7a6d0c..71f4c286 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -70,6 +70,7 @@ pub fn init() -> u32 { let bsp_id = wake_cores(); idt::enable(); + bsp_id } diff --git a/kernel/src/interrupts/idt.rs b/kernel/src/interrupts/idt.rs index 9862f209..c4db91d2 100644 --- a/kernel/src/interrupts/idt.rs +++ b/kernel/src/interrupts/idt.rs @@ -20,7 +20,7 @@ use crate::{ idt::{KEYBOARD_VECTOR, MOUSE_VECTOR, SYSCALL_HANDLER, TIMER_VECTOR, TLB_SHOOTDOWN_VECTOR}, syscalls::{SYSCALL_EXIT, SYSCALL_MMAP, SYSCALL_NANOSLEEP, SYSCALL_PRINT}, }, - devices::{keyboard::keyboard_handler, mouse::mouse_handler}, + devices::ps2_dev::{keyboard_interrupt_handler, mouse_interrupt_handler}, events::inc_runner_clock, interrupts::x2apic::{self, current_core_id, TLB_SHOOTDOWN_ADDR}, memory::{ @@ -61,8 +61,8 @@ lazy_static! { .set_handler_fn(naked_syscall_handler) .set_privilege_level(x86_64::PrivilegeLevel::Ring3); idt[TLB_SHOOTDOWN_VECTOR].set_handler_fn(tlb_shootdown_handler); - idt[KEYBOARD_VECTOR].set_handler_fn(keyboard_handler); - idt[MOUSE_VECTOR].set_handler_fn(mouse_handler); + idt[KEYBOARD_VECTOR].set_handler_fn(keyboard_interrupt_handler); + idt[MOUSE_VECTOR].set_handler_fn(mouse_interrupt_handler); idt }; } diff --git a/kernel/src/interrupts/io_apic.rs b/kernel/src/interrupts/io_apic.rs index 6d6595d3..dfed6036 100644 --- a/kernel/src/interrupts/io_apic.rs +++ b/kernel/src/interrupts/io_apic.rs @@ -237,8 +237,8 @@ impl IoApicManager { vector: KEYBOARD_VECTOR, destination: 0, // BSP masked: false, // Enable immediately - trigger_mode: TriggerMode::Edge, - polarity: Polarity::HighActive, + trigger_mode: TriggerMode::Level, + polarity: Polarity::LowActive, dest_mode: DestinationMode::Physical, delivery_mode: DeliveryMode::Fixed, }, @@ -251,8 +251,8 @@ impl IoApicManager { vector: MOUSE_VECTOR, destination: 0, // BSP masked: false, // Enable immediately - trigger_mode: TriggerMode::Edge, - polarity: Polarity::HighActive, + trigger_mode: TriggerMode::Level, + polarity: Polarity::LowActive, dest_mode: DestinationMode::Physical, delivery_mode: DeliveryMode::Fixed, }, diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index db04971b..f3d30729 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -30,6 +30,8 @@ pub mod net; pub mod processes; pub mod syscalls; +pub mod shell; + pub use devices::serial; pub mod prelude { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 00c2311b..ee8d22ec 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -8,7 +8,7 @@ #![reexport_test_harness_main = "test_main"] use limine::request::{RequestsEndMarker, RequestsStartMarker}; -use taos::{debug, events::run_loop}; +use taos::{debug, events::run_loop, shell}; extern crate alloc; @@ -38,7 +38,11 @@ extern "C" fn _start() -> ! { debug!("BSP entering event loop"); - unsafe { run_loop(bsp_id) } + unsafe { shell::init() }; + + unsafe { + run_loop(bsp_id); + } } /// Production panic handler. diff --git a/kernel/src/memory/paging.rs b/kernel/src/memory/paging.rs index 433f4f66..5e965620 100644 --- a/kernel/src/memory/paging.rs +++ b/kernel/src/memory/paging.rs @@ -288,366 +288,366 @@ pub fn update_permissions(page: Page, mapper: &mut impl Mapper, flags: tlb_shootdown(page.start_address()); } -#[cfg(test)] -mod tests { - use super::*; - - use alloc::{sync::Arc, vec::Vec}; - use core::{ - ptr::{read_volatile, write_volatile}, - sync::atomic::{AtomicU64, Ordering}, - }; - use x86_64::{ - structures::paging::{mapper::TranslateError, Page, PageTableFlags, PhysFrame}, - VirtAddr, - }; - - // Import functions from the kernel memory module. - use crate::{ - constants::memory::PAGE_SIZE, - events::schedule_kernel_on, - memory::{ - mm::{Mm, VmAreaBackings, VmAreaFlags}, - KERNEL_MAPPER, - }, - processes::process::{get_current_pid, with_current_pcb, PROCESS_TABLE}, - }; - - // Used for TLB shootdown testcases. - static PRE_READ: AtomicU64 = AtomicU64::new(0); - static POST_READ: AtomicU64 = AtomicU64::new(0); - - /// Asynchronously reads a u64 value from the start address of the given page and stores it in PRE_READ. - async fn pre_read(page: Page) { - let value = unsafe { page.start_address().as_ptr::().read_volatile() }; - PRE_READ.store(value, Ordering::SeqCst); - } - - /// Asynchronously reads a u64 value from the start address of the given page and stores it in POST_READ. - async fn post_read(page: Page) { - let value = unsafe { page.start_address().as_ptr::().read_volatile() }; - POST_READ.store(value, Ordering::SeqCst); - } - - /// Tests that after a mapping is removed the page translation fails. - /// - /// This test creates a mapping for a given virtual page, removes it, and then verifies that - /// translating the page results in a `PageNotMapped` error. - #[test_case] - async fn test_remove_mapped_frame() { - let mut mapper = KERNEL_MAPPER.lock(); - let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); - let _ = create_mapping(page, &mut *mapper, None); - - remove_mapped_frame(page, &mut *mapper); - - let translate_frame_error = mapper.translate_page(page); - - assert!(matches!( - translate_frame_error, - Err(TranslateError::PageNotMapped) - )); - } - - /// Tests that mapping a page returns the correct physical frame. - /// - /// A mapping is created for a test virtual page and its returned physical frame is then - /// verified by translating the page. Finally, the mapping is removed. - #[test_case] - async fn test_basic_map_and_translate() { - let mut mapper = KERNEL_MAPPER.lock(); - - // Use a test virtual page. - let page: Page = Page::containing_address(VirtAddr::new(0x400001000)); - let frame: PhysFrame = create_mapping(page, &mut *mapper, None); - - let translate_frame = mapper.translate_page(page).expect("Translation failed"); - - assert_eq!(frame, translate_frame); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests that updating page permissions works as expected. - /// - /// A mapping is created for a given page and then its permissions are updated (e.g. to make it - /// read-only by removing the WRITABLE flag). The test then retrieves the page table entry (PTE) - /// and asserts that it contains the expected flags. - #[test_case] - async fn test_update_permissions() { - let mut mapper = KERNEL_MAPPER.lock(); - - let page: Page = Page::containing_address(VirtAddr::new(0x400002000)); - let _ = create_mapping(page, &mut *mapper, None); - - let flags = PageTableFlags::PRESENT; // Only present (read-only). - - update_permissions(page, &mut *mapper, flags); - - let flags = get_page_flags(page, &mut mapper).expect("Getting page table flags failed"); - - assert!(flags.contains(PageTableFlags::PRESENT)); - assert!(!flags.contains(PageTableFlags::WRITABLE)); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests that contiguous mappings spanning multiple pages work correctly. - /// - /// This test allocates mappings for a contiguous region of 8 pages. Each page is mapped with - /// writable permissions, a distinct value is written to each page, and then the value is read - /// back to verify correctness. Finally, all mappings are removed. - #[test_case] // Uncomment to run this test. - async fn test_contiguous_mapping() { - let mut mapper = KERNEL_MAPPER.lock(); - - // Define a contiguous region spanning 8 pages. - let start_page: Page = Page::containing_address(VirtAddr::new(0x400004000)); - let num_pages = 8; - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - - let mut frames = Vec::new(); - for i in 0..num_pages { - let page = Page::from_start_address(start_page.start_address() + i * PAGE_SIZE as u64) - .expect("Invalid page address"); - let frame = create_mapping(page, &mut *mapper, Some(flags)); - frames.push((page, frame)); - } - - // Write and verify distinct values. - for (i, (page, _)) in frames.iter().enumerate() { - let ptr = page.start_address().as_mut_ptr::(); - unsafe { write_volatile(ptr, i as u64) }; - let val = unsafe { read_volatile(ptr) }; - assert_eq!(val, i as u64); - } - - // Cleanup: Unmap all pages. - for (page, _) in frames { - remove_mapped_frame(page, &mut *mapper); - } - } - - /// Tests that TLB shootdowns work correctly across cores. - /// - /// This test creates a mapping for a given page and writes a value to cache it on the current core. - /// It then schedules a read of that page on an alternate core (to load it into that core’s TLB cache). - /// After that, the mapping is updated to a new frame with new contents and the new value is written. - /// Finally, the test re-schedules a read on the alternate core and verifies that the new value is observed. - #[test_case] - async fn test_tlb_shootdowns_cross_core() { - const AP: u32 = 1; - const PRIORITY: usize = 3; - - // Create mapping and set a value on the current core. - let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); - - { - let mut mapper = KERNEL_MAPPER.lock(); - let _ = create_mapping(page, &mut *mapper, None); - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0xdead); - } - } - - // Mapping exists now and is cached for the first core. - - // Schedule a read on core 1 to load the page into its TLB cache. - schedule_kernel_on(AP, async move { pre_read(page).await }, PRIORITY); - - while PRE_READ.load(Ordering::SeqCst) == 0 { - core::hint::spin_loop(); - } - - { - let mut mapper = KERNEL_MAPPER.lock(); - - let new_frame = alloc_frame().expect("Could not find a new frame"); - - // Update the mapping so that a TLB shootdown is necessary. - update_mapping( - page, - &mut *mapper, - new_frame, - Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), - ); - - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0x42); - } - } - - // Schedule another read on core 1 to verify that the new value is visible. - schedule_kernel_on(AP, async move { post_read(page).await }, PRIORITY); - - while POST_READ.load(Ordering::SeqCst) == 0 { - core::hint::spin_loop(); - } - - assert_eq!(POST_READ.load(Ordering::SeqCst), 0x42); - - let mut mapper = KERNEL_MAPPER.lock(); - remove_mapped_frame(page, &mut *mapper); - } - - // Tests the copy-on-write (COW) mechanism for a mapped page. - // - // In a COW scenario, the page is initially mapped as read-only. A write to the page should - // trigger a fault that results in a new physical frame being allocated and mapped for that page. - // In this test, we simulate this behavior by: - // 1. Creating a mapping with read-only permissions. - // 2. Writing to the page which, triggers a page fault - // 3. Handling the page fault in our page fault handler - // 4. Verifying that the new frame is different from the initial one and that the written value is present. - #[test_case] - async fn test_copy_on_write() { - let mut mapper = KERNEL_MAPPER.lock(); - // Create a dummy PML4 frame. - // Locate the current process. - - const TEST_VALUE: u64 = 0x42; - - let page = Page::containing_address(VirtAddr::new(0x400003000)); - - let anon_area = Arc::new(VmAreaBackings::new()); - - with_current_pcb(|pcb| { - pcb.mm.with_vma_tree_mutable(|tree| { - let _vma1 = Mm::insert_vma( - tree, - page.start_address().as_u64(), - page.start_address().as_u64() + PAGE_SIZE as u64, - anon_area.clone(), - VmAreaFlags::WRITABLE, - 0, - 0, - ); - }); - }); - - // Create mapping with read-only permission to simulate a COW mapping. - let init_frame = create_mapping(page, &mut *mapper, Some(PageTableFlags::PRESENT)); - - // Write to the page. - // Triggers page fault - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(TEST_VALUE); - } - - // Now, translating the page should return the new frame. - let frame = mapper - .translate_page(page) - .expect("Translation after COW failed"); - - // The new frame should be different from the original frame. - assert_ne!(init_frame, frame); - - let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value, TEST_VALUE); - - // should not trigger a page fault, we should be able to write now - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0x20); - } - - let new_frame = mapper - .translate_page(page) - .expect("Translation after COW failed"); - - // We already made this our own, no need to have done COW - assert_eq!(frame, new_frame); - - let read_value2 = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value2, 0x20); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests the copy-on-write (COW) mechanism for a mapped page. - /// - /// In a COW scenario, the page is initially mapped as writable, and a full buffer is written - /// Then, the page is marked read only and the first byte in the buffer is written to. - /// This should trigger a page fault that does COW, but it should maintain the rest - /// of the values in the buffer. - #[test_case] - async fn test_copy_on_write_full() { - let mut mapper = KERNEL_MAPPER.lock(); - // Create a dummy PML4 frame. - // Locate the current process. - let pid = get_current_pid(); - let process = { - let process_table = PROCESS_TABLE.read(); - process_table - .get(&pid) - .expect("can't find pcb in process table") - .clone() - }; - const TEST_VALUE: u8 = 0x2; - let page = Page::containing_address(VirtAddr::new(0x400003000)); - let anon_area = Arc::new(VmAreaBackings::new()); - - unsafe { - (*process.pcb.get()).mm.with_vma_tree_mutable(|tree| { - let _vma1 = Mm::insert_vma( - tree, - page.start_address().as_u64(), - page.start_address().as_u64() + PAGE_SIZE as u64, - anon_area.clone(), - VmAreaFlags::WRITABLE, - 0, - 0, - ); - }); - } - - // Create mapping with read-only permission to simulate a COW mapping. - let _ = create_mapping( - page, - &mut *mapper, - Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), - ); - - // Write 1s to the entire buffer - unsafe { - let buf_ptr = page.start_address().as_mut_ptr::(); - core::ptr::write_bytes(buf_ptr, 1, PAGE_SIZE); - } - - // Make it so we page fault on write - update_permissions(page, &mut *mapper, PageTableFlags::PRESENT); - - // Write to the page. - // In a real system, this would trigger a page fault to handle copy-on-write. - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(TEST_VALUE); - } - - // the cow should not have messed with any data in the buffer besides what - // we just wrote to - let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value, TEST_VALUE); - - unsafe { - let buf_ptr = page.start_address().as_ptr::(); - for i in 1..PAGE_SIZE { - let val = *buf_ptr.add(i); - assert_eq!(val, 1, "Byte at offset {} is not 1 (found {})", i, val); - } - } - - remove_mapped_frame(page, &mut *mapper); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// +// use alloc::{sync::Arc, vec::Vec}; +// use core::{ +// ptr::{read_volatile, write_volatile}, +// sync::atomic::{AtomicU64, Ordering}, +// }; +// use x86_64::{ +// structures::paging::{mapper::TranslateError, Page, PageTableFlags, PhysFrame}, +// VirtAddr, +// }; +// +// // Import functions from the kernel memory module. +// use crate::{ +// constants::memory::PAGE_SIZE, +// events::schedule_kernel_on, +// memory::{ +// mm::{Mm, VmAreaBackings, VmAreaFlags}, +// KERNEL_MAPPER, +// }, +// processes::process::{get_current_pid, with_current_pcb, PROCESS_TABLE}, +// }; +// +// // Used for TLB shootdown testcases. +// static PRE_READ: AtomicU64 = AtomicU64::new(0); +// static POST_READ: AtomicU64 = AtomicU64::new(0); +// +// /// Asynchronously reads a u64 value from the start address of the given page and stores it in PRE_READ. +// async fn pre_read(page: Page) { +// let value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// PRE_READ.store(value, Ordering::SeqCst); +// } +// +// /// Asynchronously reads a u64 value from the start address of the given page and stores it in POST_READ. +// async fn post_read(page: Page) { +// let value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// POST_READ.store(value, Ordering::SeqCst); +// } +// +// /// Tests that after a mapping is removed the page translation fails. +// /// +// /// This test creates a mapping for a given virtual page, removes it, and then verifies that +// /// translating the page results in a `PageNotMapped` error. +// #[test_case] +// async fn test_remove_mapped_frame() { +// let mut mapper = KERNEL_MAPPER.lock(); +// let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); +// let _ = create_mapping(page, &mut *mapper, None); +// +// remove_mapped_frame(page, &mut *mapper); +// +// let translate_frame_error = mapper.translate_page(page); +// +// assert!(matches!( +// translate_frame_error, +// Err(TranslateError::PageNotMapped) +// )); +// } +// +// /// Tests that mapping a page returns the correct physical frame. +// /// +// /// A mapping is created for a test virtual page and its returned physical frame is then +// /// verified by translating the page. Finally, the mapping is removed. +// #[test_case] +// async fn test_basic_map_and_translate() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// // Use a test virtual page. +// let page: Page = Page::containing_address(VirtAddr::new(0x400001000)); +// let frame: PhysFrame = create_mapping(page, &mut *mapper, None); +// +// let translate_frame = mapper.translate_page(page).expect("Translation failed"); +// +// assert_eq!(frame, translate_frame); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests that updating page permissions works as expected. +// /// +// /// A mapping is created for a given page and then its permissions are updated (e.g. to make it +// /// read-only by removing the WRITABLE flag). The test then retrieves the page table entry (PTE) +// /// and asserts that it contains the expected flags. +// #[test_case] +// async fn test_update_permissions() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// let page: Page = Page::containing_address(VirtAddr::new(0x400002000)); +// let _ = create_mapping(page, &mut *mapper, None); +// +// let flags = PageTableFlags::PRESENT; // Only present (read-only). +// +// update_permissions(page, &mut *mapper, flags); +// +// let flags = get_page_flags(page, &mut mapper).expect("Getting page table flags failed"); +// +// assert!(flags.contains(PageTableFlags::PRESENT)); +// assert!(!flags.contains(PageTableFlags::WRITABLE)); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests that contiguous mappings spanning multiple pages work correctly. +// /// +// /// This test allocates mappings for a contiguous region of 8 pages. Each page is mapped with +// /// writable permissions, a distinct value is written to each page, and then the value is read +// /// back to verify correctness. Finally, all mappings are removed. +// #[test_case] // Uncomment to run this test. +// async fn test_contiguous_mapping() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// // Define a contiguous region spanning 8 pages. +// let start_page: Page = Page::containing_address(VirtAddr::new(0x400004000)); +// let num_pages = 8; +// let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; +// +// let mut frames = Vec::new(); +// for i in 0..num_pages { +// let page = Page::from_start_address(start_page.start_address() + i * PAGE_SIZE as u64) +// .expect("Invalid page address"); +// let frame = create_mapping(page, &mut *mapper, Some(flags)); +// frames.push((page, frame)); +// } +// +// // Write and verify distinct values. +// for (i, (page, _)) in frames.iter().enumerate() { +// let ptr = page.start_address().as_mut_ptr::(); +// unsafe { write_volatile(ptr, i as u64) }; +// let val = unsafe { read_volatile(ptr) }; +// assert_eq!(val, i as u64); +// } +// +// // Cleanup: Unmap all pages. +// for (page, _) in frames { +// remove_mapped_frame(page, &mut *mapper); +// } +// } +// +// /// Tests that TLB shootdowns work correctly across cores. +// /// +// /// This test creates a mapping for a given page and writes a value to cache it on the current core. +// /// It then schedules a read of that page on an alternate core (to load it into that core’s TLB cache). +// /// After that, the mapping is updated to a new frame with new contents and the new value is written. +// /// Finally, the test re-schedules a read on the alternate core and verifies that the new value is observed. +// #[test_case] +// async fn test_tlb_shootdowns_cross_core() { +// const AP: u32 = 1; +// const PRIORITY: usize = 3; +// +// // Create mapping and set a value on the current core. +// let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); +// +// { +// let mut mapper = KERNEL_MAPPER.lock(); +// let _ = create_mapping(page, &mut *mapper, None); +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0xdead); +// } +// } +// +// // Mapping exists now and is cached for the first core. +// +// // Schedule a read on core 1 to load the page into its TLB cache. +// schedule_kernel_on(AP, async move { pre_read(page).await }, PRIORITY); +// +// while PRE_READ.load(Ordering::SeqCst) == 0 { +// core::hint::spin_loop(); +// } +// +// { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// let new_frame = alloc_frame().expect("Could not find a new frame"); +// +// // Update the mapping so that a TLB shootdown is necessary. +// update_mapping( +// page, +// &mut *mapper, +// new_frame, +// Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), +// ); +// +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0x42); +// } +// } +// +// // Schedule another read on core 1 to verify that the new value is visible. +// schedule_kernel_on(AP, async move { post_read(page).await }, PRIORITY); +// +// while POST_READ.load(Ordering::SeqCst) == 0 { +// core::hint::spin_loop(); +// } +// +// assert_eq!(POST_READ.load(Ordering::SeqCst), 0x42); +// +// let mut mapper = KERNEL_MAPPER.lock(); +// remove_mapped_frame(page, &mut *mapper); +// } +// +// // Tests the copy-on-write (COW) mechanism for a mapped page. +// // +// // In a COW scenario, the page is initially mapped as read-only. A write to the page should +// // trigger a fault that results in a new physical frame being allocated and mapped for that page. +// // In this test, we simulate this behavior by: +// // 1. Creating a mapping with read-only permissions. +// // 2. Writing to the page which, triggers a page fault +// // 3. Handling the page fault in our page fault handler +// // 4. Verifying that the new frame is different from the initial one and that the written value is present. +// #[test_case] +// async fn test_copy_on_write() { +// let mut mapper = KERNEL_MAPPER.lock(); +// // Create a dummy PML4 frame. +// // Locate the current process. +// +// const TEST_VALUE: u64 = 0x42; +// +// let page = Page::containing_address(VirtAddr::new(0x400003000)); +// +// let anon_area = Arc::new(VmAreaBackings::new()); +// +// with_current_pcb(|pcb| { +// pcb.mm.with_vma_tree_mutable(|tree| { +// let _vma1 = Mm::insert_vma( +// tree, +// page.start_address().as_u64(), +// page.start_address().as_u64() + PAGE_SIZE as u64, +// anon_area.clone(), +// VmAreaFlags::WRITABLE, +// 0, +// 0, +// ); +// }); +// }); +// +// // Create mapping with read-only permission to simulate a COW mapping. +// let init_frame = create_mapping(page, &mut *mapper, Some(PageTableFlags::PRESENT)); +// +// // Write to the page. +// // Triggers page fault +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(TEST_VALUE); +// } +// +// // Now, translating the page should return the new frame. +// let frame = mapper +// .translate_page(page) +// .expect("Translation after COW failed"); +// +// // The new frame should be different from the original frame. +// assert_ne!(init_frame, frame); +// +// let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value, TEST_VALUE); +// +// // should not trigger a page fault, we should be able to write now +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0x20); +// } +// +// let new_frame = mapper +// .translate_page(page) +// .expect("Translation after COW failed"); +// +// // We already made this our own, no need to have done COW +// assert_eq!(frame, new_frame); +// +// let read_value2 = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value2, 0x20); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests the copy-on-write (COW) mechanism for a mapped page. +// /// +// /// In a COW scenario, the page is initially mapped as writable, and a full buffer is written +// /// Then, the page is marked read only and the first byte in the buffer is written to. +// /// This should trigger a page fault that does COW, but it should maintain the rest +// /// of the values in the buffer. +// #[test_case] +// async fn test_copy_on_write_full() { +// let mut mapper = KERNEL_MAPPER.lock(); +// // Create a dummy PML4 frame. +// // Locate the current process. +// let pid = get_current_pid(); +// let process = { +// let process_table = PROCESS_TABLE.read(); +// process_table +// .get(&pid) +// .expect("can't find pcb in process table") +// .clone() +// }; +// const TEST_VALUE: u8 = 0x2; +// let page = Page::containing_address(VirtAddr::new(0x400003000)); +// let anon_area = Arc::new(VmAreaBackings::new()); +// +// unsafe { +// (*process.pcb.get()).mm.with_vma_tree_mutable(|tree| { +// let _vma1 = Mm::insert_vma( +// tree, +// page.start_address().as_u64(), +// page.start_address().as_u64() + PAGE_SIZE as u64, +// anon_area.clone(), +// VmAreaFlags::WRITABLE, +// 0, +// 0, +// ); +// }); +// } +// +// // Create mapping with read-only permission to simulate a COW mapping. +// let _ = create_mapping( +// page, +// &mut *mapper, +// Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), +// ); +// +// // Write 1s to the entire buffer +// unsafe { +// let buf_ptr = page.start_address().as_mut_ptr::(); +// core::ptr::write_bytes(buf_ptr, 1, PAGE_SIZE); +// } +// +// // Make it so we page fault on write +// update_permissions(page, &mut *mapper, PageTableFlags::PRESENT); +// +// // Write to the page. +// // In a real system, this would trigger a page fault to handle copy-on-write. +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(TEST_VALUE); +// } +// +// // the cow should not have messed with any data in the buffer besides what +// // we just wrote to +// let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value, TEST_VALUE); +// +// unsafe { +// let buf_ptr = page.start_address().as_ptr::(); +// for i in 1..PAGE_SIZE { +// let val = *buf_ptr.add(i); +// assert_eq!(val, 1, "Byte at offset {} is not 1 (found {})", i, val); +// } +// } +// +// remove_mapped_frame(page, &mut *mapper); +// } +// } diff --git a/kernel/src/processes/loader.rs b/kernel/src/processes/loader.rs index 40ee388f..ebc297ab 100644 --- a/kernel/src/processes/loader.rs +++ b/kernel/src/processes/loader.rs @@ -6,10 +6,11 @@ use crate::{ memory::{ frame_allocator::with_generic_allocator, mm::{Mm, VmAreaBackings, VmAreaFlags}, - paging::{create_mapping, update_permissions}, + paging::{create_mapping, create_mapping_to_frame, update_permissions}, }, + serial_println, }; -use alloc::sync::Arc; +use alloc::{string::String, sync::Arc, vec::Vec}; use core::ptr::{copy_nonoverlapping, write_bytes}; use goblin::{ elf::Elf, @@ -40,6 +41,8 @@ pub fn load_elf( user_mapper: &mut impl Mapper, kernel_mapper: &mut OffsetPageTable<'static>, mm: &mut Mm, + args: Vec, + envs: Vec, ) -> (VirtAddr, u64) { let elf = Elf::parse(elf_bytes).expect("Parsing ELF failed"); for ph in elf.program_headers.iter() { @@ -141,7 +144,17 @@ pub fn load_elf( let stack_end = VirtAddr::new(STACK_START + STACK_SIZE as u64); let _start_page: Page = Page::containing_address(stack_start); let _end_page: Page = Page::containing_address(stack_end); - + let frame = create_mapping( + _end_page, + user_mapper, + Some(PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE), + ); + create_mapping_to_frame( + _end_page, + kernel_mapper, + Some(PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE), + frame, + ); // new anon_vma that corresponds to this stack let anon_vma_stack = Arc::new(VmAreaBackings::new()); @@ -157,5 +170,76 @@ pub fn load_elf( ); }); - (stack_end, elf.header.e_entry) + // 1) Start at top of stack + let mut sp = stack_end; + // 2) Align down to 16 bytes + sp = VirtAddr::new(sp.as_u64() & !0xF); + + // 3) Reserve space for the argument strings themselves, + // writing them at lower addresses, and save their pointers. + let mut arg_ptrs = Vec::with_capacity(args.len()); + for s in args.into_iter().rev() { + let bytes = s.into_bytes(); + let len = bytes.len() + 1; // +1 for '\0' + sp = VirtAddr::new(sp.as_u64() + len as u64); + unsafe { + let dst = sp.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); + dst.add(bytes.len()).write(0); + } + arg_ptrs.push(sp.as_u64()); + } + arg_ptrs.reverse(); // because we iterated in reverse + + // 4) Same for env strings + let mut env_ptrs = Vec::with_capacity(envs.len()); + for s in envs.into_iter().rev() { + let bytes = s.into_bytes(); + let len = bytes.len() + 1; + sp = VirtAddr::new(sp.as_u64() + len as u64); + unsafe { + let dst = sp.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); + dst.add(bytes.len()).write(0); + } + let cstr = unsafe { core::ffi::CStr::from_ptr(sp.as_u64() as *const i8) }; + let s = cstr.to_str().unwrap(); + crate::serial_println!("envp: {}", s); + env_ptrs.push(sp.as_u64()); + } + env_ptrs.reverse(); + + for &s in &env_ptrs { + let cstr = unsafe { core::ffi::CStr::from_ptr(s as *const i8) }; + let s = cstr.to_str().unwrap(); + crate::serial_println!("envp: {}", s); + } + + // 5) Align down again before pushing pointer arrays + sp = VirtAddr::new(sp.as_u64() & !0xF); + + // 6) Push envp pointers (NULL-terminated) + for &ptr in env_ptrs.iter().chain(core::iter::once(&0u64)) { + unsafe { + sp.as_mut_ptr::().write(ptr); + } + sp = VirtAddr::new(sp.as_u64() + 8); + } + + // 7) Push argv pointers (NULL-terminated) + for &ptr in arg_ptrs.iter().chain(core::iter::once(&0u64)) { + unsafe { + sp.as_mut_ptr::().write(ptr); + } + sp = VirtAddr::new(sp.as_u64() + 8); + } + + // 8) Finally push argc + let argc = arg_ptrs.len() as u64; + unsafe { + sp.as_mut_ptr::().write(argc); + } + + // Return the new stack pointer and entry point + (sp, elf.header.e_entry) } diff --git a/kernel/src/processes/mod.rs b/kernel/src/processes/mod.rs index 83ab3be9..b5844395 100644 --- a/kernel/src/processes/mod.rs +++ b/kernel/src/processes/mod.rs @@ -12,18 +12,24 @@ pub fn init(cpu_id: u32) { #[cfg(test)] mod tests { + use alloc::vec::Vec; + use x86_64::align_up; + use crate::{ - constants::processes::TEST_SIMPLE_PROCESS, + constants::{memory::PAGE_SIZE, processes::TEST_SIMPLE_PROCESS}, events::{ current_running_event, futures::await_on::AwaitProcess, get_runner_time, schedule_process, }, + filesys::{get_file, FileSystem, OpenFlags, FILESYSTEM}, processes::process::create_process, + serial_println, + syscalls::memorymap::{sys_mmap, MmapFlags, ProtFlags}, }; #[test_case] async fn test_simple_process() { - let pid = create_process(TEST_SIMPLE_PROCESS); + let pid = create_process(TEST_SIMPLE_PROCESS, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( pid, @@ -33,4 +39,60 @@ mod tests { .await; assert!(waiter.is_ok()); } + + // #[test_case] + async fn test_simple_c_ret() { + let fs = FILESYSTEM.get().unwrap(); + let fd = { + fs.lock() + .open_file( + "/executables/ret", + OpenFlags::O_RDONLY | OpenFlags::O_WRONLY, + ) + .await + .expect("Could not open file") + }; + let file = get_file(fd).unwrap(); + let file_len = { + fs.lock() + .filesystem + .lock() + .get_node(&file.lock().pathname) + .await + .unwrap() + .size() + }; + sys_mmap( + 0x9000, + align_up(file_len, PAGE_SIZE as u64), + ProtFlags::PROT_EXEC.bits(), + MmapFlags::MAP_FILE.bits(), + fd as i64, + 0, + ); + + serial_println!("Reading file..."); + + let mut buffer = alloc::vec![0u8; file_len as usize]; + let bytes_read = { + fs.lock() + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + + serial_println!("Bytes read: {:#?}", bytes_read); + + let pid = create_process(buf, Vec::new(), Vec::new()); + schedule_process(pid); + let waiter = AwaitProcess::new( + pid, + get_runner_time(3_000_000_000), + current_running_event().unwrap(), + ) + .await; + assert!(waiter.is_ok()); + } } diff --git a/kernel/src/processes/process.rs b/kernel/src/processes/process.rs index 61dd2a7b..1b01be7e 100644 --- a/kernel/src/processes/process.rs +++ b/kernel/src/processes/process.rs @@ -24,7 +24,7 @@ use crate::{ processes::{loader::load_elf, registers::Registers}, serial_println, }; -use alloc::{collections::BTreeMap, sync::Arc}; +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use core::{ arch::naked_asm, borrow::BorrowMut, @@ -182,7 +182,8 @@ pub fn create_placeholder_process() -> u32 { }, mmap_address: START_MMAP_ADDRESS, fd_table: [const { None }; MAX_FILES], - next_fd: Arc::new(Mutex::new(0)), + // 0 and 1 are stdin, stdout + next_fd: Arc::new(Mutex::new(2)), next_preemption_time: 0, mm, namespace: Namespace::new(), @@ -191,7 +192,7 @@ pub fn create_placeholder_process() -> u32 { pid } -pub fn create_process(elf_bytes: &[u8]) -> u32 { +pub fn create_process(elf_bytes: &[u8], args: Vec, envs: Vec) -> u32 { let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst); with_buddy_frame_allocator(|alloc| { alloc.print_free_frames(); @@ -210,6 +211,8 @@ pub fn create_process(elf_bytes: &[u8]) -> u32 { &mut mapper, &mut KERNEL_MAPPER.lock(), mm.borrow_mut(), + args, + envs, ); let process = Arc::new(UnsafePCB::new(PCB { @@ -240,7 +243,8 @@ pub fn create_process(elf_bytes: &[u8]) -> u32 { }, mmap_address: START_MMAP_ADDRESS, fd_table: [const { None }; MAX_FILES], - next_fd: Arc::new(Mutex::new(0)), + // 0 and 1 are stdin, stdout + next_fd: Arc::new(Mutex::new(2)), mm, namespace: Namespace::new(), })); @@ -334,6 +338,7 @@ use super::registers::ForkingRegisters; #[no_mangle] pub async unsafe fn run_process_ring3(pid: u32) { interrupts::disable(); + serial_println!("RUNNING PROCESS"); let process = { let process_table = PROCESS_TABLE.read(); let process = process_table @@ -705,3 +710,89 @@ pub fn sleep_process_syscall(nanos: u64, reg_vals: &ForkingRegisters) { core::arch::asm!("swapgs", "ret"); } } + +#[cfg(test)] +mod tests { + use crate::{ + constants::processes::PRINT_EXIT, + memory::{mm::Mm, HHDM_OFFSET}, + processes::loader::load_elf, + }; + + use super::*; + use core::slice; + use x86_64::{ + structures::paging::{OffsetPageTable, PageTable, PhysFrame}, + PhysAddr, + }; + + #[test_case] + async fn verify_stack_args_envs() { + // ------ setup exactly as before ------ + let mut user_mapper = unsafe { + let pml4 = create_process_page_table(); + let virt = *HHDM_OFFSET + pml4.start_address().as_u64(); + let ptr = virt.as_mut_ptr::(); + OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) + }; + let args = alloc::vec!["foo".into(), "bar".into()]; + let envs = alloc::vec!["X=1".into(), "Y=two".into()]; + let pml4_frame = PhysFrame::containing_address(PhysAddr::new(0x1000)); + let mut mm = Mm::new(pml4_frame); + + // call loader: `sp` is the address of the u64 slot containing `argc` + let (sp, _entry) = load_elf( + PRINT_EXIT, + &mut user_mapper, + &mut KERNEL_MAPPER.lock(), + &mut mm, + args.clone(), + envs.clone(), + ); + + // total u64 slots we pushed: envs + NULL, args + NULL, argc + let nen = envs.len() as u64; + let nar = args.len() as u64; + + // ---- 1) verify argc ---- + // `sp` points at argc, so just read it + let got_argc = unsafe { (sp.as_u64() as *const u64).read() }; + assert_eq!(got_argc, nar, "argc mismatch"); + + // 2) verify argv + let argv0_ptr = (sp.as_u64() - 8 * (nar + 1)) as *const u64; + (0..(nar as usize)).for_each(|i| { + // read argv[i] + let str_addr = unsafe { argv0_ptr.add(i).read() as *const u8 }; + // walk until NUL + let mut len = 0; + while unsafe { *str_addr.add(len) } != 0 { + len += 1; + } + let got = core::str::from_utf8(unsafe { slice::from_raw_parts(str_addr, len) }) + .expect("Invalid UTF-8 in argv"); + serial_println!("GOT: {:#?}", got); + assert_eq!(got, &args[i], "argv[{}] mismatch", i); + }); + + // ---- 3) verify envp pointers & strings ---- + // envp[] sits *below* argv array + its NULL terminator: + // offset = sp - 8 * (nar + 1) - 8 * (nen + 1) + let envp0_ptr = ( + sp.as_u64() + - 8 * (nar + 1) // skip argv + NULL + - 8 * (nen + 1) + // skip envp + NULL + ) as *const u64; + (0..(nen as usize)).for_each(|i| { + let str_addr = unsafe { envp0_ptr.add(i).read() as *const u8 }; + let mut len = 0; + while unsafe { *str_addr.add(len) } != 0 { + len += 1; + } + let got = core::str::from_utf8(unsafe { slice::from_raw_parts(str_addr, len) }) + .expect("Invalid UTF-8 in envp"); + assert_eq!(got, &envs[i], "envp[{}] mismatch", i); + }); + } +} diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs new file mode 100644 index 00000000..5dc170ec --- /dev/null +++ b/kernel/src/shell.rs @@ -0,0 +1,240 @@ +use alloc::{ + fmt::format, + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ + devices::ps2_dev::keyboard, + events::schedule_kernel, + serial_println, + syscalls::syscall_handlers::{sys_exec, sys_read, sys_write}, +}; + +pub struct Shell { + buffer: [u8; 256], + position: usize, + env: Vec, +} +impl Shell { + pub fn new() -> Self { + Self { + buffer: [0; 256], + position: 0, + env: Vec::new(), + } + } + pub fn run(self) { + serial_println!("SHELL RUNNING"); + self.print_prompt(); + let mut i = 0; + + schedule_kernel( + async move { + let mut shell = self; + loop { + let c = shell.read_char(); + match c { + b'\n' | b'\r' => { + shell.execute_command().await; + keyboard::flush_buffer(); + shell.print_prompt(); + serial_println!("ENVS: {:#?}", shell.env); + // TODO: Until the heap allocation error is fixed arbitrary number of + // commands allowed to run + i += 1; + if i == 5 { + return; + } + } + 0x08 => shell.handle_backspace(), + _ if c.is_ascii_graphic() || c == b' ' => shell.handle_char(c), + _ => {} + } + // yield_now().await + } + }, + 3, + ); + } + + fn read_char(&mut self) -> u8 { + let mut c: u8 = 0; + unsafe { sys_read(0, &mut c as *mut u8, 1) }; + match c { + b'\n' => { + // Enter handled separately + } + 0x08 => { + // Backspace handled separately + } + _ => { + // Only print printable ASCII characters + if c.is_ascii_graphic() || c == b' ' { + self.print(&format(format_args!("{}", c as char))); + } + } + } + c + } + + fn print(&self, s: &str) { + unsafe { sys_write(1, s.as_ptr() as *mut u8, s.len()) }; + } + + fn handle_char(&mut self, c: u8) { + if self.position < self.buffer.len() - 1 { + self.buffer[self.position] = c; + self.position += 1; + } + } + + fn handle_backspace(&mut self) { + if self.position > 0 { + self.position -= 1; + self.print("\x08 \x08"); + } + } + + async fn execute_command(&mut self) { + let cmd_owned = { + let slice = &self.buffer[..self.position]; + String::from_utf8_lossy(slice).to_string() + }; + self.print("\n"); + self.process_command(&cmd_owned); + self.position = 0; + } + + fn process_command(&mut self, cmd: &str) { + let trimmed = cmd.trim(); + match trimmed { + "" => {} + + "help" => self.print("Available: help, echo, clear, export, [/cmd]\n"), + + "clear" => self.print("\x1B[2J\x1B[H"), + + t if t.starts_with("echo ") => { + let raw = t.strip_prefix("echo ").unwrap(); + // simple var‐expansion: $VAR or ${VAR} + let mut out = String::new(); + let mut chars = raw.chars().peekable(); + while let Some(c) = chars.next() { + // At this point we know that we're printing out an environment variable + if c == '$' { + // detect ${VAR} vs $VAR + let name = if chars.peek() == Some(&'{') { + chars.next(); // skip '{' + let mut nm = String::new(); + while let Some(&nx) = chars.peek() { + if nx == '}' { + chars.next(); + break; + } + nm.push(nx); + chars.next(); + } + nm + } else { + let mut nm = String::new(); + while let Some(&next) = chars.peek() { + if !next.is_ascii_alphanumeric() && next != '_' { + break; + } + nm.push(next); + chars.next(); + } + nm + }; + // lookup NAME in self.env + let val = self + .env + .iter() + .find_map(|kv| { + let mut sp = kv.splitn(2, '='); + if sp.next()? == name { + sp.next().map(|v| v.to_string()) + } else { + None + } + }) + .unwrap_or_default(); + out.push_str(&val); + } else { + out.push(c); + } + } + self.print(&format(format_args!("{}\n", out))); + } + + t if t.starts_with("export ") => { + // export KEY=VAL + if let Some(rest) = t.strip_prefix("export ") { + if let Some((k, v)) = rest.split_once('=') { + let pair = format(format_args!("{}={}", k, v)); + self.env.push(pair); + } + } + } + + t if t.starts_with('/') => { + // build argv[] + const MAX_ARGS: usize = 16; + let mut argv: [*mut u8; MAX_ARGS + 1] = [core::ptr::null_mut(); MAX_ARGS + 1]; + let mut argc = 0; + let mut start = 0; + + // split buffer on spaces + for i in 0..=self.position { + if i == self.position || self.buffer[i] == b' ' { + self.buffer[i] = 0; + if argc < MAX_ARGS { + argv[argc] = unsafe { self.buffer.as_mut_ptr().add(start) }; + argc += 1; + } + start = i + 1; + } + } + // end + argv[argc] = core::ptr::null_mut(); + + // build envp[] + let mut envp: Vec<*mut u8> = self + .env + .iter_mut() + .map(|s| { + s.push('\0'); + s.as_mut_ptr() + }) + .collect(); + // end + envp.push(core::ptr::null_mut()); + serial_println!("EXECUTING"); + unsafe { + sys_exec(argv[0], argv.as_mut_ptr(), envp.as_mut_ptr()); + } + } + + _ => self.print("Unknown command\n"), + } + } + + fn print_prompt(&self) { + self.print("> "); + } +} + +impl Default for Shell { + fn default() -> Self { + Self::new() + } +} + +/// # Safety +/// TODO +pub unsafe fn init() { + keyboard::flush_buffer(); + let shell = Shell::new(); + shell.run(); +} diff --git a/kernel/src/syscalls/fork.rs b/kernel/src/syscalls/fork.rs index e3399ef6..1586079f 100644 --- a/kernel/src/syscalls/fork.rs +++ b/kernel/src/syscalls/fork.rs @@ -170,144 +170,145 @@ fn duplicate_page_table_recursive(parent_frame: PhysFrame, level: u8) -> PhysFra child_frame } -#[cfg(test)] -mod tests { - use x86_64::structures::paging::{ - OffsetPageTable, PageTable, PageTableFlags, PhysFrame, Size4KiB, - }; - - use crate::{ - constants::processes::FORK_SIMPLE, - events::{ - current_running_event, futures::await_on::AwaitProcess, get_runner_time, - schedule_process, - }, - memory::HHDM_OFFSET, - processes::{process::create_process, registers::ForkingRegisters}, - syscalls::syscall_handlers::{EXIT_CODES, PML4_FRAMES, REGISTER_VALUES}, - }; - - fn verify_page_table_walk(parent_pml4: PhysFrame, child_pml4: PhysFrame) { - let parent_mapper = unsafe { - let virt = *HHDM_OFFSET + parent_pml4.start_address().as_u64(); - let ptr = virt.as_mut_ptr::(); - OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) - }; - let child_mapper = unsafe { - let virt = *HHDM_OFFSET + child_pml4.start_address().as_u64(); - let ptr = virt.as_mut_ptr::(); - OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) - }; - - for i in 0..256 { - let parent_entry = &parent_mapper.level_4_table()[i]; - let child_entry = &child_mapper.level_4_table()[i]; - if parent_entry.is_unused() { - assert!(child_entry.is_unused()); - continue; - } else { - assert_eq!(parent_entry.flags(), child_entry.flags()); - let parent_pdpt_frame = PhysFrame::containing_address(parent_entry.addr()); - recursive_walk(parent_pdpt_frame, 3); - } - } - } - - fn recursive_walk(parent_frame: PhysFrame, level: u8) { - let parent_virt = HHDM_OFFSET.as_u64() + parent_frame.start_address().as_u64(); - - let parent_table = unsafe { &mut *(parent_virt as *mut PageTable) }; - let child_table = unsafe { &mut *(parent_virt as *mut PageTable) }; - - for i in 0..512 { - let parent_entry = &parent_table[i]; - let child_entry = &child_table[i]; - if parent_entry.is_unused() { - assert!(child_entry.is_unused()); - continue; - } - - // from the parent and child tables, ensure each entry is the same - assert_eq!(parent_entry.flags(), child_entry.flags()); - if level == 1 { - // This logic is not correct and only works if you don't update the Writable flag during COW duplicate - if parent_entry.flags().contains(PageTableFlags::WRITABLE) { - assert!(parent_entry.flags().contains(PageTableFlags::BIT_9)); - } - assert_eq!(parent_entry.addr(), child_entry.addr()); - assert_eq!( - parent_entry.frame().expect("Could not find frame."), - child_entry.frame().expect("Could not find frame.") - ); - } - if level > 1 { - let parent_frame: PhysFrame = PhysFrame::containing_address(parent_entry.addr()); - recursive_walk(parent_frame, level - 1); - } - } - } - - #[test_case] - async fn test_simple_fork() { - let parent_pid = create_process(FORK_SIMPLE); - schedule_process(parent_pid); - let _waiter = AwaitProcess::new( - parent_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - let child_pid = parent_pid + 1; - - let _waiter = AwaitProcess::new( - parent_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - let _waiter = AwaitProcess::new( - child_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - let exit_codes = EXIT_CODES.lock(); - let parent_exit_code = exit_codes - .get(&parent_pid) - .expect("Could not find parent pid."); - let child_exit_code = exit_codes - .get(&child_pid) - .expect("Could not find child pid."); - - let registers = REGISTER_VALUES.lock(); - - let (parent_regs_ptr, child_regs_ptr) = { - let parent = registers - .get(&parent_pid) - .expect("Could not find parent pid.") - as *const ForkingRegisters; - let child = registers - .get(&child_pid) - .expect("Could not find child pid.") - as *const ForkingRegisters; - (parent, child) - }; - - let parent_regs: &mut ForkingRegisters = unsafe { &mut *(parent_regs_ptr as *mut _) }; - let child_regs: &mut ForkingRegisters = unsafe { &mut *(child_regs_ptr as *mut _) }; - - let frames = PML4_FRAMES.lock(); - let parent_pml4 = frames.get(&parent_pid).expect("Could not find parent pid."); - let child_pml4 = frames.get(&child_pid).expect("Could not find child pid."); - - assert_eq!(parent_exit_code, child_exit_code); - assert_eq!(child_pid as u64, parent_regs.r12); - child_regs.r12 = child_pid as u64; - assert_eq!(parent_regs, child_regs); - - // check that the pml4 frame is set correctly - verify_page_table_walk(*parent_pml4, *child_pml4); - } -} +// #[cfg(test)] +// mod tests { +// use alloc::vec::Vec; +// use x86_64::structures::paging::{ +// OffsetPageTable, PageTable, PageTableFlags, PhysFrame, Size4KiB, +// }; +// +// use crate::{ +// constants::processes::FORK_SIMPLE, +// events::{ +// current_running_event, futures::await_on::AwaitProcess, get_runner_time, +// schedule_process, +// }, +// memory::HHDM_OFFSET, +// processes::{process::create_process, registers::ForkingRegisters}, +// syscalls::syscall_handlers::{EXIT_CODES, PML4_FRAMES, REGISTER_VALUES}, +// }; +// +// fn verify_page_table_walk(parent_pml4: PhysFrame, child_pml4: PhysFrame) { +// let parent_mapper = unsafe { +// let virt = *HHDM_OFFSET + parent_pml4.start_address().as_u64(); +// let ptr = virt.as_mut_ptr::(); +// OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) +// }; +// let child_mapper = unsafe { +// let virt = *HHDM_OFFSET + child_pml4.start_address().as_u64(); +// let ptr = virt.as_mut_ptr::(); +// OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) +// }; +// +// for i in 0..256 { +// let parent_entry = &parent_mapper.level_4_table()[i]; +// let child_entry = &child_mapper.level_4_table()[i]; +// if parent_entry.is_unused() { +// assert!(child_entry.is_unused()); +// continue; +// } else { +// assert_eq!(parent_entry.flags(), child_entry.flags()); +// let parent_pdpt_frame = PhysFrame::containing_address(parent_entry.addr()); +// recursive_walk(parent_pdpt_frame, 3); +// } +// } +// } +// +// fn recursive_walk(parent_frame: PhysFrame, level: u8) { +// let parent_virt = HHDM_OFFSET.as_u64() + parent_frame.start_address().as_u64(); +// +// let parent_table = unsafe { &mut *(parent_virt as *mut PageTable) }; +// let child_table = unsafe { &mut *(parent_virt as *mut PageTable) }; +// +// for i in 0..512 { +// let parent_entry = &parent_table[i]; +// let child_entry = &child_table[i]; +// if parent_entry.is_unused() { +// assert!(child_entry.is_unused()); +// continue; +// } +// +// // from the parent and child tables, ensure each entry is the same +// assert_eq!(parent_entry.flags(), child_entry.flags()); +// if level == 1 { +// // This logic is not correct and only works if you don't update the Writable flag during COW duplicate +// if parent_entry.flags().contains(PageTableFlags::WRITABLE) { +// assert!(parent_entry.flags().contains(PageTableFlags::BIT_9)); +// } +// assert_eq!(parent_entry.addr(), child_entry.addr()); +// assert_eq!( +// parent_entry.frame().expect("Could not find frame."), +// child_entry.frame().expect("Could not find frame.") +// ); +// } +// if level > 1 { +// let parent_frame: PhysFrame = PhysFrame::containing_address(parent_entry.addr()); +// recursive_walk(parent_frame, level - 1); +// } +// } +// } +// +// #[test_case] +// async fn test_simple_fork() { +// let parent_pid = create_process(FORK_SIMPLE, Vec::new(), Vec::new()); +// schedule_process(parent_pid); +// let _waiter = AwaitProcess::new( +// parent_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// let child_pid = parent_pid + 1; +// +// let _waiter = AwaitProcess::new( +// parent_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// let _waiter = AwaitProcess::new( +// child_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// let exit_codes = EXIT_CODES.lock(); +// let parent_exit_code = exit_codes +// .get(&parent_pid) +// .expect("Could not find parent pid."); +// let child_exit_code = exit_codes +// .get(&child_pid) +// .expect("Could not find child pid."); +// +// let registers = REGISTER_VALUES.lock(); +// +// let (parent_regs_ptr, child_regs_ptr) = { +// let parent = registers +// .get(&parent_pid) +// .expect("Could not find parent pid.") +// as *const ForkingRegisters; +// let child = registers +// .get(&child_pid) +// .expect("Could not find child pid.") +// as *const ForkingRegisters; +// (parent, child) +// }; +// +// let parent_regs: &mut ForkingRegisters = unsafe { &mut *(parent_regs_ptr as *mut _) }; +// let child_regs: &mut ForkingRegisters = unsafe { &mut *(child_regs_ptr as *mut _) }; +// +// let frames = PML4_FRAMES.lock(); +// let parent_pml4 = frames.get(&parent_pid).expect("Could not find parent pid."); +// let child_pml4 = frames.get(&child_pid).expect("Could not find child pid."); +// +// assert_eq!(parent_exit_code, child_exit_code); +// assert_eq!(child_pid as u64, parent_regs.r12); +// child_regs.r12 = child_pid as u64; +// assert_eq!(parent_regs, child_regs); +// +// // check that the pml4 frame is set correctly +// verify_page_table_walk(*parent_pml4, *child_pml4); +// } +// } diff --git a/kernel/src/syscalls/memorymap.rs b/kernel/src/syscalls/memorymap.rs index 607de3a1..54d3fc04 100644 --- a/kernel/src/syscalls/memorymap.rs +++ b/kernel/src/syscalls/memorymap.rs @@ -439,76 +439,78 @@ pub fn sys_munmap(addr: u64, len: u64) -> u64 { } // TODO: Mmap tests -#[cfg(test)] -mod tests { - use crate::{ - constants::processes::{ - TEST_MMAP_ANON_SHARED, TEST_MMAP_CHILD_WRITES, TEST_MPROTECT_CHILD_WRITES, - }, - events::schedule_process, - processes::process::create_process, - syscalls::syscall_handlers::REGISTER_VALUES, - }; - - use crate::events::{current_running_event, futures::await_on::AwaitProcess, get_runner_time}; - - #[test_case] - async fn mmap_anonymous_shared() { - let pid = create_process(TEST_MMAP_ANON_SHARED); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'A' as u64); - assert_eq!(reg_vals_on_exit.r9, 'B' as u64); - assert_eq!(reg_vals_on_exit.r10, 'C' as u64); - } - - #[test_case] - async fn mmap_anonymous_child_writes_first() { - let pid = create_process(TEST_MMAP_CHILD_WRITES); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'X' as u64); - assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); - assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); - } - - #[test_case] - async fn mprotect_child_writes() { - let pid = create_process(TEST_MPROTECT_CHILD_WRITES); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'X' as u64); - assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); - assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); - } -} +// #[cfg(test)] +// mod tests { +// use alloc::vec::Vec; +// +// use crate::{ +// constants::processes::{ +// TEST_MMAP_ANON_SHARED, TEST_MMAP_CHILD_WRITES, TEST_MPROTECT_CHILD_WRITES, +// }, +// events::schedule_process, +// processes::process::create_process, +// syscalls::syscall_handlers::REGISTER_VALUES, +// }; +// +// use crate::events::{current_running_event, futures::await_on::AwaitProcess, get_runner_time}; +// +// #[test_case] +// async fn mmap_anonymous_shared() { +// let pid = create_process(TEST_MMAP_ANON_SHARED, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'A' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'B' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'C' as u64); +// } +// +// #[test_case] +// async fn mmap_anonymous_child_writes_first() { +// let pid = create_process(TEST_MMAP_CHILD_WRITES, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'X' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); +// } +// +// #[test_case] +// async fn mprotect_child_writes() { +// let pid = create_process(TEST_MPROTECT_CHILD_WRITES, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'X' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); +// } +// } diff --git a/kernel/src/syscalls/mod.rs b/kernel/src/syscalls/mod.rs index c22f6dcb..5229e51c 100644 --- a/kernel/src/syscalls/mod.rs +++ b/kernel/src/syscalls/mod.rs @@ -4,6 +4,7 @@ pub mod syscall_handlers; #[cfg(test)] mod tests { + use alloc::vec::Vec; use syscall_handlers::EXIT_CODES; use crate::{ @@ -20,7 +21,7 @@ mod tests { /// The binary exits with code 0 immediately #[test_case] async fn test_exit_code() { - let pid = create_process(TEST_EXIT_CODE); + let pid = create_process(TEST_EXIT_CODE, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -39,7 +40,7 @@ mod tests { /// content is correct #[test_case] async fn test_print_exit() { - let pid = create_process(TEST_PRINT_EXIT); + let pid = create_process(TEST_PRINT_EXIT, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -61,7 +62,7 @@ mod tests { /// from waiting #[test_case] async fn test_fork_wait() { - let pid = create_process(TEST_WAIT); + let pid = create_process(TEST_WAIT, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -83,7 +84,7 @@ mod tests { /// Currently, requires manual verification of buffer #[test_case] async fn test_fork_cow() { - let pid = create_process(TEST_FORK_COW); + let pid = create_process(TEST_FORK_COW, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( diff --git a/kernel/src/syscalls/syscall_handlers.rs b/kernel/src/syscalls/syscall_handlers.rs index e1ccf246..986966ca 100644 --- a/kernel/src/syscalls/syscall_handlers.rs +++ b/kernel/src/syscalls/syscall_handlers.rs @@ -1,9 +1,13 @@ use core::ffi::CStr; -use alloc::collections::btree_map::BTreeMap; +use alloc::{collections::btree_map::BTreeMap, slice, string::ToString, vec}; use lazy_static::lazy_static; +use pc_keyboard::{DecodedKey, KeyCode, KeyState}; use spin::Mutex; -use x86_64::structures::paging::{PhysFrame, Size4KiB}; +use x86_64::{ + align_up, + structures::paging::{PhysFrame, Size4KiB}, +}; use core::{ future::Future, @@ -12,19 +16,26 @@ use core::{ }; use crate::{ - constants::syscalls::*, + constants::{memory::PAGE_SIZE, syscalls::*}, + devices::ps2_dev::keyboard, events::{ current_running_event, current_running_event_info, futures::await_on::AwaitProcess, - get_runner_time, yield_now, EventInfo, + get_runner_time, schedule_kernel, schedule_process, yield_now, EventInfo, }, + filesys::{get_file, FileSystem, OpenFlags, FILESYSTEM}, interrupts::x2apic, memory::frame_allocator::with_buddy_frame_allocator, processes::{ - process::{sleep_process_int, sleep_process_syscall, ProcessState, PROCESS_TABLE}, + process::{ + create_process, sleep_process_int, sleep_process_syscall, ProcessState, PROCESS_TABLE, + }, registers::ForkingRegisters, }, - serial_println, - syscalls::{fork::sys_fork, memorymap::sys_mmap}, + serial_print, serial_println, + syscalls::{ + fork::sys_fork, + memorymap::{sys_mmap, MmapFlags, ProtFlags}, + }, }; use core::arch::naked_asm; @@ -178,12 +189,215 @@ pub unsafe extern "C" fn syscall_handler_impl( SYSCALL_WAIT => block_on(sys_wait(syscall.arg1 as u32)), SYSCALL_MUNMAP => sys_munmap(syscall.arg1, syscall.arg2), SYSCALL_MPROTECT => sys_mprotect(syscall.arg1, syscall.arg2, syscall.arg3), + SYSCALL_READ => sys_read( + syscall.arg1 as u32, + syscall.arg2 as *mut u8, + syscall.arg3 as usize, + ), + SYSCALL_WRITE => sys_write( + syscall.arg1 as u32, + syscall.arg2 as *mut u8, + syscall.arg3 as usize, + ), + SYSCALL_EXECVE => sys_exec( + syscall.arg1 as *mut u8, + syscall.arg2 as *mut *mut u8, + syscall.arg3 as *mut *mut u8, + ), + _ => { panic!("Unknown syscall, {}", syscall.number); } } } +/// # Safety +/// TODO +pub unsafe fn sys_exec(path: *mut u8, argv: *mut *mut u8, envp: *mut *mut u8) -> u64 { + if path.is_null() { + return u64::MAX; + } + + // Find string length by looking for null terminator + let mut len = 0; + unsafe { + while *path.add(len) != 0 { + len += 1; + } + } + + // Convert to string + let bytes = unsafe { alloc::slice::from_raw_parts(path, len) }; + let pathname = match alloc::str::from_utf8(bytes) { + Ok(s) => s, + Err(_) => return u64::MAX, + }; + + // build args + let mut args = vec![]; + unsafe { + let mut i = 0; + loop { + let ptr = *argv.add(i); + if ptr.is_null() { + break; + } + let mut l = 0; + while *ptr.add(l) != 0 { + l += 1; + } + let slice = slice::from_raw_parts(&*ptr, l); + if let Ok(s) = str::from_utf8(slice) { + args.push(s.to_string()); + } + i += 1; + } + } + + serial_println!("NOT EXITING"); + + // build env vars + let mut envs = vec![]; + unsafe { + let mut i = 0; + loop { + let ptr = *envp.add(i); + if ptr.is_null() { + break; + } + let mut l = 0; + while *ptr.add(l) != 0 { + l += 1; + } + let slice = slice::from_raw_parts(&*ptr, l); + if let Ok(s) = str::from_utf8(slice) { + envs.push(s.to_string()); + } + i += 1; + } + } + serial_println!("PATHNAME: {:#?}", pathname); + serial_println!("CMD ARGS: {:#?}", args); + serial_println!("ENV VARS: {:#?}", envs); + schedule_kernel( + async { + let fs = FILESYSTEM.get().unwrap(); + let fd = { + fs.lock() + .open_file( + "/executables/hello", + OpenFlags::O_RDONLY | OpenFlags::O_WRONLY, + ) + .await + }; + // if fd.is_err() { + // serial_println!("Unknown command"); + // return; + // } + serial_println!("RUNNING EXECUTABLE PLEASE HOLD"); + // At this point we assume a valid executable + // TODO: check if it is actually executable with chmod mode + let fd = fd.unwrap(); + let file = get_file(fd).unwrap(); + let file_len = { + fs.lock() + .filesystem + .lock() + .get_node(&file.lock().pathname) + .await + .unwrap() + .size() + }; + sys_mmap( + 0x9000, + align_up(file_len, PAGE_SIZE as u64), + ProtFlags::PROT_EXEC.bits(), + MmapFlags::MAP_FILE.bits(), + fd as i64, + 0, + ); + + serial_println!("Reading file..."); + + let mut buffer = vec![0u8; file_len as usize]; + let bytes_read = { + fs.lock() + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + + serial_println!("Bytes read: {:#?}", bytes_read); + + let pid = create_process(buf, args, envs); + schedule_process(pid); + let _waiter = AwaitProcess::new( + pid, + get_runner_time(3_000_000_000), + current_running_event().unwrap(), + ) + .await; + }, + 3, + ); + 0 +} + +/// # Safety +/// TODO +pub unsafe fn sys_read(fd: u32, buf: *mut u8, count: usize) -> u64 { + if fd == 0 { + let mut i = 0; + while i < count { + unsafe { + match keyboard::try_read_event() { + Some(event) => { + if let Some(c) = event_to_ascii(&event) { + *buf.add(i) = c; + i += 1; + } + } + None => break, // Exit early + } + } + } + i as u64 + } else { + u64::MAX + } +} + +/// # Safety +/// TODO +pub unsafe fn sys_write(fd: u32, buf: *const u8, count: usize) -> u64 { + if fd == 1 { + // STDOUT + unsafe { + let slice = core::slice::from_raw_parts(buf, count); + serial_print!("{}", core::str::from_utf8_unchecked(slice)); + } + count as u64 + } else { + u64::MAX // Error + } +} + +// Helper to convert keyboard events to ASCII +pub fn event_to_ascii(event: &keyboard::KeyboardEvent) -> Option { + if event.state != KeyState::Down { + return None; + } + + match event.decoded { + Some(DecodedKey::Unicode(c)) if c.is_ascii() => Some(c as u8), + Some(DecodedKey::RawKey(KeyCode::Return)) => Some(b'\n'), + Some(DecodedKey::RawKey(KeyCode::Backspace)) => Some(0x08), + _ => None, + } +} + pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { // TODO handle hierarchy (parent processes), resources, threads, etc. diff --git a/resources/executables/hello b/resources/executables/hello new file mode 100755 index 00000000..27b9f7b3 Binary files /dev/null and b/resources/executables/hello differ diff --git a/resources/executables/hello.c b/resources/executables/hello.c new file mode 100644 index 00000000..dafadc78 --- /dev/null +++ b/resources/executables/hello.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello World!\n"); + return 0; +} diff --git a/resources/executables/ret b/resources/executables/ret new file mode 100755 index 00000000..dee274c0 Binary files /dev/null and b/resources/executables/ret differ diff --git a/resources/executables/ret.c b/resources/executables/ret.c new file mode 100644 index 00000000..4cce7f66 --- /dev/null +++ b/resources/executables/ret.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} diff --git a/setup_sysroot.sh b/setup_sysroot.sh new file mode 100755 index 00000000..994c918b --- /dev/null +++ b/setup_sysroot.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -euo pipefail + +# Ensure system architecture is x86_64 +ARCH="$(uname -m)" +if [[ "${ARCH}" != "x86_64" ]]; then + echo "This script only supports x86_64 architecture" + exit 1 +fi + +SYSROOT="$HOME/linux_sysroot" +MUSL_VERSION="1.2.5" +MUSL_TAR="musl-${MUSL_VERSION}.tar.gz" +MUSL_URL="https://musl.libc.org/releases/${MUSL_TAR}" + +# Create sysroot and build dirs +echo "Creating sysroot directory at ${SYSROOT}..." +mkdir -p "${SYSROOT}" +BUILD_DIR="$(mktemp -d)" +echo "Using build directory: ${BUILD_DIR}" + +# Download musl tar +echo "Downloading and extracting musl ${MUSL_VERSION}..." +curl -LO "${MUSL_URL}" +tar -xf "${MUSL_TAR}" -C "${BUILD_DIR}" + +pushd "${BUILD_DIR}/musl-${MUSL_VERSION}" >/dev/null + +# configure for static linking +echo "Configuring musl..." +./configure --prefix=/usr --sysroot=~/linux_sysroot --disable-shared +make && make install DESTDIR=~/linux_sysroot + +popd >/dev/null + +# Update musl-gcc wrapper to point to the correct specs file +echo "Updating musl-gcc wrapper..." +MUSL_GCC_PATH="${SYSROOT}/usr/bin/musl-gcc" +if [ -f "${MUSL_GCC_PATH}" ]; then + "${SYSROOT}/usr/bin/musl-gcc" -dumpspecs >"${SYSROOT}/usr/lib/musl-gcc.specs" + sed -i 's|exec "${REALGCC:-gcc}" "$@" -specs "/usr/lib/musl-gcc.specs"|"${REALGCC:-gcc}" "$@" -specs "'${SYSROOT}'/usr/lib/musl-gcc.specs"|g' "${MUSL_GCC_PATH}" +else + echo "musl-gcc wrapper not found at ${MUSL_GCC_PATH}" +fi + +# Clean up build directory and tarball +echo "Cleaning up..." +rm -rf "${BUILD_DIR}" +rm -f "${MUSL_TAR}" + +echo "Sysroot setup complete at ${SYSROOT}." +echo "You can now statically compile C programs using musl by invoking:" +echo " ${SYSROOT}/usr/bin/musl-gcc -static -o your_program your_program.c"