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}