Skip to main content

strat9_kernel/vfs/
input_scheme.rs

1//! Input scheme : exposes raw keyboard and mouse events to userspace.
2//!
3//! Mounted at `/dev/input`. Provides two virtual files:
4//!   `/dev/input/kbd`  : raw keyboard scancodes (1 byte per event)
5//!   `/dev/input/mouse` : raw mouse events (7 bytes per event)
6//!
7//! The scheme reads from the kernel-internal ring buffers (keyboard.rs,
8//! mouse.rs) which are filled by PS/2 and USB HID IRQ handlers.
9//! Layout translation and modifier state are handled in userspace
10//! by the Input Server.
11
12use crate::syscall::error::SyscallError;
13use alloc::{string::String, vec, vec::Vec};
14use core::sync::atomic::{AtomicU64, Ordering};
15
16use super::scheme::{
17    finalize_pseudo_stat, DirEntry, FileFlags, FileStat, OpenFlags, OpenResult, Scheme, DEV_INPUT,
18};
19
20static NEXT_ID: AtomicU64 = AtomicU64::new(1);
21
22/// File IDs for the two virtual input devices.
23const KBD_FILE_ID: u64 = 1;
24const MOUSE_FILE_ID: u64 = 2;
25
26pub struct InputScheme;
27
28impl InputScheme {
29    pub fn new() -> Self {
30        InputScheme
31    }
32}
33
34impl Scheme for InputScheme {
35    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
36        let file_id = match path {
37            "kbd" | "keyboard" => KBD_FILE_ID,
38            "mouse" => MOUSE_FILE_ID,
39            "" | "/" => {
40                return Ok(OpenResult {
41                    file_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
42                    size: None,
43                    flags: FileFlags::DIRECTORY,
44                });
45            }
46            _ => return Err(SyscallError::NotFound),
47        };
48
49        Ok(OpenResult {
50            file_id,
51            size: None,
52            flags: FileFlags::DEVICE,
53        })
54    }
55
56    /// Read raw keyboard scancodes.
57    ///
58    /// Each byte is a PS/2 set-1 scancode. Bit 7 set = key released.
59    /// The Input Server in userspace handles layout translation.
60    fn read(&self, file_id: u64, _offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
61        match file_id {
62            KBD_FILE_ID => {
63                let mut count = 0;
64                for slot in buf.iter_mut() {
65                    match crate::arch::x86_64::keyboard::read_char() {
66                        Some(ch) => {
67                            *slot = ch;
68                            count += 1;
69                        }
70                        None => break,
71                    }
72                }
73                Ok(count)
74            }
75            MOUSE_FILE_ID => {
76                // Each mouse event is 7 bytes: [dx_lo, dx_hi, dy_lo, dy_hi, dz, buttons, 0]
77                let mut count = 0;
78                while count + 7 <= buf.len() {
79                    match crate::arch::x86_64::mouse::read_event() {
80                        Some(ev) => {
81                            buf[count] = ev.dx as u8;
82                            buf[count + 1] = (ev.dx >> 8) as u8;
83                            buf[count + 2] = ev.dy as u8;
84                            buf[count + 3] = (ev.dy >> 8) as u8;
85                            buf[count + 4] = ev.dz as u8;
86                            buf[count + 5] = (ev.left as u8)
87                                | ((ev.right as u8) << 1)
88                                | ((ev.middle as u8) << 2);
89                            buf[count + 6] = 0; // reserved
90                            count += 7;
91                        }
92                        None => break,
93                    }
94                }
95                Ok(count)
96            }
97            _ => Err(SyscallError::BadHandle),
98        }
99    }
100
101    fn write(&self, _file_id: u64, _offset: u64, _buf: &[u8]) -> Result<usize, SyscallError> {
102        Err(SyscallError::PermissionDenied) // Input is read-only
103    }
104
105    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
106        Ok(())
107    }
108
109    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
110        let (mode, size) = match file_id {
111            KBD_FILE_ID => (0o020000 | 0o444, 0u64), // S_IFCHR | r--r--r--
112            MOUSE_FILE_ID => (0o020000 | 0o444, 0u64),
113            _ => (0o040555, 0u64), // directory
114        };
115        Ok(finalize_pseudo_stat(
116            FileStat {
117                st_ino: file_id,
118                st_mode: mode,
119                st_nlink: 1,
120                st_size: size,
121                st_blksize: 1,
122                st_blocks: 0,
123                ..FileStat::zeroed()
124            },
125            DEV_INPUT,
126            0,
127        ))
128    }
129
130    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
131        if file_id != 0 {
132            return Err(SyscallError::InvalidArgument);
133        }
134        Ok(vec![
135            DirEntry {
136                ino: KBD_FILE_ID,
137                file_type: super::scheme::DT_CHR,
138                name: String::from("kbd"),
139            },
140            DirEntry {
141                ino: MOUSE_FILE_ID,
142                file_type: super::scheme::DT_CHR,
143                name: String::from("mouse"),
144            },
145        ])
146    }
147}