Skip to main content

strat9_kernel/arch/x86_64/
keyboard.rs

1//! PS/2 Keyboard driver
2//!
3//! Handles IRQ1 keyboard interrupts, reads scancodes from port 0x60,
4//! and converts them to characters for the VGA console.
5//!
6//! Modifier state and scancode processing are shared via keyboard_layout.rs.
7//! This module owns the ring buffer, PS/2 port init, and lost-key diagnostics.
8
9use super::io::{inb, outb};
10use core::sync::atomic::{AtomicUsize, Ordering};
11use spin::Mutex;
12
13/// Keyboard input buffer size
14const KEYBOARD_BUFFER_SIZE: usize = 256;
15
16// ========== Interrupt-safe ring buffer ======================================================================
17//
18// All state collapsed into a single Mutex. In non-ISR paths (pop/has_data),
19// interrupts are disabled BEFORE taking the lock. The ISR already runs with
20// IF=0, so it never needs to disable interrupts.
21
22struct KeyboardBufferInner {
23    buf: [u8; KEYBOARD_BUFFER_SIZE],
24    head: usize,
25    tail: usize,
26}
27
28struct KeyboardBuffer {
29    inner: Mutex<KeyboardBufferInner>,
30}
31
32static KEYBOARD_BUFFER: KeyboardBuffer = KeyboardBuffer::new();
33
34/// Number of characters dropped due to buffer overflow.
35/// Incremented atomically in IRQ context, safe to read from any context.
36static LOST_KEY_COUNT: AtomicUsize = AtomicUsize::new(0);
37
38impl KeyboardBuffer {
39    const fn new() -> Self {
40        Self {
41            inner: Mutex::new(KeyboardBufferInner {
42                buf: [0u8; KEYBOARD_BUFFER_SIZE],
43                head: 0,
44                tail: 0,
45            }),
46        }
47    }
48
49    /// Called exclusively from IRQ context (IF=0 already).
50    pub fn push(&self, ch: u8) {
51        let mut g = self.inner.lock();
52        let tail = g.tail;
53        g.buf[tail] = ch;
54        g.tail = (tail + 1) % KEYBOARD_BUFFER_SIZE;
55        // Buffer full: drop oldest character and count it.
56        if g.head == g.tail {
57            g.head = (g.head + 1) % KEYBOARD_BUFFER_SIZE;
58            LOST_KEY_COUNT.fetch_add(1, Ordering::Relaxed);
59        }
60    }
61
62    /// Called from task context. Interrupts disabled before lock acquisition.
63    pub fn pop(&self) -> Option<u8> {
64        let saved = super::save_flags_and_cli();
65        let result = {
66            let mut g = self.inner.lock();
67            if g.head == g.tail {
68                None
69            } else {
70                let ch = g.buf[g.head];
71                g.head = (g.head + 1) % KEYBOARD_BUFFER_SIZE;
72                Some(ch)
73            }
74        };
75        super::restore_flags(saved);
76        result
77    }
78
79    /// Called from task context. Same interrupt-disable discipline as pop.
80    pub fn has_data(&self) -> bool {
81        let saved = super::save_flags_and_cli();
82        let result = {
83            let g = self.inner.lock();
84            g.head != g.tail
85        };
86        super::restore_flags(saved);
87        result
88    }
89}
90
91/// Add a character to the keyboard buffer (called from IRQ context).
92///
93/// Ctrl+C (0x03) also sets the global SHELL_INTERRUPTED flag.
94pub fn add_to_buffer(ch: u8) {
95    if ch == 0x03 {
96        crate::shell::SHELL_INTERRUPTED.store(true, Ordering::Relaxed);
97    }
98    KEYBOARD_BUFFER.push(ch);
99}
100
101/// Get a character from the keyboard buffer (non-blocking, task context only).
102pub fn read_char() -> Option<u8> {
103    KEYBOARD_BUFFER.pop()
104}
105
106/// Check if keyboard buffer has data (task context only).
107pub fn has_input() -> bool {
108    KEYBOARD_BUFFER.has_data()
109}
110
111/// Return the total number of characters lost due to buffer overflow since boot.
112pub fn lost_key_count() -> usize {
113    LOST_KEY_COUNT.load(Ordering::Relaxed)
114}
115
116/// Reset the lost-key counter to zero.
117pub fn reset_lost_key_count() {
118    LOST_KEY_COUNT.store(0, Ordering::Relaxed);
119}
120
121/// Inject a PS/2 scancode from USB HID or other non-PS/2 source.
122///
123/// Must be called from task context. Converts scancode to character via
124/// the current layout and pushes to the buffer if applicable.
125pub fn inject_hid_scancode(scancode: u8, pressed: bool) {
126    let raw = if pressed { scancode } else { scancode | 0x80 };
127    if let Some(ch) = super::keyboard_layout::handle_scancode_raw(raw) {
128        let saved = super::save_flags_and_cli();
129        KEYBOARD_BUFFER.push(ch);
130        super::restore_flags(saved);
131    }
132}
133
134// Re-export special key constants for consumers (e.g. shell/commands/top)
135pub use super::keyboard_layout::{KEY_DOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_RIGHT, KEY_UP};
136
137/// PS/2 keyboard data port
138pub(crate) const KEYBOARD_DATA_PORT: u16 = 0x60;
139const PS2_CMD_PORT: u16 = 0x64;
140
141const CMD_READ_CFG: u8 = 0x20;
142const CMD_WRITE_CFG: u8 = 0x60;
143const CMD_ENABLE_KBD: u8 = 0xAE;
144
145const STATUS_OUTPUT_FULL: u8 = 0x01;
146const STATUS_INPUT_FULL: u8 = 0x02;
147
148#[inline]
149fn wait_write() {
150    for _ in 0..100_000u32 {
151        if unsafe { inb(PS2_CMD_PORT) } & STATUS_INPUT_FULL == 0 {
152            return;
153        }
154        core::hint::spin_loop();
155    }
156}
157
158#[inline]
159fn wait_read() {
160    for _ in 0..100_000u32 {
161        if unsafe { inb(PS2_CMD_PORT) } & STATUS_OUTPUT_FULL != 0 {
162            return;
163        }
164        core::hint::spin_loop();
165    }
166}
167
168fn ps2_read() -> u8 {
169    wait_read();
170    unsafe { inb(KEYBOARD_DATA_PORT) }
171}
172
173fn ps2_write_cmd(cmd: u8) {
174    wait_write();
175    unsafe { outb(PS2_CMD_PORT, cmd) };
176}
177
178fn ps2_write_data(data: u8) {
179    wait_write();
180    unsafe { outb(KEYBOARD_DATA_PORT, data) };
181}
182
183fn flush_output() {
184    for _ in 0..16 {
185        if unsafe { inb(PS2_CMD_PORT) } & STATUS_OUTPUT_FULL == 0 {
186            break;
187        }
188        unsafe { inb(KEYBOARD_DATA_PORT) };
189    }
190}
191
192/// Initialize the PS/2 keyboard controller
193///
194/// After APIC/IOAPIC reconfiguration, explicitly enable the keyboard port,
195/// IRQ1, and the keyboard clock.
196pub fn init() {
197    flush_output();
198
199    ps2_write_cmd(CMD_ENABLE_KBD);
200
201    ps2_write_cmd(CMD_READ_CFG);
202    let mut cfg = ps2_read();
203    cfg |= 0x01;
204    cfg &= !0x10;
205    ps2_write_cmd(CMD_WRITE_CFG);
206    ps2_write_data(cfg);
207
208    flush_output();
209}