⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/drivers/
input.rs

1//! Input multiplexer — unified character input from keyboard and serial.
2//!
3//! Checks all available input sources (PS/2 keyboard on x86_64, then serial)
4//! and returns the first available byte. This replaces the per-architecture
5//! inline serial reads in the shell.
6
7/// Read a single character from any available input source (non-blocking).
8///
9/// On x86_64: polls PS/2 keyboard controller, checks ring buffer, then serial.
10/// On AArch64: checks PL011 UART.
11/// On RISC-V: checks SBI console_getchar.
12pub fn read_char() -> Option<u8> {
13    #[cfg(target_arch = "x86_64")]
14    {
15        // Poll the PS/2 keyboard controller directly. The APIC takes over
16        // interrupt routing from the PIC, so IRQ1 may never fire. Polling
17        // the keyboard controller status port (0x64) ensures keystrokes
18        // from the QEMU graphical window are captured regardless.
19        poll_ps2_keyboard();
20
21        // Check decoded key buffer (filled by polling above or IRQ handler)
22        if let Some(key) = crate::drivers::keyboard::read_key() {
23            return Some(key);
24        }
25        // Fall back to serial port (COM1 at 0x3F8)
26        read_serial_x86_64()
27    }
28
29    #[cfg(target_arch = "aarch64")]
30    {
31        read_uart_aarch64()
32    }
33
34    #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
35    {
36        read_sbi_riscv()
37    }
38}
39
40/// Poll the PS/2 controller for available data bytes.
41///
42/// Drains all pending bytes from the PS/2 output buffer (port 0x60),
43/// dispatching keyboard scancodes to the keyboard driver and mouse
44/// bytes to the mouse driver. Both must be drained because the PS/2
45/// controller has a single output buffer -- leaving a mouse byte
46/// unread blocks all subsequent keyboard data.
47///
48/// Status register (port 0x64) bits:
49///   bit 0 = output buffer full (data available in port 0x60)
50///   bit 5 = data is from auxiliary (mouse) port
51///
52/// This is necessary because the APIC (initialized during boot) takes
53/// over interrupt routing from the legacy PIC. The PIC's IRQ1 (keyboard)
54/// and IRQ12 (mouse) may never fire, so we poll instead.
55#[cfg(target_arch = "x86_64")]
56fn poll_ps2_keyboard() {
57    for _ in 0..16 {
58        let status: u8;
59        // SAFETY: Reading the PS/2 controller status register (port 0x64).
60        unsafe {
61            core::arch::asm!(
62                "in al, dx",
63                out("al") status,
64                in("dx") 0x64u16,
65                options(nomem, nostack)
66            );
67        }
68        if (status & 0x01) == 0 {
69            break; // No data pending
70        }
71        let byte: u8;
72        // SAFETY: Reading the PS/2 data register (port 0x60).
73        // This clears the output buffer, allowing the next byte through.
74        unsafe {
75            core::arch::asm!(
76                "in al, dx",
77                out("al") byte,
78                in("dx") 0x60u16,
79                options(nomem, nostack)
80            );
81        }
82        if (status & 0x20) != 0 {
83            // Mouse byte -- dispatch to mouse driver to unblock the buffer
84            crate::drivers::mouse::poll_mouse_byte(byte);
85        } else {
86            // Keyboard scancode
87            crate::drivers::keyboard::handle_scancode(byte);
88        }
89    }
90}
91
92/// Read from COM1 serial port (x86_64).
93#[cfg(target_arch = "x86_64")]
94fn read_serial_x86_64() -> Option<u8> {
95    let status: u8;
96    // SAFETY: Reading the Line Status Register (port 0x3FD) to check if
97    // data is available. Port 0x3F8 is the COM1 data register.
98    unsafe {
99        core::arch::asm!(
100            "in al, dx",
101            out("al") status,
102            in("dx") 0x3FDu16,
103            options(nomem, nostack)
104        );
105    }
106    if (status & 1) != 0 {
107        let data: u8;
108        unsafe {
109            core::arch::asm!(
110                "in al, dx",
111                out("al") data,
112                in("dx") 0x3F8u16,
113                options(nomem, nostack)
114            );
115        }
116        Some(data)
117    } else {
118        None
119    }
120}
121
122/// Read from PL011 UART (AArch64, QEMU virt).
123#[cfg(target_arch = "aarch64")]
124fn read_uart_aarch64() -> Option<u8> {
125    const UART_BASE: usize = 0x0900_0000;
126    const UART_FR: usize = UART_BASE + 0x18; // Flag register
127    const UART_DR: usize = UART_BASE; // Data register
128
129    // SAFETY: Reading MMIO registers for PL011 UART. The QEMU virt
130    // machine maps UART at this address.
131    unsafe {
132        let flags = core::ptr::read_volatile(UART_FR as *const u32);
133        if (flags & (1 << 4)) == 0 {
134            // RXFE bit clear = data available
135            let data = core::ptr::read_volatile(UART_DR as *const u32);
136            Some((data & 0xFF) as u8)
137        } else {
138            None
139        }
140    }
141}
142
143/// Read via SBI console_getchar (RISC-V).
144#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
145fn read_sbi_riscv() -> Option<u8> {
146    let result: isize;
147    // SAFETY: SBI call to console_getchar (legacy extension 0x02).
148    // Returns the character or -1 if no input is available.
149    unsafe {
150        core::arch::asm!(
151            "li a7, 0x02",  // SBI_CONSOLE_GETCHAR
152            "ecall",
153            out("a0") result,
154            out("a7") _,
155            options(nomem)
156        );
157    }
158    if result >= 0 {
159        Some(result as u8)
160    } else {
161        None
162    }
163}