Microkernel based hobby OS

More build

Changed files
+1072 -53
aethelos-source
+2 -7
aethelos-source/heartwood/src/attunement/idt_handlers.rs
··· 247 247 } 248 248 249 249 extern "C" fn keyboard_inner() { 250 - // Will be connected to keyboard driver 251 - // For now, just read and discard the scancode 252 - unsafe { 253 - let scancode: u8; 254 - asm!("in al, 0x60", out("al") scancode, options(nostack, preserves_flags)); 255 - // TODO: Send to keyboard driver via Nexus 256 - } 250 + // Handle keyboard interrupt via the keyboard driver 251 + crate::attunement::keyboard::on_interrupt(); 257 252 } 258 253 259 254 // Stub handlers for other hardware interrupts
+507
aethelos-source/heartwood/src/attunement/keyboard.rs
··· 1 + //! # PS/2 Keyboard Driver 2 + //! 3 + //! The voice of the user - how humans commune with AethelOS. 4 + //! Each keystroke is a word in the conversation between human and machine. 5 + //! 6 + //! ## Philosophy 7 + //! Input is not commands to be obeyed, but intentions to be understood. 8 + //! We listen to the keyboard not as servants awaiting orders, 9 + //! but as partners engaged in dialogue. 10 + //! 11 + //! ## Technical Details 12 + //! The PS/2 keyboard controller uses IRQ 1 (interrupt 33 after remapping). 13 + //! Scancodes arrive as single or multi-byte sequences via port 0x60. 14 + //! We translate scan codes (Set 1) into meaningful key events. 15 + 16 + use core::arch::asm; 17 + use lazy_static::lazy_static; 18 + use spin::Mutex; 19 + 20 + /// PS/2 keyboard data port 21 + const KEYBOARD_DATA: u16 = 0x60; 22 + 23 + /// PS/2 keyboard status/command port 24 + const KEYBOARD_STATUS: u16 = 0x64; 25 + 26 + /// Status register flags 27 + const STATUS_OUTPUT_FULL: u8 = 0x01; 28 + 29 + lazy_static! { 30 + /// Global keyboard state 31 + pub static ref KEYBOARD: Mutex<Keyboard> = Mutex::new(Keyboard::new()); 32 + } 33 + 34 + /// Keyboard modifier state 35 + #[derive(Debug, Clone, Copy, Default)] 36 + pub struct Modifiers { 37 + pub left_shift: bool, 38 + pub right_shift: bool, 39 + pub left_ctrl: bool, 40 + pub right_ctrl: bool, 41 + pub left_alt: bool, 42 + pub right_alt: bool, 43 + pub caps_lock: bool, 44 + pub num_lock: bool, 45 + pub scroll_lock: bool, 46 + } 47 + 48 + impl Modifiers { 49 + /// Check if any shift key is pressed 50 + pub fn shift(&self) -> bool { 51 + self.left_shift || self.right_shift 52 + } 53 + 54 + /// Check if any ctrl key is pressed 55 + pub fn ctrl(&self) -> bool { 56 + self.left_ctrl || self.right_ctrl 57 + } 58 + 59 + /// Check if any alt key is pressed 60 + pub fn alt(&self) -> bool { 61 + self.left_alt || self.right_alt 62 + } 63 + } 64 + 65 + /// A keyboard event 66 + #[derive(Debug, Clone, Copy)] 67 + pub enum KeyEvent { 68 + /// Key was pressed 69 + Pressed(Key), 70 + /// Key was released 71 + Released(Key), 72 + } 73 + 74 + /// Represents a key on the keyboard 75 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 76 + pub enum Key { 77 + // Letters 78 + A, B, C, D, E, F, G, H, I, J, K, L, M, 79 + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 80 + 81 + // Numbers 82 + Num0, Num1, Num2, Num3, Num4, 83 + Num5, Num6, Num7, Num8, Num9, 84 + 85 + // Function keys 86 + F1, F2, F3, F4, F5, F6, 87 + F7, F8, F9, F10, F11, F12, 88 + 89 + // Special keys 90 + Escape, 91 + Backspace, 92 + Tab, 93 + Enter, 94 + Space, 95 + 96 + // Modifiers 97 + LeftShift, 98 + RightShift, 99 + LeftCtrl, 100 + RightCtrl, 101 + LeftAlt, 102 + RightAlt, 103 + 104 + // Lock keys 105 + CapsLock, 106 + NumLock, 107 + ScrollLock, 108 + 109 + // Navigation 110 + Insert, 111 + Delete, 112 + Home, 113 + End, 114 + PageUp, 115 + PageDown, 116 + ArrowUp, 117 + ArrowDown, 118 + ArrowLeft, 119 + ArrowRight, 120 + 121 + // Punctuation and symbols 122 + Minus, // - 123 + Equal, // = 124 + LeftBracket, // [ 125 + RightBracket, // ] 126 + Backslash, // \ 127 + Semicolon, // ; 128 + Quote, // ' 129 + Grave, // ` 130 + Comma, // , 131 + Period, // . 132 + Slash, // / 133 + 134 + // Numpad 135 + NumpadDivide, 136 + NumpadMultiply, 137 + NumpadMinus, 138 + NumpadPlus, 139 + NumpadEnter, 140 + NumpadPeriod, 141 + Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, 142 + Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, 143 + 144 + // Unknown key 145 + Unknown(u8), 146 + } 147 + 148 + impl Key { 149 + /// Convert key to ASCII character if possible 150 + pub fn to_ascii(&self, modifiers: &Modifiers) -> Option<char> { 151 + let shift = modifiers.shift() ^ modifiers.caps_lock; 152 + 153 + match self { 154 + // Letters 155 + Key::A => Some(if shift { 'A' } else { 'a' }), 156 + Key::B => Some(if shift { 'B' } else { 'b' }), 157 + Key::C => Some(if shift { 'C' } else { 'c' }), 158 + Key::D => Some(if shift { 'D' } else { 'd' }), 159 + Key::E => Some(if shift { 'E' } else { 'e' }), 160 + Key::F => Some(if shift { 'F' } else { 'f' }), 161 + Key::G => Some(if shift { 'G' } else { 'g' }), 162 + Key::H => Some(if shift { 'H' } else { 'h' }), 163 + Key::I => Some(if shift { 'I' } else { 'i' }), 164 + Key::J => Some(if shift { 'J' } else { 'j' }), 165 + Key::K => Some(if shift { 'K' } else { 'k' }), 166 + Key::L => Some(if shift { 'L' } else { 'l' }), 167 + Key::M => Some(if shift { 'M' } else { 'm' }), 168 + Key::N => Some(if shift { 'N' } else { 'n' }), 169 + Key::O => Some(if shift { 'O' } else { 'o' }), 170 + Key::P => Some(if shift { 'P' } else { 'p' }), 171 + Key::Q => Some(if shift { 'Q' } else { 'q' }), 172 + Key::R => Some(if shift { 'R' } else { 'r' }), 173 + Key::S => Some(if shift { 'S' } else { 's' }), 174 + Key::T => Some(if shift { 'T' } else { 't' }), 175 + Key::U => Some(if shift { 'U' } else { 'u' }), 176 + Key::V => Some(if shift { 'V' } else { 'v' }), 177 + Key::W => Some(if shift { 'W' } else { 'w' }), 178 + Key::X => Some(if shift { 'X' } else { 'x' }), 179 + Key::Y => Some(if shift { 'Y' } else { 'y' }), 180 + Key::Z => Some(if shift { 'Z' } else { 'z' }), 181 + 182 + // Numbers 183 + Key::Num0 => Some(if modifiers.shift() { ')' } else { '0' }), 184 + Key::Num1 => Some(if modifiers.shift() { '!' } else { '1' }), 185 + Key::Num2 => Some(if modifiers.shift() { '@' } else { '2' }), 186 + Key::Num3 => Some(if modifiers.shift() { '#' } else { '3' }), 187 + Key::Num4 => Some(if modifiers.shift() { '$' } else { '4' }), 188 + Key::Num5 => Some(if modifiers.shift() { '%' } else { '5' }), 189 + Key::Num6 => Some(if modifiers.shift() { '^' } else { '6' }), 190 + Key::Num7 => Some(if modifiers.shift() { '&' } else { '7' }), 191 + Key::Num8 => Some(if modifiers.shift() { '*' } else { '8' }), 192 + Key::Num9 => Some(if modifiers.shift() { '(' } else { '9' }), 193 + 194 + // Special keys 195 + Key::Space => Some(' '), 196 + Key::Enter => Some('\n'), 197 + Key::Tab => Some('\t'), 198 + Key::Backspace => Some('\x08'), 199 + 200 + // Punctuation 201 + Key::Minus => Some(if modifiers.shift() { '_' } else { '-' }), 202 + Key::Equal => Some(if modifiers.shift() { '+' } else { '=' }), 203 + Key::LeftBracket => Some(if modifiers.shift() { '{' } else { '[' }), 204 + Key::RightBracket => Some(if modifiers.shift() { '}' } else { ']' }), 205 + Key::Backslash => Some(if modifiers.shift() { '|' } else { '\\' }), 206 + Key::Semicolon => Some(if modifiers.shift() { ':' } else { ';' }), 207 + Key::Quote => Some(if modifiers.shift() { '"' } else { '\'' }), 208 + Key::Grave => Some(if modifiers.shift() { '~' } else { '`' }), 209 + Key::Comma => Some(if modifiers.shift() { '<' } else { ',' }), 210 + Key::Period => Some(if modifiers.shift() { '>' } else { '.' }), 211 + Key::Slash => Some(if modifiers.shift() { '?' } else { '/' }), 212 + 213 + // Numpad (when numlock is on) 214 + Key::Numpad0 => if modifiers.num_lock { Some('0') } else { None }, 215 + Key::Numpad1 => if modifiers.num_lock { Some('1') } else { None }, 216 + Key::Numpad2 => if modifiers.num_lock { Some('2') } else { None }, 217 + Key::Numpad3 => if modifiers.num_lock { Some('3') } else { None }, 218 + Key::Numpad4 => if modifiers.num_lock { Some('4') } else { None }, 219 + Key::Numpad5 => if modifiers.num_lock { Some('5') } else { None }, 220 + Key::Numpad6 => if modifiers.num_lock { Some('6') } else { None }, 221 + Key::Numpad7 => if modifiers.num_lock { Some('7') } else { None }, 222 + Key::Numpad8 => if modifiers.num_lock { Some('8') } else { None }, 223 + Key::Numpad9 => if modifiers.num_lock { Some('9') } else { None }, 224 + Key::NumpadPeriod => if modifiers.num_lock { Some('.') } else { None }, 225 + Key::NumpadDivide => Some('/'), 226 + Key::NumpadMultiply => Some('*'), 227 + Key::NumpadMinus => Some('-'), 228 + Key::NumpadPlus => Some('+'), 229 + Key::NumpadEnter => Some('\n'), 230 + 231 + // Non-printable keys 232 + _ => None, 233 + } 234 + } 235 + } 236 + 237 + /// PS/2 Keyboard state machine 238 + pub struct Keyboard { 239 + modifiers: Modifiers, 240 + extended_scancode: bool, 241 + } 242 + 243 + impl Keyboard { 244 + /// Create a new keyboard state 245 + pub const fn new() -> Self { 246 + Keyboard { 247 + modifiers: Modifiers { 248 + left_shift: false, 249 + right_shift: false, 250 + left_ctrl: false, 251 + right_ctrl: false, 252 + left_alt: false, 253 + right_alt: false, 254 + caps_lock: false, 255 + num_lock: false, 256 + scroll_lock: false, 257 + }, 258 + extended_scancode: false, 259 + } 260 + } 261 + 262 + /// Get current modifiers 263 + pub fn modifiers(&self) -> Modifiers { 264 + self.modifiers 265 + } 266 + 267 + /// Process a scancode byte and return a key event 268 + pub fn process_scancode(&mut self, scancode: u8) -> Option<KeyEvent> { 269 + // Handle extended scancodes (0xE0 prefix) 270 + if scancode == 0xE0 { 271 + self.extended_scancode = true; 272 + return None; 273 + } 274 + 275 + // Check if this is a key release (high bit set) 276 + let released = (scancode & 0x80) != 0; 277 + let code = scancode & 0x7F; 278 + 279 + // Translate scancode to key 280 + let key = if self.extended_scancode { 281 + self.extended_scancode = false; 282 + match code { 283 + 0x1D => Key::RightCtrl, 284 + 0x38 => Key::RightAlt, 285 + 0x48 => Key::ArrowUp, 286 + 0x50 => Key::ArrowDown, 287 + 0x4B => Key::ArrowLeft, 288 + 0x4D => Key::ArrowRight, 289 + 0x49 => Key::PageUp, 290 + 0x51 => Key::PageDown, 291 + 0x47 => Key::Home, 292 + 0x4F => Key::End, 293 + 0x52 => Key::Insert, 294 + 0x53 => Key::Delete, 295 + 0x35 => Key::NumpadDivide, 296 + _ => Key::Unknown(scancode), 297 + } 298 + } else { 299 + scancode_to_key(code) 300 + }; 301 + 302 + // Update modifier state 303 + self.update_modifiers(&key, released); 304 + 305 + // Return the event 306 + if released { 307 + Some(KeyEvent::Released(key)) 308 + } else { 309 + Some(KeyEvent::Pressed(key)) 310 + } 311 + } 312 + 313 + /// Update modifier key states 314 + fn update_modifiers(&mut self, key: &Key, released: bool) { 315 + match key { 316 + Key::LeftShift => self.modifiers.left_shift = !released, 317 + Key::RightShift => self.modifiers.right_shift = !released, 318 + Key::LeftCtrl => self.modifiers.left_ctrl = !released, 319 + Key::RightCtrl => self.modifiers.right_ctrl = !released, 320 + Key::LeftAlt => self.modifiers.left_alt = !released, 321 + Key::RightAlt => self.modifiers.right_alt = !released, 322 + 323 + // Toggle lock keys on press 324 + Key::CapsLock if !released => self.modifiers.caps_lock = !self.modifiers.caps_lock, 325 + Key::NumLock if !released => self.modifiers.num_lock = !self.modifiers.num_lock, 326 + Key::ScrollLock if !released => self.modifiers.scroll_lock = !self.modifiers.scroll_lock, 327 + 328 + _ => {} 329 + } 330 + } 331 + } 332 + 333 + /// Translate a scancode (Set 1) to a Key 334 + fn scancode_to_key(code: u8) -> Key { 335 + match code { 336 + // Row 1 337 + 0x01 => Key::Escape, 338 + 0x3B => Key::F1, 339 + 0x3C => Key::F2, 340 + 0x3D => Key::F3, 341 + 0x3E => Key::F4, 342 + 0x3F => Key::F5, 343 + 0x40 => Key::F6, 344 + 0x41 => Key::F7, 345 + 0x42 => Key::F8, 346 + 0x43 => Key::F9, 347 + 0x44 => Key::F10, 348 + 0x57 => Key::F11, 349 + 0x58 => Key::F12, 350 + 351 + // Row 2 352 + 0x29 => Key::Grave, 353 + 0x02 => Key::Num1, 354 + 0x03 => Key::Num2, 355 + 0x04 => Key::Num3, 356 + 0x05 => Key::Num4, 357 + 0x06 => Key::Num5, 358 + 0x07 => Key::Num6, 359 + 0x08 => Key::Num7, 360 + 0x09 => Key::Num8, 361 + 0x0A => Key::Num9, 362 + 0x0B => Key::Num0, 363 + 0x0C => Key::Minus, 364 + 0x0D => Key::Equal, 365 + 0x0E => Key::Backspace, 366 + 367 + // Row 3 368 + 0x0F => Key::Tab, 369 + 0x10 => Key::Q, 370 + 0x11 => Key::W, 371 + 0x12 => Key::E, 372 + 0x13 => Key::R, 373 + 0x14 => Key::T, 374 + 0x15 => Key::Y, 375 + 0x16 => Key::U, 376 + 0x17 => Key::I, 377 + 0x18 => Key::O, 378 + 0x19 => Key::P, 379 + 0x1A => Key::LeftBracket, 380 + 0x1B => Key::RightBracket, 381 + 0x2B => Key::Backslash, 382 + 383 + // Row 4 384 + 0x3A => Key::CapsLock, 385 + 0x1E => Key::A, 386 + 0x1F => Key::S, 387 + 0x20 => Key::D, 388 + 0x21 => Key::F, 389 + 0x22 => Key::G, 390 + 0x23 => Key::H, 391 + 0x24 => Key::J, 392 + 0x25 => Key::K, 393 + 0x26 => Key::L, 394 + 0x27 => Key::Semicolon, 395 + 0x28 => Key::Quote, 396 + 0x1C => Key::Enter, 397 + 398 + // Row 5 399 + 0x2A => Key::LeftShift, 400 + 0x2C => Key::Z, 401 + 0x2D => Key::X, 402 + 0x2E => Key::C, 403 + 0x2F => Key::V, 404 + 0x30 => Key::B, 405 + 0x31 => Key::N, 406 + 0x32 => Key::M, 407 + 0x33 => Key::Comma, 408 + 0x34 => Key::Period, 409 + 0x35 => Key::Slash, 410 + 0x36 => Key::RightShift, 411 + 412 + // Row 6 413 + 0x1D => Key::LeftCtrl, 414 + 0x38 => Key::LeftAlt, 415 + 0x39 => Key::Space, 416 + 417 + // Numpad 418 + 0x45 => Key::NumLock, 419 + 0x46 => Key::ScrollLock, 420 + 0x47 => Key::Numpad7, 421 + 0x48 => Key::Numpad8, 422 + 0x49 => Key::Numpad9, 423 + 0x4A => Key::NumpadMinus, 424 + 0x4B => Key::Numpad4, 425 + 0x4C => Key::Numpad5, 426 + 0x4D => Key::Numpad6, 427 + 0x4E => Key::NumpadPlus, 428 + 0x4F => Key::Numpad1, 429 + 0x50 => Key::Numpad2, 430 + 0x51 => Key::Numpad3, 431 + 0x52 => Key::Numpad0, 432 + 0x53 => Key::NumpadPeriod, 433 + 0x37 => Key::NumpadMultiply, 434 + 435 + // Unknown 436 + _ => Key::Unknown(code), 437 + } 438 + } 439 + 440 + /// Read a scancode from the keyboard data port 441 + /// 442 + /// # Safety 443 + /// Reads from I/O port 0x60. Should only be called from keyboard interrupt handler. 444 + pub unsafe fn read_scancode() -> u8 { 445 + inb(KEYBOARD_DATA) 446 + } 447 + 448 + /// Check if keyboard data is available 449 + pub unsafe fn data_available() -> bool { 450 + (inb(KEYBOARD_STATUS) & STATUS_OUTPUT_FULL) != 0 451 + } 452 + 453 + /// Helper function to input a byte from an I/O port 454 + #[inline] 455 + unsafe fn inb(port: u16) -> u8 { 456 + let value: u8; 457 + asm!("in al, dx", out("al") value, in("dx") port, options(nomem, nostack, preserves_flags)); 458 + value 459 + } 460 + 461 + /// Initialize the keyboard 462 + pub fn init() { 463 + // The PS/2 keyboard is initialized by the BIOS 464 + // We just need to enable IRQ 1 and start receiving scancodes 465 + unsafe { 466 + super::PIC.lock().enable_irq(1); 467 + } 468 + } 469 + 470 + /// Handle a keyboard interrupt (called from IRQ 1 handler) 471 + pub fn on_interrupt() { 472 + unsafe { 473 + if data_available() { 474 + let scancode = read_scancode(); 475 + let mut keyboard = KEYBOARD.lock(); 476 + 477 + if let Some(event) = keyboard.process_scancode(scancode) { 478 + handle_key_event(event, &keyboard.modifiers()); 479 + } 480 + } 481 + } 482 + } 483 + 484 + /// Handle a key event (override this for custom behavior) 485 + fn handle_key_event(event: KeyEvent, modifiers: &Modifiers) { 486 + match event { 487 + KeyEvent::Pressed(key) => { 488 + if let Some(ch) = key.to_ascii(modifiers) { 489 + // Echo printable characters to screen 490 + if ch.is_ascii() && !ch.is_ascii_control() { 491 + crate::print!("{}", ch); 492 + } else if ch == '\n' { 493 + crate::println!(); 494 + } else if ch == '\x08' { 495 + // Backspace - would need VGA driver support 496 + crate::print!("\x08 \x08"); 497 + } 498 + } else { 499 + // Non-ASCII key pressed 500 + // Could log special keys for debugging 501 + } 502 + } 503 + KeyEvent::Released(_) => { 504 + // Usually we don't care about key releases for typing 505 + } 506 + } 507 + }
+5
aethelos-source/heartwood/src/attunement/mod.rs
··· 11 11 12 12 pub mod gdt; 13 13 pub mod idt; 14 + pub mod keyboard; 14 15 pub mod pic; 15 16 pub mod pit; 16 17 ··· 72 73 // Initialize system timer (PIT) 73 74 crate::println!(" ∴ Starting system heartbeat (PIT @ {} Hz)...", PIT.frequency()); 74 75 timer::init(); 76 + 77 + // Initialize keyboard 78 + crate::println!(" ∴ Listening for keystrokes (PS/2)..."); 79 + keyboard::init(); 75 80 76 81 crate::println!(" ◈ Hardware attunement complete"); 77 82 }
+248
aethelos-source/heartwood/src/loom_of_fate/context.rs
··· 1 + //! # Thread Context 2 + //! 3 + //! The essence of a thread - its complete state at a moment in time. 4 + //! When we preserve context, we capture the thread's entire being: 5 + //! every register, every thought, every intention. 6 + //! 7 + //! ## Philosophy 8 + //! Context switches are not interruptions, but transitions. 9 + //! We preserve the current moment with reverence, knowing we will 10 + //! restore it faithfully when the thread's turn returns. 11 + 12 + use core::arch::asm; 13 + 14 + /// The complete state of a thread 15 + /// 16 + /// This structure holds all CPU registers needed to resume execution 17 + /// exactly where the thread left off. The layout matches x86-64 calling 18 + /// conventions and interrupt frames. 19 + #[repr(C)] 20 + #[derive(Debug, Clone, Copy)] 21 + pub struct ThreadContext { 22 + // General purpose registers (callee-saved) 23 + pub r15: u64, 24 + pub r14: u64, 25 + pub r13: u64, 26 + pub r12: u64, 27 + pub rbp: u64, 28 + pub rbx: u64, 29 + 30 + // Additional general purpose registers 31 + pub r11: u64, 32 + pub r10: u64, 33 + pub r9: u64, 34 + pub r8: u64, 35 + pub rax: u64, 36 + pub rcx: u64, 37 + pub rdx: u64, 38 + pub rsi: u64, 39 + pub rdi: u64, 40 + 41 + // Special registers 42 + pub rip: u64, // Instruction pointer - where to resume 43 + pub cs: u64, // Code segment 44 + pub rflags: u64, // CPU flags 45 + pub rsp: u64, // Stack pointer 46 + pub ss: u64, // Stack segment 47 + } 48 + 49 + impl ThreadContext { 50 + /// Create a new context for a thread starting at the given entry point 51 + /// 52 + /// # Arguments 53 + /// * `entry_point` - The function pointer where the thread will begin 54 + /// * `stack_top` - The top of the thread's stack (high address) 55 + /// 56 + /// # Returns 57 + /// A context ready to be restored, which will begin executing at the entry point 58 + pub fn new(entry_point: u64, stack_top: u64) -> Self { 59 + ThreadContext { 60 + // General purpose registers start at zero 61 + r15: 0, 62 + r14: 0, 63 + r13: 0, 64 + r12: 0, 65 + rbp: 0, 66 + rbx: 0, 67 + r11: 0, 68 + r10: 0, 69 + r9: 0, 70 + r8: 0, 71 + rax: 0, 72 + rcx: 0, 73 + rdx: 0, 74 + rsi: 0, 75 + rdi: 0, 76 + 77 + // Special registers 78 + rip: entry_point, 79 + cs: 0x08, // Kernel code segment (from GDT) 80 + rflags: 0x202, // Interrupts enabled (IF flag) 81 + rsp: stack_top, 82 + ss: 0x10, // Kernel data segment (from GDT) 83 + } 84 + } 85 + 86 + /// Create an empty context (all zeros) 87 + pub const fn empty() -> Self { 88 + ThreadContext { 89 + r15: 0, r14: 0, r13: 0, r12: 0, 90 + rbp: 0, rbx: 0, r11: 0, r10: 0, 91 + r9: 0, r8: 0, rax: 0, rcx: 0, 92 + rdx: 0, rsi: 0, rdi: 0, 93 + rip: 0, cs: 0, rflags: 0, 94 + rsp: 0, ss: 0, 95 + } 96 + } 97 + } 98 + 99 + /// Switch from the current thread context to a new thread context 100 + /// 101 + /// This is the heart of cooperative multitasking. It saves the current 102 + /// thread's state and restores the new thread's state in one atomic operation. 103 + /// 104 + /// # Arguments 105 + /// * `old_context` - Where to save the current thread's state 106 + /// * `new_context` - The context to restore and resume 107 + /// 108 + /// # Safety 109 + /// This function performs raw register manipulation and must only be called 110 + /// from the scheduler with interrupts disabled. 111 + #[unsafe(naked)] 112 + pub unsafe extern "C" fn switch_context(_old_context: *mut ThreadContext, _new_context: *const ThreadContext) { 113 + core::arch::naked_asm!( 114 + // Save current context 115 + // rdi = old_context pointer (first argument) 116 + // rsi = new_context pointer (second argument) 117 + 118 + // Save general purpose registers 119 + "mov [rdi + 0x00], r15", 120 + "mov [rdi + 0x08], r14", 121 + "mov [rdi + 0x10], r13", 122 + "mov [rdi + 0x18], r12", 123 + "mov [rdi + 0x20], rbp", 124 + "mov [rdi + 0x28], rbx", 125 + "mov [rdi + 0x30], r11", 126 + "mov [rdi + 0x38], r10", 127 + "mov [rdi + 0x40], r9", 128 + "mov [rdi + 0x48], r8", 129 + "mov [rdi + 0x50], rax", 130 + "mov [rdi + 0x58], rcx", 131 + "mov [rdi + 0x60], rdx", 132 + "mov [rdi + 0x68], rsi", 133 + "mov [rdi + 0x70], rdi", 134 + 135 + // Save RIP (return address is on stack) 136 + "mov rax, [rsp]", 137 + "mov [rdi + 0x78], rax", 138 + 139 + // Save CS 140 + "mov ax, cs", 141 + "mov [rdi + 0x80], rax", 142 + 143 + // Save RFLAGS 144 + "pushfq", 145 + "pop rax", 146 + "mov [rdi + 0x88], rax", 147 + 148 + // Save RSP (before return address was pushed) 149 + "lea rax, [rsp + 8]", 150 + "mov [rdi + 0x90], rax", 151 + 152 + // Save SS 153 + "mov ax, ss", 154 + "mov [rdi + 0x98], rax", 155 + 156 + // Now restore new context 157 + // rsi = new_context pointer 158 + 159 + // Restore general purpose registers 160 + "mov r15, [rsi + 0x00]", 161 + "mov r14, [rsi + 0x08]", 162 + "mov r13, [rsi + 0x10]", 163 + "mov r12, [rsi + 0x18]", 164 + "mov rbp, [rsi + 0x20]", 165 + "mov rbx, [rsi + 0x28]", 166 + "mov r11, [rsi + 0x30]", 167 + "mov r10, [rsi + 0x38]", 168 + "mov r9, [rsi + 0x40]", 169 + "mov r8, [rsi + 0x48]", 170 + "mov rax, [rsi + 0x50]", 171 + "mov rcx, [rsi + 0x58]", 172 + "mov rdx, [rsi + 0x60]", 173 + 174 + // Restore RSP first (we'll need it) 175 + "mov rsp, [rsi + 0x90]", 176 + 177 + // Push new context's RIP onto the new stack (for ret) 178 + "push qword ptr [rsi + 0x78]", 179 + 180 + // Restore RFLAGS 181 + "push qword ptr [rsi + 0x88]", 182 + "popfq", 183 + 184 + // Restore remaining registers 185 + "mov rdi, [rsi + 0x70]", 186 + "mov rsi, [rsi + 0x68]", 187 + 188 + // Return to new thread's RIP 189 + "ret", 190 + ); 191 + } 192 + 193 + /// Initialize a new thread's stack with the proper frame for first execution 194 + /// 195 + /// This sets up the stack so that when we "restore" the context for the first time, 196 + /// it will cleanly jump to the thread's entry point. 197 + /// 198 + /// # Arguments 199 + /// * `stack_top` - The top of the stack (high address) 200 + /// * `entry_point` - The function where the thread starts 201 + /// 202 + /// # Returns 203 + /// The adjusted stack pointer after initialization 204 + #[allow(dead_code)] 205 + pub unsafe fn init_thread_stack(stack_top: u64, _entry_point: fn() -> !) -> u64 { 206 + // The stack grows downward, so we work backward from the top 207 + // When context is restored, ret will pop RIP from stack 208 + // So we don't need to push anything here - the context struct handles it 209 + 210 + stack_top 211 + } 212 + 213 + /// Helper wrapper for thread entry points 214 + /// 215 + /// This function is called when a new thread is first scheduled. 216 + /// It handles any setup needed before calling the actual entry point. 217 + pub extern "C" fn thread_entry_wrapper(entry_point: fn() -> !) -> ! { 218 + // Enable interrupts for this new thread 219 + unsafe { 220 + asm!("sti", options(nomem, nostack)); 221 + } 222 + 223 + // Call the actual entry point 224 + entry_point() 225 + } 226 + 227 + #[cfg(test)] 228 + mod tests { 229 + use super::*; 230 + 231 + #[test] 232 + fn test_context_creation() { 233 + let ctx = ThreadContext::new(0x1000, 0x5000); 234 + assert_eq!(ctx.rip, 0x1000); 235 + assert_eq!(ctx.rsp, 0x5000); 236 + assert_eq!(ctx.cs, 0x08); 237 + assert_eq!(ctx.ss, 0x10); 238 + assert_ne!(ctx.rflags & 0x200, 0); // IF flag set 239 + } 240 + 241 + #[test] 242 + fn test_empty_context() { 243 + let ctx = ThreadContext::empty(); 244 + assert_eq!(ctx.rip, 0); 245 + assert_eq!(ctx.rsp, 0); 246 + assert_eq!(ctx.rax, 0); 247 + } 248 + }
+3
aethelos-source/heartwood/src/loom_of_fate/mod.rs
··· 15 15 //! - Resource negotiation based on system-wide harmony 16 16 //! - Parasite detection and throttling (not killing) 17 17 18 + pub mod context; 18 19 pub mod scheduler; 20 + pub mod stack; 19 21 pub mod thread; 20 22 pub mod harmony; 21 23 ··· 60 62 OutOfThreads, 61 63 ThreadNotFound, 62 64 InvalidPriority, 65 + StackAllocationFailed, 63 66 }
+119 -45
aethelos-source/heartwood/src/loom_of_fate/scheduler.rs
··· 1 1 //! The Scheduler - The core of the Loom of Fate 2 2 3 + use super::context::{switch_context, ThreadContext}; 3 4 use super::harmony::{HarmonyAnalyzer, HarmonyMetrics}; 5 + use super::stack::Stack; 4 6 use super::thread::{Thread, ThreadId, ThreadPriority, ThreadState}; 5 7 use super::LoomError; 6 8 use alloc::collections::VecDeque; ··· 11 13 /// The harmony-based cooperative scheduler 12 14 pub struct Scheduler { 13 15 threads: Vec<Thread>, 16 + stacks: Vec<Stack>, // Stack storage (owned by scheduler) 14 17 ready_queue: VecDeque<ThreadId>, 15 18 current_thread: Option<ThreadId>, 16 19 next_thread_id: u64, 17 20 harmony_analyzer: HarmonyAnalyzer, 18 21 /// Latest harmony metrics from the analyzer 19 22 latest_metrics: HarmonyMetrics, 23 + /// Total number of context switches performed 24 + context_switches: u64, 20 25 } 21 26 22 27 impl Default for Scheduler { ··· 29 34 pub fn new() -> Self { 30 35 Self { 31 36 threads: Vec::new(), 37 + stacks: Vec::new(), 32 38 ready_queue: VecDeque::new(), 33 39 current_thread: None, 34 40 next_thread_id: 1, 35 41 harmony_analyzer: HarmonyAnalyzer::new(), 36 42 latest_metrics: HarmonyMetrics::default(), 43 + context_switches: 0, 37 44 } 38 45 } 39 46 ··· 46 53 let thread_id = ThreadId(self.next_thread_id); 47 54 self.next_thread_id += 1; 48 55 49 - let thread = Thread::new(thread_id, entry_point, priority); 56 + // Allocate a stack for this thread 57 + let stack = Stack::new().ok_or(LoomError::StackAllocationFailed)?; 58 + let stack_bottom = stack.bottom(); 59 + let stack_top = stack.top(); 60 + 61 + // Create the thread with its stack 62 + let thread = Thread::new(thread_id, entry_point, priority, stack_bottom, stack_top); 63 + 50 64 self.threads.push(thread); 65 + self.stacks.push(stack); // Keep stack alive 51 66 self.ready_queue.push_back(thread_id); 52 67 53 68 Ok(thread_id) 54 69 } 55 70 56 - /// Yield the current thread 71 + /// Yield the current thread and switch to the next one 72 + /// 73 + /// This is the heart of cooperative multitasking. The current thread 74 + /// voluntarily gives up the CPU, and we select the next thread based 75 + /// on harmony and priority. 76 + /// 77 + /// # Safety 78 + /// This function performs context switching which involves raw register 79 + /// manipulation. It should only be called from safe contexts where 80 + /// the scheduler state is valid. 57 81 pub fn yield_current(&mut self) { 58 - if let Some(current_id) = self.current_thread { 59 - if let Some(thread) = self.find_thread_mut(current_id) { 60 - thread.record_yield(); 61 - thread.set_state(ThreadState::Resting); 62 - } 63 - 64 - self.ready_queue.push_back(current_id); 65 - self.current_thread = None; 66 - } 67 - 68 - self.schedule_next(); 69 - } 70 - 71 - /// Schedule the next thread to run 72 - fn schedule_next(&mut self) { 73 82 // Analyze harmony before scheduling 74 83 let metrics = self.harmony_analyzer.analyze(&mut self.threads); 75 84 self.latest_metrics = metrics; ··· 77 86 // Adaptive scheduling based on system harmony 78 87 if metrics.system_harmony < 0.5 { 79 88 // System is in disharmony - prioritize cooperative threads 80 - // and deprioritize parasites more aggressively 81 89 self.rebalance_for_harmony(); 82 90 } 83 91 84 - // Find the next thread to run based on harmony 92 + // Find the next thread to run 85 93 let next_thread_id = self.select_next_thread(); 86 94 87 - if let Some(thread_id) = next_thread_id { 88 - // Check if thread is parasitic and get soothe factor before mutable borrow 89 - let (is_parasitic, soothe_factor) = self.find_thread(thread_id) 90 - .map(|t| { 91 - let parasitic = self.harmony_analyzer.should_soothe(t); 92 - let factor = if parasitic { 93 - self.harmony_analyzer.soothe_factor(t) 94 - } else { 95 - 1.0 96 - }; 97 - (parasitic, factor) 98 - }) 99 - .unwrap_or((false, 1.0)); 95 + if next_thread_id.is_none() { 96 + // No threads ready - this shouldn't happen in a well-designed system 97 + // but if it does, we just return (stay on current thread if any) 98 + return; 99 + } 100 + 101 + let next_id = next_thread_id.unwrap(); 100 102 101 - if let Some(thread) = self.find_thread_mut(thread_id) { 102 - thread.set_state(ThreadState::Weaving); 103 - thread.record_time_slice(); 103 + // If we're switching to a different thread, perform context switch 104 + if self.current_thread.is_some() && self.current_thread != Some(next_id) { 105 + let current_id = self.current_thread.unwrap(); 104 106 105 - // If this thread is parasitic, soothe it based on harmony score 106 - if is_parasitic { 107 - // In a real implementation, we would: 108 - // 1. Reduce time slice based on soothe_factor (lower = more throttling) 109 - // 2. Insert deliberate pauses/delays 110 - // 3. Lower its effective priority 111 - // For now, this documents the intent for future implementation 112 - let _ = soothe_factor; // Acknowledge we have the factor 113 - } 107 + // Update current thread state 108 + if let Some(current_thread) = self.find_thread_mut(current_id) { 109 + current_thread.record_yield(); 110 + current_thread.set_state(ThreadState::Resting); 114 111 } 115 112 116 - self.current_thread = Some(thread_id); 113 + // Add current thread back to ready queue 114 + self.ready_queue.push_back(current_id); 115 + 116 + // Update next thread state 117 + if let Some(next_thread) = self.find_thread_mut(next_id) { 118 + next_thread.set_state(ThreadState::Weaving); 119 + next_thread.record_time_slice(); 120 + next_thread.last_run_time = crate::attunement::timer::ticks(); 121 + } 122 + 123 + // Perform the actual context switch 124 + self.context_switches += 1; 125 + self.perform_context_switch(current_id, next_id); 126 + 127 + // After we return from context switch, we're running as the "next" thread 128 + // (which may actually be this thread again after future switches) 129 + } else if self.current_thread.is_none() { 130 + // First thread to run - just start it (no context to save) 131 + if let Some(next_thread) = self.find_thread_mut(next_id) { 132 + next_thread.set_state(ThreadState::Weaving); 133 + next_thread.record_time_slice(); 134 + next_thread.last_run_time = crate::attunement::timer::ticks(); 135 + } 136 + 137 + self.current_thread = Some(next_id); 138 + // Jump to the first thread (this will not return) 139 + self.jump_to_thread(next_id); 117 140 } 141 + 142 + // Update current thread ID 143 + self.current_thread = Some(next_id); 144 + } 145 + 146 + /// Perform a context switch between two threads 147 + /// 148 + /// # Safety 149 + /// Assumes both thread IDs are valid and the threads exist 150 + fn perform_context_switch(&mut self, from_id: ThreadId, to_id: ThreadId) { 151 + // Get raw pointers to the contexts before borrowing 152 + let from_idx = self.threads.iter().position(|t| t.id() == from_id).unwrap(); 153 + let to_idx = self.threads.iter().position(|t| t.id() == to_id).unwrap(); 154 + 155 + let from_ctx_ptr = &mut self.threads[from_idx].context as *mut ThreadContext; 156 + let to_ctx_ptr = &self.threads[to_idx].context as *const ThreadContext; 157 + 158 + // Perform the context switch 159 + // This will save the current state to from_ctx and restore to_ctx 160 + unsafe { 161 + switch_context(from_ctx_ptr, to_ctx_ptr); 162 + } 163 + 164 + // When we return here, we're running as the "to" thread 165 + // (or possibly some other thread that later switched back to us) 166 + } 167 + 168 + /// Jump to a thread for the first time (no context to save) 169 + /// 170 + /// # Safety 171 + /// This function never returns - it jumps to the thread's entry point 172 + fn jump_to_thread(&mut self, to_id: ThreadId) -> ! { 173 + // Find the thread's context 174 + let to_idx = self.threads.iter().position(|t| t.id() == to_id).unwrap(); 175 + let to_ctx_ptr = &self.threads[to_idx].context as *const ThreadContext; 176 + 177 + // Create a dummy context for the "from" side (we won't use it) 178 + let mut dummy_ctx = ThreadContext::empty(); 179 + let dummy_ctx_ptr = &mut dummy_ctx as *mut ThreadContext; 180 + 181 + // Jump to the new thread 182 + // Note: switch_context will try to save our state to dummy_ctx, 183 + // but since we're never coming back, that's fine 184 + unsafe { 185 + switch_context(dummy_ctx_ptr, to_ctx_ptr); 186 + } 187 + 188 + // Never reached 189 + unreachable!("jump_to_thread should never return"); 118 190 } 119 191 120 192 /// Rebalance the ready queue when system harmony is low ··· 210 282 average_harmony: self.latest_metrics.average_harmony, 211 283 system_harmony: self.latest_metrics.system_harmony, 212 284 parasite_count: self.latest_metrics.parasite_count, 285 + context_switches: self.context_switches, 213 286 } 214 287 } 215 288 ··· 229 302 pub average_harmony: f32, 230 303 pub system_harmony: f32, 231 304 pub parasite_count: usize, 305 + pub context_switches: u64, 232 306 }
+148
aethelos-source/heartwood/src/loom_of_fate/stack.rs
··· 1 + //! # Stack Allocation 2 + //! 3 + //! Each thread needs its own stack - a space for its local thoughts. 4 + //! We allocate stacks from the heap, giving each thread room to breathe. 5 + //! 6 + //! ## Philosophy 7 + //! A thread's stack is its private sanctuary. We provide generous space 8 + //! (default 64KB) so threads never feel cramped or anxious. 9 + 10 + use alloc::alloc::{alloc, dealloc, Layout}; 11 + use core::ptr::NonNull; 12 + 13 + /// Default stack size: 64 KB 14 + /// This is generous enough for most threads while being conservative with memory 15 + pub const DEFAULT_STACK_SIZE: usize = 64 * 1024; 16 + 17 + /// Minimum stack size: 4 KB (one page) 18 + pub const MIN_STACK_SIZE: usize = 4 * 1024; 19 + 20 + /// Maximum stack size: 1 MB 21 + pub const MAX_STACK_SIZE: usize = 1024 * 1024; 22 + 23 + /// A thread's stack allocation 24 + pub struct Stack { 25 + bottom: NonNull<u8>, 26 + size: usize, 27 + } 28 + 29 + // Safety: Stack pointers can be safely sent between threads 30 + // as they represent heap-allocated memory 31 + unsafe impl Send for Stack {} 32 + unsafe impl Sync for Stack {} 33 + 34 + impl Stack { 35 + /// Allocate a new stack with the default size 36 + pub fn new() -> Option<Self> { 37 + Self::with_size(DEFAULT_STACK_SIZE) 38 + } 39 + 40 + /// Allocate a new stack with a specific size 41 + /// 42 + /// # Arguments 43 + /// * `size` - Size in bytes (will be clamped to MIN/MAX and aligned to 16 bytes) 44 + /// 45 + /// # Returns 46 + /// Some(Stack) if allocation succeeds, None otherwise 47 + pub fn with_size(size: usize) -> Option<Self> { 48 + // Clamp size to valid range and align to 16 bytes 49 + let size = size.clamp(MIN_STACK_SIZE, MAX_STACK_SIZE); 50 + let size = (size + 15) & !15; // Align to 16 bytes 51 + 52 + // Create layout for allocation 53 + let layout = Layout::from_size_align(size, 16).ok()?; 54 + 55 + // Allocate the stack 56 + let ptr = unsafe { alloc(layout) }; 57 + 58 + NonNull::new(ptr).map(|bottom| Stack { bottom, size }) 59 + } 60 + 61 + /// Get the bottom (low address) of the stack 62 + pub fn bottom(&self) -> u64 { 63 + self.bottom.as_ptr() as u64 64 + } 65 + 66 + /// Get the top (high address) of the stack 67 + /// 68 + /// The stack grows downward, so this is bottom + size 69 + pub fn top(&self) -> u64 { 70 + self.bottom() + self.size as u64 71 + } 72 + 73 + /// Get the size of the stack in bytes 74 + pub fn size(&self) -> usize { 75 + self.size 76 + } 77 + 78 + /// Check if an address is within this stack 79 + pub fn contains(&self, addr: u64) -> bool { 80 + addr >= self.bottom() && addr < self.top() 81 + } 82 + } 83 + 84 + impl Drop for Stack { 85 + fn drop(&mut self) { 86 + // Deallocate the stack when it's no longer needed 87 + let layout = Layout::from_size_align(self.size, 16) 88 + .expect("Invalid layout during stack deallocation"); 89 + 90 + unsafe { 91 + dealloc(self.bottom.as_ptr(), layout); 92 + } 93 + } 94 + } 95 + 96 + /// Guard page support (for future implementation) 97 + /// 98 + /// A guard page is a page marked as non-accessible that sits at the bottom 99 + /// of the stack. If a stack overflow occurs, accessing the guard page will 100 + /// trigger a page fault, allowing us to detect the overflow gracefully. 101 + #[allow(dead_code)] 102 + pub struct GuardedStack { 103 + stack: Stack, 104 + guard_page_addr: u64, 105 + } 106 + 107 + #[cfg(test)] 108 + mod tests { 109 + use super::*; 110 + 111 + #[test] 112 + fn test_stack_allocation() { 113 + let stack = Stack::new().expect("Failed to allocate stack"); 114 + assert_eq!(stack.size(), DEFAULT_STACK_SIZE); 115 + assert!(stack.top() > stack.bottom()); 116 + assert_eq!(stack.top() - stack.bottom(), DEFAULT_STACK_SIZE as u64); 117 + } 118 + 119 + #[test] 120 + fn test_custom_size() { 121 + let stack = Stack::with_size(8192).expect("Failed to allocate"); 122 + assert_eq!(stack.size(), 8192); 123 + } 124 + 125 + #[test] 126 + fn test_size_clamping() { 127 + // Too small - should clamp to MIN_STACK_SIZE 128 + let stack = Stack::with_size(100).expect("Failed to allocate"); 129 + assert_eq!(stack.size(), MIN_STACK_SIZE); 130 + 131 + // Too large - should clamp to MAX_STACK_SIZE 132 + let stack = Stack::with_size(10 * 1024 * 1024).expect("Failed to allocate"); 133 + assert_eq!(stack.size(), MAX_STACK_SIZE); 134 + } 135 + 136 + #[test] 137 + fn test_contains() { 138 + let stack = Stack::new().expect("Failed to allocate"); 139 + let bottom = stack.bottom(); 140 + let top = stack.top(); 141 + 142 + assert!(stack.contains(bottom)); 143 + assert!(stack.contains(bottom + 100)); 144 + assert!(stack.contains(top - 1)); 145 + assert!(!stack.contains(top)); 146 + assert!(!stack.contains(bottom - 1)); 147 + } 148 + }
+40 -1
aethelos-source/heartwood/src/loom_of_fate/thread.rs
··· 1 1 //! Thread definitions - The Threads of Fate 2 2 3 + use super::context::ThreadContext; 4 + 3 5 /// A unique identifier for a thread 4 6 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 5 7 pub struct ThreadId(pub u64); ··· 37 39 pub(crate) priority: ThreadPriority, 38 40 pub(crate) entry_point: fn() -> !, 39 41 42 + // CPU state (for context switching) 43 + pub(crate) context: ThreadContext, 44 + pub(crate) stack_bottom: u64, 45 + pub(crate) stack_top: u64, 46 + 40 47 // Harmony tracking 41 48 pub(crate) resource_usage: ResourceUsage, 42 49 pub(crate) harmony_score: f32, ··· 44 51 // Execution context 45 52 pub(crate) time_slices_used: u64, 46 53 pub(crate) yields: u64, 54 + pub(crate) last_run_time: u64, 47 55 } 48 56 49 57 impl Thread { 50 - pub fn new(id: ThreadId, entry_point: fn() -> !, priority: ThreadPriority) -> Self { 58 + /// Create a new thread with allocated stack 59 + /// 60 + /// # Arguments 61 + /// * `id` - Unique thread identifier 62 + /// * `entry_point` - Function where thread begins execution 63 + /// * `priority` - Thread priority level 64 + /// * `stack_bottom` - Low address of thread's stack 65 + /// * `stack_top` - High address of thread's stack 66 + pub fn new( 67 + id: ThreadId, 68 + entry_point: fn() -> !, 69 + priority: ThreadPriority, 70 + stack_bottom: u64, 71 + stack_top: u64, 72 + ) -> Self { 73 + // Create initial context for this thread 74 + let context = ThreadContext::new(entry_point as u64, stack_top); 75 + 51 76 Self { 52 77 id, 53 78 state: ThreadState::Resting, 54 79 priority, 55 80 entry_point, 81 + context, 82 + stack_bottom, 83 + stack_top, 56 84 resource_usage: ResourceUsage::default(), 57 85 harmony_score: 1.0, // Start in perfect harmony 58 86 time_slices_used: 0, 59 87 yields: 0, 88 + last_run_time: 0, 60 89 } 90 + } 91 + 92 + /// Get a mutable reference to the thread's context 93 + pub fn context_mut(&mut self) -> &mut ThreadContext { 94 + &mut self.context 95 + } 96 + 97 + /// Get a reference to the thread's context 98 + pub fn context(&self) -> &ThreadContext { 99 + &self.context 61 100 } 62 101 63 102 pub fn id(&self) -> ThreadId {