diff --git a/boards/qemu_i486_q35/src/main.rs b/boards/qemu_i486_q35/src/main.rs index 98bb0632e8..30945cc51d 100644 --- a/boards/qemu_i486_q35/src/main.rs +++ b/boards/qemu_i486_q35/src/main.rs @@ -9,13 +9,12 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -use core::ptr; - use capsules_core::alarm; use capsules_core::console::{self, Console}; use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use components::console::ConsoleComponent; use components::debug_writer::DebugWriterComponent; +use core::ptr; use kernel::capabilities; use kernel::component::Component; use kernel::debug; @@ -28,11 +27,11 @@ use kernel::process::ProcessArray; use kernel::scheduler::cooperative::CooperativeSched; use kernel::syscall::SyscallDriver; use kernel::{create_capability, static_init}; - use x86::registers::bits32::paging::{PDEntry, PTEntry, PD, PT}; use x86::registers::irq; - +use x86_q35::dv_kb::Keyboard; use x86_q35::pit::{Pit, RELOAD_1KHZ}; +use x86_q35::ps2::Ps2Controller; use x86_q35::{Pc, PcComponent}; mod multiboot; @@ -76,6 +75,15 @@ pub static mut PAGE_DIR: PD = [PDEntry(0); 1024]; #[link_section = ".pte"] pub static mut PAGE_TABLE: PT = [PTEntry(0); 1024]; +#[no_mangle] +pub static mut PS2: core::mem::MaybeUninit> = + core::mem::MaybeUninit::uninit(); + +/// Keyboard object used by chip.rs +#[no_mangle] +pub static mut KEYBOARD: core::mem::MaybeUninit>> = + core::mem::MaybeUninit::uninit(); + pub struct QemuI386Q35Platform { pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, @@ -153,13 +161,29 @@ impl KernelResources for QemuI386Q35Platform { #[no_mangle] unsafe extern "cdecl" fn main() { - // ---------- BASIC INITIALIZATION ----------- + // -------- PS/2 controller & keyboard -------- + let ps2_ctrl: &'static Ps2Controller = { + let ctrl_mut: &'static mut Ps2Controller = + static_init!(Ps2Controller<'static>, Ps2Controller::new()); + x86_q35::ps2_ctl::init_controller::>() + .expect("PS/2 controller init failed"); + ctrl_mut + }; + + let kb_val: Keyboard<'static, Ps2Controller> = { + let kb = Keyboard::new(ps2_ctrl); + kb.init().expect("Keyboard init failed"); + kb + }; + let slot: *mut Keyboard<'static, Ps2Controller> = core::ptr::addr_of_mut!(KEYBOARD).cast(); + ptr::write(slot, kb_val); - // Basic setup of the i486 platform + // -------- Chip initialisation -------- let chip = PcComponent::new( &mut *ptr::addr_of_mut!(PAGE_DIR), &mut *ptr::addr_of_mut!(PAGE_TABLE), ) + .with_ps2(ps2_ctrl) .finalize(x86_q35::x86_q35_component_static!()); // Acquire required capabilities diff --git a/capsules/core/src/process_console.rs b/capsules/core/src/process_console.rs index 74064c331d..2d43f115b4 100644 --- a/capsules/core/src/process_console.rs +++ b/capsules/core/src/process_console.rs @@ -53,7 +53,7 @@ const ESC: u8 = b'\x1B'; const EOL: u8 = b'\x00'; /// Backspace ANSI character -const BS: u8 = b'\x08'; +const BACKSPACE: u8 = b'\x08'; /// Delete ANSI character const DEL: u8 = b'\x7F'; @@ -103,6 +103,7 @@ enum EscKey { Home, End, Delete, + Backspace, } /// Escape state machine to check if @@ -144,7 +145,7 @@ enum EscState { impl EscState { fn next_state(self, data: u8) -> Self { use self::{ - EscKey::{Delete, Down, End, Home, Left, Right, Up}, + EscKey::{Backspace, Delete, Down, End, Home, Left, Right, Up}, EscState::{ Bracket, Bracket3, Bypass, Complete, Started, Unrecognized, UnrecognizedDone, }, @@ -153,7 +154,7 @@ impl EscState { (Bypass, ESC) | (UnrecognizedDone, ESC) | (Complete(_), ESC) => Started, // This is a short-circuit. // ASCII DEL and ANSI Escape Sequence "Delete" should be treated the same way. - (Bypass, DEL) | (UnrecognizedDone, DEL) | (Complete(_), DEL) => Complete(Delete), + (Bypass, DEL) | (UnrecognizedDone, DEL) | (Complete(_), DEL) => Complete(Backspace), (Bypass, _) | (UnrecognizedDone, _) | (Complete(_), _) => Bypass, (Started, b'[') => Bracket, (Bracket, b'A') => Complete(Up), @@ -1227,7 +1228,8 @@ impl< // Clear the displayed command for _ in 0..index { - let _ = self.write_bytes(&[BS, SPACE, BS]); + let _ = self + .write_bytes(&[BACKSPACE, SPACE, BACKSPACE]); } // Display the new command @@ -1245,7 +1247,7 @@ impl< }); } EscKey::Left if cursor > 0 => { - let _ = self.write_byte(BS); + let _ = self.write_byte(BACKSPACE); self.cursor.set(cursor - 1); } EscKey::Right if cursor < index => { @@ -1254,7 +1256,7 @@ impl< } EscKey::Home if cursor > 0 => { for _ in 0..cursor { - let _ = self.write_byte(BS); + let _ = self.write_byte(BACKSPACE); } self.cursor.set(0); @@ -1266,6 +1268,62 @@ impl< self.cursor.set(index); } + EscKey::Backspace if cursor > 0 && cursor <= index => { + // Move the bytes one position to left + for i in (cursor - 1)..index { + command[i] = command[i + 1]; + let _ = self.write_byte(command[i]); + } + + // Now that we copied all bytes to the left, we are left over with + // a duplicate "ghost" character of the last byte, + // In case we deleted the first character, this doesn't do anything as + // the duplicate is not there. + // |abcdef -> abcdef (won't enter this match case) + // a|bcdef -> bcdef + // abc|def -> abdeff -> abdef + + let _ = self.write_bytes(&[BACKSPACE, SPACE, BACKSPACE]); + + // The following for statements are mandatory for correctly displaying + // the command and cursor + // + // Move the cursor to the correct position (without this, + // it will get sent all the way right) + for _ in (cursor - 1)..(index - 1) { + let _ = self.write_byte(BACKSPACE); + } + // Rewrite the command, this will move the cursor right (skipping this + // step will cause the command to not get updated correctly, for example + // abc|def -> ab|cde. This is just a visual mismatch) + for i in (cursor - 1)..(index - 1) { + let _ = self.write_byte(command[i]); + } + // Move the cursor left again (previously, the cursor was sent to the + // right by the previous for statement, so we need to move it left) + for _ in (cursor - 1)..(index - 1) { + let _ = self.write_byte(BACKSPACE); + } + + self.cursor.set(cursor - 1); + self.command_index.set(index - 1); + + // Remove the byte from the command in order + // not to permit accumulation of the text + if COMMAND_HISTORY_LEN > 1 { + self.command_history.map(|ht| { + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].delete_byte(cursor); + } + }); + } + } EscKey::Delete if cursor < index => { // Move the bytes one position to left for i in cursor..(index - 1) { @@ -1276,16 +1334,16 @@ impl< command[index - 1] = command[index]; // Now that we copied all bytes to the left, we are left over with - // a dublicate "ghost" character of the last byte, + // a duplicate "ghost" character of the last byte, // In case we deleted the first character, this doesn't do anything as - // the dublicate is not there. + // the duplicate is not there. // |abcdef -> bcdef // abc|def -> abceff -> abcef - let _ = self.write_bytes(&[SPACE, BS]); + let _ = self.write_bytes(&[SPACE, BACKSPACE]); // Move the cursor to last position for _ in cursor..(index - 1) { - let _ = self.write_byte(BS); + let _ = self.write_byte(BACKSPACE); } self.command_index.set(index - 1); @@ -1329,12 +1387,12 @@ impl< }); } } - } else if read_buf[0] == BS { + } else if read_buf[0] == BACKSPACE { if cursor > 0 { // Backspace, echo and remove the byte // preceding the cursor // Note echo is '\b \b' to erase - let _ = self.write_bytes(&[BS, SPACE, BS]); + let _ = self.write_bytes(&[BACKSPACE, SPACE, BACKSPACE]); // Move the bytes one position to left for i in (cursor - 1)..(index - 1) { @@ -1345,16 +1403,16 @@ impl< command[index - 1] = command[index]; // Now that we copied all bytes to the left, we are left over with - // a dublicate "ghost" character of the last byte, + // a duplicate "ghost" character of the last byte, // In case we deleted the last character, this doesn't do anything as - // the dublicate is not there. + // the duplicate is not there. // abcdef| -> abcdef // abcd|ef -> abceff -> abcef - let _ = self.write_bytes(&[SPACE, BS]); + let _ = self.write_bytes(&[SPACE, BACKSPACE]); // Move the cursor to last position for _ in cursor..index { - let _ = self.write_byte(BS); + let _ = self.write_byte(BACKSPACE); } self.command_index.set(index - 1); @@ -1399,7 +1457,7 @@ impl< // Move the cursor to the last position for _ in cursor..index { - let _ = self.write_byte(BS); + let _ = self.write_byte(BACKSPACE); } command[cursor] = read_buf[0]; diff --git a/chips/x86_q35/src/chip.rs b/chips/x86_q35/src/chip.rs index 00571764be..cbd95adf8c 100644 --- a/chips/x86_q35/src/chip.rs +++ b/chips/x86_q35/src/chip.rs @@ -4,17 +4,21 @@ use core::fmt::Write; use core::mem::MaybeUninit; - use kernel::component::Component; +use kernel::hil::ps2_traits::PS2Traits; use kernel::platform::chip::Chip; - use x86::mpu::PagingMPU; use x86::registers::bits32::paging::{PD, PT}; use x86::support; use x86::{Boundary, InterruptPoller}; +use crate::dv_kb::Keyboard; use crate::pit::{Pit, RELOAD_1KHZ}; use crate::serial::{SerialPort, SerialPortComponent, COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE}; +extern "Rust" { + /// Global keyboard object instantiated in the board setup + static KEYBOARD: Keyboard<'static, crate::ps2::Ps2Controller<'static>>; +} /// Interrupt constants for legacy PC peripherals mod interrupt { @@ -28,6 +32,9 @@ mod interrupt { /// Interrupt number shared by COM1 and COM3 serial devices pub(super) const COM1_COM3: u32 = (PIC1_OFFSET as u32) + 4; + + /// Interrupt number for PS/2 keyboard (IRQ1) + pub(super) const KEYBOARD: u32 = (PIC1_OFFSET as u32) + 1; } /// Representation of a generic PC platform. @@ -53,6 +60,9 @@ pub struct Pc<'a, const PR: u16 = RELOAD_1KHZ> { /// Legacy PIT timer pub pit: Pit<'a, PR>, + /// PS/2 controller (keyboard/mouse) + pub ps2: &'a crate::ps2::Ps2Controller<'a>, + /// System call context syscall: Boundary, paging: PagingMPU<'a>, @@ -82,6 +92,13 @@ impl<'a, const PR: u16> Chip for Pc<'a, PR> { self.com1.handle_interrupt(); self.com3.handle_interrupt(); } + interrupt::KEYBOARD => { + let _ = self.ps2.handle_interrupt(); + // decode + queue KeyEvent + unsafe { + KEYBOARD.poll(); + } + } _ => unimplemented!("interrupt {num}"), } @@ -163,6 +180,7 @@ impl<'a, const PR: u16> Chip for Pc<'a, PR> { pub struct PcComponent<'a> { pd: &'a mut PD, pt: &'a mut PT, + ps2: Option<&'a crate::ps2::Ps2Controller<'a>>, } impl<'a> PcComponent<'a> { @@ -178,7 +196,13 @@ impl<'a> PcComponent<'a> { /// /// See [`x86::init`] for further details. pub unsafe fn new(pd: &'a mut PD, pt: &'a mut PT) -> Self { - Self { pd, pt } + Self { pd, pt, ps2: None } + } + + /// Provide the PS/2 controller instance so `Pc` can dispatch IRQ1. + pub fn with_ps2(mut self, ps2: &'a crate::ps2::Ps2Controller<'a>) -> Self { + self.ps2 = Some(ps2); + self } } @@ -217,12 +241,15 @@ impl Component for PcComponent<'static> { let syscall = Boundary::new(); + let ps2 = self.ps2.expect("PcComponent.with_ps2 not called"); + let pc = s.4.write(Pc { com1, com2, com3, com4, pit, + ps2, syscall, paging, }); diff --git a/chips/x86_q35/src/dv_kb.rs b/chips/x86_q35/src/dv_kb.rs new file mode 100644 index 0000000000..698abd492d --- /dev/null +++ b/chips/x86_q35/src/dv_kb.rs @@ -0,0 +1,575 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! PS/2 keyboard wrapper and Set‑2 decoder for the 8042 controller +use crate::ps2_cmd; +use core::cell::RefCell; +use core::marker::PhantomData; +use kernel::errorcode::ErrorCode; +use kernel::hil::ps2_traits::{PS2Keyboard, PS2Traits}; + +/// Public key‑event types + +/// High‑level keyboard event exposed to capsules. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum KeyEvent { + /// Printable ASCII (already affected by Shift / CapsLock). + Ascii(u8), + /// A few non‑printing keys that text UIs care about. + Special(SpecialKey), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SpecialKey { + Backspace, + Enter, + Tab, + ArrowUp, + ArrowDown, + ArrowLeft, + ArrowRight, + PauseBreak, +} + +// Single‑producer / single‑consumer ring buffer for events +const EVT_CAP: usize = 64; // power‑of‑two not required here + +struct EventFifo { + buf: [Option; EVT_CAP], + head: usize, // write position + tail: usize, // read position + full: bool, +} + +impl EventFifo { + const fn new() -> Self { + Self { + buf: [None; EVT_CAP], + head: 0, + tail: 0, + full: false, + } + } + + /// Push, overwriting the oldest event on overflow (lossy but simple). + #[inline] + fn push(&mut self, ev: KeyEvent) { + self.buf[self.head] = Some(ev); + self.head = (self.head + 1) % EVT_CAP; + if self.full { + self.tail = (self.tail + 1) % EVT_CAP; // drop oldest + } else if self.head == self.tail { + self.full = true; + } + } + + #[inline] + fn pop(&mut self) -> Option { + if !self.full && self.head == self.tail { + return None; // empty + } + let ev = self.buf[self.tail].take(); + self.tail = (self.tail + 1) % EVT_CAP; + self.full = false; + ev + } +} + +/// Internal decoder state. Only make codes generate output. +pub struct DecoderState { + prefix_e0: bool, + prefix_e1: u8, // 0 = none, 1 = got E1, 2 = got E1 14 + break_code: bool, + + // Modifier latches + shift: bool, + caps_lock: bool, +} + +impl DecoderState { + /// Fresh decoder (modifiers cleared). + pub const fn new() -> Self { + Self { + prefix_e0: false, + prefix_e1: 0, + break_code: false, + shift: false, + caps_lock: false, + } + } + + /// Feed one raw scan‑code byte; returns Some(KeyEvent) on make only. + #[inline] + pub fn process(&mut self, raw: u8) -> Option { + // ---------------- Prefix handling ---------------- + if raw == 0xE0 { + self.prefix_e0 = true; + return None; + } + if raw == 0xE1 { + self.prefix_e1 = 1; + return None; + } + if self.prefix_e1 != 0 { + // Only handle Pause/Break (E1 14 77 …) + match (self.prefix_e1, raw) { + (1, 0x14) => { + self.prefix_e1 = 2; + return None; + } + (2, 0x77) => { + self.prefix_e1 = 0; + return Some(KeyEvent::Special(SpecialKey::PauseBreak)); + } + _ => { + self.prefix_e1 = 0; // unrecognised sequence + return None; + } + } + } + + if raw == 0xF0 { + self.break_code = true; + return None; + } + + // At this point `raw` is a make/break code depending on flag. + let make = !self.break_code; + self.break_code = false; + + // Modifier latches + match raw { + 0x12 | 0x59 => { + // Shift (both sides) + self.shift = make; + return None; + } + 0x58 => { + // CapsLock toggles on make only + if make { + self.caps_lock = !self.caps_lock; + } + return None; + } + _ => {} + } + + if !make { + return None; // ignore releases for non‑modifier keys + } + + // Make => KeyEvent + if let Some(ascii) = map_scan_to_ascii(raw, self.shift ^ self.caps_lock) { + match ascii { + b'\n' => return Some(KeyEvent::Special(SpecialKey::Enter)), + 0x08 => return Some(KeyEvent::Special(SpecialKey::Backspace)), + b'\t' => return Some(KeyEvent::Special(SpecialKey::Tab)), + _ => return Some(KeyEvent::Ascii(ascii)), + } + } + + // Arrow keys are prefixed with E0. + if self.prefix_e0 { + self.prefix_e0 = false; // consume prefix + let sk = match raw { + 0x75 => SpecialKey::ArrowUp, + 0x72 => SpecialKey::ArrowDown, + 0x6B => SpecialKey::ArrowLeft, + 0x74 => SpecialKey::ArrowRight, + _ => return None, + }; + return Some(KeyEvent::Special(sk)); + } + + None + } +} + +const fn map_scan_to_ascii(code: u8, shifted: bool) -> Option { + match (code, shifted) { + // Letters + (0x1C, false) => Some(b'a'), + (0x1C, true) => Some(b'A'), + (0x32, false) => Some(b'b'), + (0x32, true) => Some(b'B'), + (0x21, false) => Some(b'c'), + (0x21, true) => Some(b'C'), + (0x23, false) => Some(b'd'), + (0x23, true) => Some(b'D'), + (0x24, false) => Some(b'e'), + (0x24, true) => Some(b'E'), + (0x2B, false) => Some(b'f'), + (0x2B, true) => Some(b'F'), + (0x34, false) => Some(b'g'), + (0x34, true) => Some(b'G'), + (0x33, false) => Some(b'h'), + (0x33, true) => Some(b'H'), + (0x43, false) => Some(b'i'), + (0x43, true) => Some(b'I'), + (0x3B, false) => Some(b'j'), + (0x3B, true) => Some(b'J'), + (0x42, false) => Some(b'k'), + (0x42, true) => Some(b'K'), + (0x4B, false) => Some(b'l'), + (0x4B, true) => Some(b'L'), + (0x3A, false) => Some(b'm'), + (0x3A, true) => Some(b'M'), + (0x31, false) => Some(b'n'), + (0x31, true) => Some(b'N'), + (0x44, false) => Some(b'o'), + (0x44, true) => Some(b'O'), + (0x4D, false) => Some(b'p'), + (0x4D, true) => Some(b'P'), + (0x15, false) => Some(b'q'), + (0x15, true) => Some(b'Q'), + (0x2D, false) => Some(b'r'), + (0x2D, true) => Some(b'R'), + (0x1B, false) => Some(b's'), + (0x1B, true) => Some(b'S'), + (0x2C, false) => Some(b't'), + (0x2C, true) => Some(b'T'), + (0x3C, false) => Some(b'u'), + (0x3C, true) => Some(b'U'), + (0x2A, false) => Some(b'v'), + (0x2A, true) => Some(b'V'), + (0x1D, false) => Some(b'w'), + (0x1D, true) => Some(b'W'), + (0x22, false) => Some(b'x'), + (0x22, true) => Some(b'X'), + (0x35, false) => Some(b'y'), + (0x35, true) => Some(b'Y'), + (0x1A, false) => Some(b'z'), + (0x1A, true) => Some(b'Z'), + // Digits + (0x45, false) => Some(b'0'), + (0x45, true) => Some(b')'), + (0x16, false) => Some(b'1'), + (0x16, true) => Some(b'!'), + (0x1E, false) => Some(b'2'), + (0x1E, true) => Some(b'@'), + (0x26, false) => Some(b'3'), + (0x26, true) => Some(b'#'), + (0x25, false) => Some(b'4'), + (0x25, true) => Some(b'$'), + (0x2E, false) => Some(b'5'), + (0x2E, true) => Some(b'%'), + (0x36, false) => Some(b'6'), + (0x36, true) => Some(b'^'), + (0x3D, false) => Some(b'7'), + (0x3D, true) => Some(b'&'), + (0x3E, false) => Some(b'8'), + (0x3E, true) => Some(b'*'), + (0x46, false) => Some(b'9'), + (0x46, true) => Some(b'('), + // Punctuation + (0x0E, false) => Some(b'`'), + (0x0E, true) => Some(b'~'), + (0x4E, false) => Some(b'-'), + (0x4E, true) => Some(b'_'), + (0x55, false) => Some(b'='), + (0x55, true) => Some(b'+'), + (0x54, false) => Some(b'['), + (0x54, true) => Some(b'{'), + (0x5B, false) => Some(b']'), + (0x5B, true) => Some(b'}'), + (0x5D, false) => Some(b'\\'), + (0x5D, true) => Some(b'|'), + (0x4C, false) => Some(b';'), + (0x4C, true) => Some(b':'), + (0x52, false) => Some(b'\''), + (0x52, true) => Some(b'"'), + (0x41, false) => Some(b','), + (0x41, true) => Some(b'<'), + (0x49, false) => Some(b'.'), + (0x49, true) => Some(b'>'), + (0x4A, false) => Some(b'/'), + (0x4A, true) => Some(b'?'), + // Whitespace & control + (0x29, _) => Some(b' '), // space (shift has no effect) + (0x5A, _) => Some(b'\n'), // Enter + (0x66, _) => Some(0x08), // Backspace + (0x0D, _) => Some(b'\t'), // Tab + _ => None, + } +} +/// PS/2 Keyboard wrapper using any `PS2Traits` implementer. +pub struct Keyboard<'a, C: PS2Traits> { + ps2: &'a C, + decoder: RefCell, + events: RefCell, + _marker: PhantomData<&'a ()>, +} + +impl<'a, C: PS2Traits> Keyboard<'a, C> { + pub fn new(ps2: &'a C) -> Self { + Self { + ps2, + decoder: RefCell::new(DecoderState::new()), + events: RefCell::new(EventFifo::new()), + _marker: PhantomData, + } + } + + /// Full device bring-up + /// Controller must already be init + pub fn init(&self) -> Result<(), ErrorCode> { + use crate::ps2_cmd::send; + + // Reset & self test (0xFF - expect 0xAA) + let r = send::(self.ps2, &[0xFF], 1)?; + if r.as_slice() != [0xAA] { + return Err(ErrorCode::FAIL); + } + + // Use Scan-Set 2 + send::(self.ps2, &[0xF0, 0x02], 0)?; + + // Restore Defaults + send::(self.ps2, &[0xF6], 0)?; + + // Typematic: 10.9 cps / 250 ms (0x20) + send::(self.ps2, &[0xF3, 0x20], 0)?; + + // Enable scanning + send::(self.ps2, &[0xF4], 0)?; + + Ok(()) + } + + /// Thin top‑half: simply forward to the controller. + pub fn handle_interrupt(&self) { + let _ = self.ps2.handle_interrupt(); + } + /// Bottom-half: drain raw bytes and queue KeyEvents + pub fn poll(&self) { + while let Some(raw) = self.ps2.pop_scan_code() { + if let Some(evt) = self.decoder.borrow_mut().process(raw) { + self.events.borrow_mut().push(evt); + } + } + } + /// Non-blocking getter for consumers + pub fn next_event(&self) -> Option { + self.events.borrow_mut().pop() + } +} + +impl PS2Keyboard for Keyboard<'_, C> { + fn set_leds(&self, mask: u8) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xED, mask & 0x07], 0).map(|_| ()) + } + + fn probe_echo(&self) -> Result<(), ErrorCode> { + let r = ps2_cmd::send::(self.ps2, &[0xEE], 1)?; + (r.as_slice() == [0xEE]) + .then_some(()) + .ok_or(ErrorCode::FAIL) + } + + fn identify(&self) -> Result<([u8; 3], usize), ErrorCode> { + let r = ps2_cmd::send::(self.ps2, &[0xF2], 3)?; + let mut ids = [0u8; 3]; + ids[..r.len()].copy_from_slice(r.as_slice()); + Ok((ids, r.len())) + } + + fn scan_code_set(&self, cmd: u8) -> Result { + let resp_len = usize::from(cmd == 0); + let r = ps2_cmd::send::(self.ps2, &[0xF0, cmd], resp_len)?; + Ok(if resp_len == 1 { r.as_slice()[0] } else { cmd }) + } + + fn set_typematic(&self, rate: u8) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF3, rate & 0x7F], 0).map(|_| ()) + } + + fn enable_scanning(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF4], 0).map(|_| ()) + } + + fn disable_scanning(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF5], 0).map(|_| ()) + } + + fn set_defaults(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF6], 0).map(|_| ()) + } + + fn set_typematic_only(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF7], 0).map(|_| ()) + } + + fn set_make_release(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF8], 0).map(|_| ()) + } + + fn set_make_only(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xF9], 0).map(|_| ()) + } + + fn set_full_mode(&self) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xFA], 0).map(|_| ()) + } + + fn set_key_typematic_only(&self, sc: u8) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xFB, sc], 0).map(|_| ()) + } + + fn set_key_make_release(&self, sc: u8) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xFC, sc], 0).map(|_| ()) + } + + fn set_key_make_only(&self, sc: u8) -> Result<(), ErrorCode> { + ps2_cmd::send::(self.ps2, &[0xFD, sc], 0).map(|_| ()) + } + + fn is_present(&self) -> bool { + self.probe_echo().is_ok() + } + + fn resend_last_byte(&self) -> Result { + let r = ps2_cmd::send::(self.ps2, &[0xFE], 1)?; + Ok(r.as_slice()[0]) + } + + fn reset_and_self_test(&self) -> Result<(), ErrorCode> { + let r = ps2_cmd::send::(self.ps2, &[0xFF], 1)?; + match r.as_slice()[0] { + 0xAA => Ok(()), + _ => Err(ErrorCode::FAIL), + } + } +} +/// Unit tests, compiled with cargo test +#[cfg(test)] +mod tests { + use super::*; + use core::cell::Cell; + use kernel::errorcode::ErrorCode; + use kernel::hil::ps2_traits::PS2Traits; + + struct DummyPs2 { + bytes: &'static [u8], + idx: Cell, + } + + impl DummyPs2 { + const fn new(bytes: &'static [u8]) -> Self { + Self { + bytes, + idx: Cell::new(0), + } + } + } + + impl PS2Traits for DummyPs2 { + /* functions the keyboard actually uses in these tests */ + fn pop_scan_code(&self) -> Option { + let i = self.idx.get(); + if i < self.bytes.len() { + self.idx.set(i + 1); + Some(self.bytes[i]) + } else { + None + } + } + + /* signature-correct no-ops / dummies */ + fn init(&self) {} + fn wait_input_ready() {} + fn write_data(_: u8) {} + fn write_command(_: u8) {} + fn wait_output_ready() {} + fn read_data() -> u8 { + 0 + } + fn handle_interrupt(&self) -> Result<(), ErrorCode> { + Ok(()) + } + fn push_code(&self, _: u8) -> Result<(), ErrorCode> { + Ok(()) + } + } + #[test] + fn pump_basic() { + // ‘a’ press, then release (F0 1C) + static BYTES: &[u8] = &[0x1C, 0xF0, 0x1C]; + let ctl = DummyPs2::new(BYTES); + let kb = Keyboard::new(&ctl); + + kb.poll(); // drain all bytes + + assert_eq!(kb.next_event(), Some(KeyEvent::Ascii(b'a'))); + assert_eq!(kb.next_event(), None); // release ignored + } + #[test] + fn overflow() { + // 70 ‘a’ presses -> EVT_CAP = 64, so oldest 6 must drop + const N: usize = 70; + static BYTES: [u8; N] = [0x1C; N]; + let ctl = DummyPs2::new(&BYTES); + let kb = Keyboard::new(&ctl); + + kb.poll(); + + let mut count = 0; + while kb.next_event().is_some() { + count += 1; + } + assert_eq!(count, EVT_CAP); // capped at 64 + } + + #[test] + fn init_ok() { + /// Stub controller: + /// Every byte we send is ACKed with 0xFA. + /// After the 0xFF reset command the keyboard replies 0xFA (ACK) + /// followed by 0xAA (self-test pass). + struct AckCtl { + read_cnt: Cell, + } + impl AckCtl { + const fn new() -> Self { + Self { + read_cnt: Cell::new(0), + } + } + } + impl PS2Traits for AckCtl { + fn wait_input_ready() {} + fn write_data(_: u8) {} + fn write_command(_: u8) {} + fn wait_output_ready() {} + + fn read_data() -> u8 { + // 0 -> ACK for 0xFF + // 1 -> 0xAA self-test pass + // 2+ -> ACKs for subsequent commands + use core::sync::atomic::{AtomicU8, Ordering}; + static COUNTER: AtomicU8 = AtomicU8::new(0); + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => 0xFA, + 1 => 0xAA, + _ => 0xFA, + } + } + + /* Unused in this test */ + fn init(&self) {} + fn handle_interrupt(&self) -> Result<(), ErrorCode> { + Ok(()) + } + fn pop_scan_code(&self) -> Option { + None + } + fn push_code(&self, _: u8) -> Result<(), ErrorCode> { + Ok(()) + } + } + + let ctl = AckCtl::new(); + let kb = Keyboard::new(&ctl); + assert!(kb.init().is_ok()); + } +} diff --git a/chips/x86_q35/src/dv_ms.rs b/chips/x86_q35/src/dv_ms.rs new file mode 100644 index 0000000000..1e04ec8f58 --- /dev/null +++ b/chips/x86_q35/src/dv_ms.rs @@ -0,0 +1,200 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Your Name 2024. + +//! PS/2 mouse wrapper for the 8042 controller + +use core::cell::{Cell, RefCell}; +use core::marker::PhantomData; +use kernel::errorcode::ErrorCode; +use kernel::hil::ps2_traits::{MouseEvent, PS2Mouse, PS2Traits}; + +const RAW_BUF_SIZE: usize = 32; // depth of raw‐byte ring +const PACKET_BUF_SIZE: usize = 16; // depth of 3‐byte packet ring + +/// Raw‐byte FIFO +struct RawFifo { + buf: [u8; RAW_BUF_SIZE], + head: usize, + tail: usize, + full: bool, +} + +impl RawFifo { + const fn new() -> Self { + Self { + buf: [0; RAW_BUF_SIZE], + head: 0, + tail: 0, + full: false, + } + } + fn push(&mut self, b: u8) { + self.buf[self.head] = b; + self.head = (self.head + 1) % RAW_BUF_SIZE; + if self.full { + self.tail = (self.tail + 1) % RAW_BUF_SIZE; + } else if self.head == self.tail { + self.full = true; + } + } + fn pop(&mut self) -> Option { + if !self.full && self.head == self.tail { + None + } else { + let b = self.buf[self.tail]; + self.tail = (self.tail + 1) % RAW_BUF_SIZE; + self.full = false; + Some(b) + } + } +} + +/// 3‑byte packet FIFO +struct PacketFifo { + buf: [[u8; 3]; PACKET_BUF_SIZE], + head: usize, + tail: usize, + full: bool, +} + +impl PacketFifo { + const fn new() -> Self { + Self { + buf: [[0; 3]; PACKET_BUF_SIZE], + head: 0, + tail: 0, + full: false, + } + } + fn push(&mut self, pkt: [u8; 3]) { + self.buf[self.head] = pkt; + self.head = (self.head + 1) % PACKET_BUF_SIZE; + if self.full { + self.tail = (self.tail + 1) % PACKET_BUF_SIZE; + } else if self.head == self.tail { + self.full = true; + } + } + fn pop(&mut self) -> Option<[u8; 3]> { + if !self.full && self.head == self.tail { + None + } else { + let pkt = self.buf[self.tail]; + self.tail = (self.tail + 1) % PACKET_BUF_SIZE; + self.full = false; + Some(pkt) + } + } +} + +/// Main PS/2 mouse driver +pub struct Mouse<'a, C: PS2Traits> { + controller: &'a C, + raw: RefCell, + packet_fifo: RefCell, + state: Cell, + pkt: Cell<[u8; 3]>, + _marker: PhantomData<&'a ()>, +} + +impl<'a, C: PS2Traits> Mouse<'a, C> { + pub fn new(controller: &'a C) -> Self { + Self { + controller, + raw: RefCell::new(RawFifo::new()), + packet_fifo: RefCell::new(PacketFifo::new()), + state: Cell::new(0), + pkt: Cell::new([0; 3]), + _marker: PhantomData, + } + } + + /// Top‑half: call from IRQ stub. Reads one byte & assembles 3‑byte packets. + pub fn handle_interrupt(&self) { + let _ = self.controller.handle_interrupt(); + if let Some(b) = self.controller.pop_scan_code() { + let mut st = self.state.get(); + let mut buf_pkt = self.pkt.get(); + buf_pkt[st] = b; + st += 1; + if st == 3 { + // full packet ready + self.packet_fifo.borrow_mut().push(buf_pkt); + st = 0; + } + self.pkt.set(buf_pkt); + self.state.set(st); + } + } + + /// Bottom‑half: non‑blocking decode of next packet → MouseEvent + pub fn poll(&self) -> Option { + if let Some(pkt) = self.packet_fifo.borrow_mut().pop() { + // TODO: translate raw pkt bytes into MouseEvent fields + let event = MouseEvent { + buttons: pkt[0] & 0x07, + x_movement: ((pkt[0] as i8 as i16) << 8 | pkt[1] as i16) as i8, + y_movement: ((pkt[0] as i8 as i16) << 8 | pkt[2] as i16) as i8, + }; + Some(event) + } else { + None + } + } +} +impl<'a, C: PS2Traits> PS2Mouse for Mouse<'a, C> { + /// 1:1 scaling + fn set_scaling_1_1(&self) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xE6], 0).map(|_| ()) + } + + /// 2:1 scaling + fn set_scaling_2_1(&self) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xE7], 0).map(|_| ()) + } + + /// Resolution (0–3) + fn set_resolution(&self, res: u8) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xE8, res], 0).map(|_| ()) + } + + /// Status request → 3‑byte response + fn status_request(&self) -> Result<[u8; 3], ErrorCode> { + let resp = crate::ps2_cmd::send::(self.controller, &[0xE9], 3)?; + let mut out = [0u8; 3]; + out.copy_from_slice(resp.as_slice()); + Ok(out) + } + + /// Read one “packet” (3 raw bytes assembled in your FIFO → decode elsewhere) + fn read_data(&self) -> Result { + self.poll().ok_or(ErrorCode::NOMEM) + } + + /// Remote‐mode sample‐rate setter (0xF3) + fn set_sample_rate(&self, rate: u8) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xF3, rate], 0).map(|_| ()) + } + + /// Put the device into “streaming” (push on movement) + fn enable_streaming(&self) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xF4], 0).map(|_| ()) + } + + /// Halt streaming updates + fn disable_streaming(&self) -> Result<(), ErrorCode> { + crate::ps2_cmd::send::(self.controller, &[0xF5], 0).map(|_| ()) + } + + /// Reset device and verify self‐test + fn reset(&self) -> Result<(), ErrorCode> { + let resp = crate::ps2_cmd::send::(self.controller, &[0xFF], 2)?; + // resp.as_slice() == &[0xFA, 0xAA] + if resp.as_slice() == &[0xAA] { + Ok(()) + } else { + Err(ErrorCode::FAIL) + } + } +} diff --git a/chips/x86_q35/src/interrupts.rs b/chips/x86_q35/src/interrupts.rs index b35763eeea..4c3632c93d 100644 --- a/chips/x86_q35/src/interrupts.rs +++ b/chips/x86_q35/src/interrupts.rs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Copyright Tock Contributors 2024. -use x86::InterruptPoller; - use super::pic; +use x86::InterruptPoller; /// Handler for external interrupts. /// diff --git a/chips/x86_q35/src/lib.rs b/chips/x86_q35/src/lib.rs index c80a455b36..955c4d7295 100644 --- a/chips/x86_q35/src/lib.rs +++ b/chips/x86_q35/src/lib.rs @@ -18,9 +18,12 @@ mod chip; pub use chip::{Pc, PcComponent}; mod interrupts; - +//keyboard + mouse drivers +pub mod dv_kb; +pub mod dv_ms; mod pic; - pub mod pit; - +pub mod ps2; +pub mod ps2_cmd; +pub mod ps2_ctl; pub mod serial; diff --git a/chips/x86_q35/src/ps2.rs b/chips/x86_q35/src/ps2.rs new file mode 100644 index 0000000000..7b3b88052f --- /dev/null +++ b/chips/x86_q35/src/ps2.rs @@ -0,0 +1,165 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +use core::cell::{Cell, RefCell}; +use core::marker::PhantomData; +use kernel::debug; +use kernel::hil::ps2_traits::PS2Traits; +use kernel::ErrorCode; +use x86::registers::io; + +/// PS/2 controller ports +const PS2_DATA_PORT: u16 = 0x60; +const PS2_STATUS_PORT: u16 = 0x64; + +/// Status‑register bits +const STATUS_OUTPUT_FULL: u8 = 1 << 0; // data ready +const STATUS_INPUT_FULL: u8 = 1 << 1; // input buffer full + +/// Timeout limit for spin loops +const TIMEOUT_LIMIT: usize = 1_000_000; + +/// Depth of the scan‑code ring buffer +const BUFFER_SIZE: usize = 32; + +/// PS/2 controller driver (the “8042” peripheral) +pub struct Ps2Controller<'a> { + buffer: RefCell<[u8; BUFFER_SIZE]>, + head: Cell, + tail: Cell, + _marker: PhantomData<&'a ()>, +} + +impl Ps2Controller<'_> { + /// Create a new PS/2 controller instance. + pub fn new() -> Self { + Ps2Controller { + buffer: RefCell::new([0; BUFFER_SIZE]), + head: Cell::new(0), + tail: Cell::new(0), + _marker: PhantomData, + } + } +} + +impl PS2Traits for Ps2Controller<'_> { + fn wait_input_ready() { + let mut cnt = 0; + while unsafe { io::inb(PS2_STATUS_PORT) } & STATUS_INPUT_FULL != 0 { + cnt += 1; + if cnt >= TIMEOUT_LIMIT { + debug!("PS/2 wait_input_ready timed out"); + break; + } + } + } + + fn wait_output_ready() { + let mut cnt = 0; + while unsafe { io::inb(PS2_STATUS_PORT) } & STATUS_OUTPUT_FULL == 0 { + cnt += 1; + if cnt >= TIMEOUT_LIMIT { + debug!("PS/2 wait_output_ready timed out"); + break; + } + } + } + + fn read_data() -> u8 { + Self::wait_output_ready(); + unsafe { io::inb(PS2_DATA_PORT) } + } + + fn write_command(cmd: u8) { + Self::wait_input_ready(); + unsafe { io::outb(PS2_STATUS_PORT, cmd) }; + } + + fn write_data(data: u8) { + Self::wait_input_ready(); + unsafe { io::outb(PS2_DATA_PORT, data) }; + } + + fn init(&self) { + unsafe { + // 1) Disable keyboard and auxiliary channels + Self::write_command(0xAD); + Self::write_command(0xA7); + + // 2) Flush any pending output + while io::inb(PS2_STATUS_PORT) & STATUS_OUTPUT_FULL != 0 { + let _ = Self::read_data(); + } + + // 3) Controller self-test (0xAA → expect 0x55) + Self::write_command(0xAA); + Self::wait_output_ready(); + let res = Self::read_data(); + if res != 0x55 { + debug!("PS/2 self-test failed: {:02x}", res); + } + + // 4) Read/modify/write config byte (enable IRQ1) + Self::write_command(0x20); + let mut cfg = Self::read_data(); + cfg |= 1 << 0; // enable IRQ1 + Self::write_command(0x60); + Self::write_data(cfg); + + // 5) Test keyboard port (0xAB → expect 0x00) + Self::write_command(0xAB); + Self::wait_output_ready(); + let port_ok = Self::read_data(); + if port_ok != 0x00 { + debug!("PS/2 keyboard-port test failed: {:02x}", port_ok); + } + + // 6) Enable scanning on keyboard device (0xF4 → expect 0xFA) + Self::write_data(0xF4); + Self::wait_output_ready(); + let ack = Self::read_data(); + if ack != 0xFA { + debug!("PS/2 enable-scan ACK failed: {:02x}", ack); + } + + // 7) Re-enable keyboard channel + Self::write_command(0xAE); + + // 8) Unmask IRQ1 on master PIC + const PIC1_DATA: u16 = 0x21; + let mask = io::inb(PIC1_DATA); + io::outb(PIC1_DATA, mask & !(1 << 1)); + } + } + + fn handle_interrupt(&self) -> Result<(), ErrorCode> { + let sc = Self::read_data(); + self.push_code(sc)?; + Ok(()) + } + + fn pop_scan_code(&self) -> Option { + let head = self.head.get(); + let tail = self.tail.get(); + if head == tail { + None + } else { + let byte = self.buffer.borrow()[tail]; + self.tail.set((tail + 1) % BUFFER_SIZE); + Some(byte) + } + } + + fn push_code(&self, code: u8) -> Result<(), ErrorCode> { + let head = self.head.get(); + let next = (head + 1) % BUFFER_SIZE; + if next == self.tail.get() { + // buffer full → drop oldest + self.tail.set((self.tail.get() + 1) % BUFFER_SIZE); + } + self.buffer.borrow_mut()[head] = code; + self.head.set(next); + Ok(()) + } +} diff --git a/chips/x86_q35/src/ps2_cmd.rs b/chips/x86_q35/src/ps2_cmd.rs new file mode 100644 index 0000000000..13ce333d86 --- /dev/null +++ b/chips/x86_q35/src/ps2_cmd.rs @@ -0,0 +1,144 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Shared command‑queue helper for PS/2 host‑to‑device transactions +//! +//! Centralises the ACK/RESEND handshake and retry logic required by +//! LED, typematic‑rate, scan‑set and similar commands. + +use kernel::errorcode::ErrorCode; +use kernel::hil::ps2_traits::PS2Traits; + +/// Maximum number of bytes the command helper supports +/// (opcode + parameters + response). +pub const MAX_CMD: usize = 8; + +/// Simple fixed‑capacity response buffer. +#[derive(Copy, Clone, Debug)] +pub struct Resp { + buf: [u8; MAX_CMD], + len: usize, +} +impl Resp { + pub const fn new() -> Self { + Self { + buf: [0; MAX_CMD], + len: 0, + } + } + pub fn push(&mut self, b: u8) { + if self.len < MAX_CMD { + self.buf[self.len] = b; + self.len += 1; + } + } + pub fn as_slice(&self) -> &[u8] { + &self.buf[..self.len] + } + pub fn len(&self) -> usize { + self.len + } +} + +/// Send `cmd` (opcode + optional data) and collect `resp_len` bytes. +/// Automatically retries the entire sequence on `0xFE` (RESEND) +/// up to 3 times. +pub fn send( + _ctl: &C, // reference kept for type inference; methods are static + cmd: &[u8], + resp_len: usize, +) -> Result { + const MAX_RETRIES: usize = 3; + assert!(cmd.len() <= MAX_CMD); + assert!(resp_len <= MAX_CMD); + + let mut retries = 0; + let mut resp = Resp::new(); + + let _ = _ctl; // suppress unused warning + + 'retry: loop { + // host => device + for &b in cmd { + C::wait_input_ready(); + C::write_data(b); + + C::wait_output_ready(); + match C::read_data() { + 0xFA => {} // ACK – proceed + 0xFE => { + retries += 1; + if retries > MAX_RETRIES { + return Err(ErrorCode::FAIL); + } + continue 'retry; // restart whole sequence + } + _ => return Err(ErrorCode::FAIL), // unexpected byte + } + } + + // device => host response + resp.len = 0; // reset + for _ in 0..resp_len { + C::wait_output_ready(); + resp.push(C::read_data()); + } + return Ok(resp); + } +} + +/// Testing the cmd +#[cfg(test)] +mod tests { + use super::*; + use core::cell::Cell; + use kernel::errorcode::ErrorCode; + use kernel::hil::ps2_traits::PS2Traits; + + /// Dummy controller that ACKs every byte except the first time, where it RESENDs once. + struct StubCtl { + step: Cell, + } + impl StubCtl { + const fn new() -> Self { + Self { step: Cell::new(0) } + } + } + impl PS2Traits for StubCtl { + fn wait_input_ready() {} + fn wait_output_ready() {} + fn write_data(_b: u8) {} + fn write_command(_: u8) {} + fn read_data() -> u8 { + // first call => 0xFE (RESEND), then 0xFA (ACK) + static mut COUNT: u8 = 0; + unsafe { + COUNT += 1; + if COUNT == 1 { + 0xFE + } else { + 0xFA + } + } + } + fn init(&self) {} + fn handle_interrupt(&self) -> Result<(), ErrorCode> { + Ok(()) + } + fn pop_scan_code(&self) -> Option { + None + } + fn push_code(&self, _: u8) -> Result<(), ErrorCode> { + Ok(()) + } + } + + #[test] + fn resend_retry_succeeds() { + let ctl = StubCtl::new(); + // Send “Set LEDs” (0xED) with dummy mask, expect no data bytes + let r = super::send(&ctl, &[0xED, 0x00], 0).unwrap(); + assert_eq!(r.len(), 0); + } +} diff --git a/chips/x86_q35/src/ps2_ctl.rs b/chips/x86_q35/src/ps2_ctl.rs new file mode 100644 index 0000000000..c9a0376477 --- /dev/null +++ b/chips/x86_q35/src/ps2_ctl.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Low-level 8042 (i8042) controller bring-up. +//! Does not touch keyboard protocol settings. + +use kernel::errorcode::ErrorCode; +use kernel::hil::ps2_traits::PS2Traits; + +/// Run once, before any device-level init + +pub fn init_controller() -> Result<(), ErrorCode> { + // Disable keyboard (port 1) and aux (port 2) + C::write_command(0xAD); + C::write_command(0xA7); + + // Self-test: 0xAA - expect 0x55 + C::write_command(0xAA); + C::wait_output_ready(); + if C::read_data() != 0x55 { + return Err(ErrorCode::FAIL); + } + + // Enable IRQ1 in config byte + C::write_command(0x20); + C::wait_output_ready(); + let mut cfg = C::read_data(); + cfg |= 1 << 0; //bit0 = IRQ1 enable + C::write_command(0x60); + C::write_data(cfg); + + // Port-1 interface test 0xAB - expect 0x00 + C::write_command(0xAB); + C::wait_output_ready(); + if C::read_data() != 0x00 { + return Err(ErrorCode::FAIL); + } + Ok(()) +} diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs index 1362583d89..54a5e37664 100644 --- a/kernel/src/hil/mod.rs +++ b/kernel/src/hil/mod.rs @@ -27,6 +27,8 @@ pub mod kv; pub mod led; pub mod log; pub mod nonvolatile_storage; + +pub mod ps2_traits; pub mod public_key_crypto; pub mod pwm; pub mod radio; @@ -42,7 +44,6 @@ pub mod touch; pub mod uart; pub mod usb; pub mod usb_hid; - /// Shared interface for configuring components. pub trait Controller { type Config; diff --git a/kernel/src/hil/ps2_traits.rs b/kernel/src/hil/ps2_traits.rs new file mode 100644 index 0000000000..351a74bbe7 --- /dev/null +++ b/kernel/src/hil/ps2_traits.rs @@ -0,0 +1,135 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +use crate::ErrorCode; + +/// A Tock HIL for talking to an 8042 PS/2 controller. +pub trait PS2Traits { + /// Block until the controller input buffer is free. + fn wait_input_ready(); + + /// Block until the output buffer has data. + fn wait_output_ready(); + + /// Read a byte from data port. + fn read_data() -> u8; + + /// Write a command byte to the command port. + fn write_command(cmd: u8); + + /// Write a data byte to the data port. + fn write_data(data: u8); + + /// Initialize the controller (self-test, config, enable IRQ, etc). + fn init(&self); + + /// Called from your IRQ stub. Should read one byte and + /// return `Ok(())` or an error code. + fn handle_interrupt(&self) -> Result<(), ErrorCode>; + + /// Pop one scan‐code out of the driver’s ring buffer. + fn pop_scan_code(&self) -> Option; + + /// Push one scan‐code into the ring buffer. + fn push_code(&self, code: u8) -> Result<(), ErrorCode>; +} +pub trait PS2Keyboard { + /// Set keyboard LEDs: bit0=ScrollLock, bit1=NumLock, bit2=CapsLock. + fn set_leds(&self, mask: u8) -> Result<(), ErrorCode>; + + /// Send Echo (0xEE) to keyboard and expect same byte back. + fn probe_echo(&self) -> Result<(), ErrorCode>; + + /// Check if a keyboard is present (returns true on successful echo). + fn is_present(&self) -> bool; + + /// Identify keyboard: send 0xF2, get up to 3 ID bytes and count. + fn identify(&self) -> Result<([u8; 3], usize), ErrorCode>; + + /// Get or set current scan‑code set (0=Get, 1-3=Set). + /// Returns the set number on success. + fn scan_code_set(&self, subcmd: u8) -> Result; + + /// Set typematic rate and delay (0xF3 + rate_delay byte). + fn set_typematic(&self, rate_delay: u8) -> Result<(), ErrorCode>; + + /// Enable scan-code reporting (0xF4). + fn enable_scanning(&self) -> Result<(), ErrorCode>; + + /// Disable scan-code reporting (0xF5). + fn disable_scanning(&self) -> Result<(), ErrorCode>; + + /// Restore default keyboard parameters (0xF6). + fn set_defaults(&self) -> Result<(), ErrorCode>; + + /// Set all keys to auto-repeat only (0xF7, scancode set 3 only). + fn set_typematic_only(&self) -> Result<(), ErrorCode>; + + /// Set all keys to make + release (0xF8, scancode set 3 only). + fn set_make_release(&self) -> Result<(), ErrorCode>; + + /// Set all keys to make-only (0xF9, scancode set 3 only). + fn set_make_only(&self) -> Result<(), ErrorCode>; + + /// Set full-full-full mode (0xFA, scancode set 3 only). + fn set_full_mode(&self) -> Result<(), ErrorCode>; + + /// Set a specific key to auto-repeat only (0xFB, scancode set 3 only). + fn set_key_typematic_only(&self, scancode: u8) -> Result<(), ErrorCode>; + + /// Set a specific key to make + release (0xFC, scancode set 3 only). + fn set_key_make_release(&self, scancode: u8) -> Result<(), ErrorCode>; + + /// Set a specific key to make-only (0xFD, scancode set 3 only). + fn set_key_make_only(&self, scancode: u8) -> Result<(), ErrorCode>; + + /// Request keyboard to resend last byte (0xFE). Returns the resent byte. + fn resend_last_byte(&self) -> Result; + + /// Reset keyboard and run self-test (0xFF). Expects ACK (0xFA), then result (0xAA pass). + fn reset_and_self_test(&self) -> Result<(), ErrorCode>; +} + +/// Trait for consuming decoded key inputs (ASCII or keycodes). +pub trait KBReceiver { + /// Called by consumers to fetch one decoded byte, `None` if none available. + fn receive(&self) -> Option; +} + +pub enum MousePacket { + /// (left, right, middle buttons state, x_delta, y_delta) + Relative { buttons: u8, dx: i8, dy: i8 }, + /// (optional) Wheel or 5‑button support, if we want to expand + Extended { + buttons: u8, + dx: i8, + dy: i8, + wheel: i8, + }, +} + +pub trait PS2Mouse { + /// Reset + self‑test, returns OK or FAIL. + fn set_scaling_1_1(&self) -> Result<(), ErrorCode>; + fn set_scaling_2_1(&self) -> Result<(), ErrorCode>; + fn status_request(&self) -> Result<[u8; 3], ErrorCode>; + fn read_data(&self) -> Result; + fn reset(&self) -> Result<(), ErrorCode>; + + /// Enable streaming (data reporting). + fn enable_streaming(&self) -> Result<(), ErrorCode>; + + /// Disable streaming. + fn disable_streaming(&self) -> Result<(), ErrorCode>; + + /// Set sampling rate, protocol resolution, etc. + fn set_sample_rate(&self, hz: u8) -> Result<(), ErrorCode>; + fn set_resolution(&self, counts_per_mm: u8) -> Result<(), ErrorCode>; +} +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MouseEvent { + pub buttons: u8, + pub x_movement: i8, + pub y_movement: i8, +}