kernel/abi/linux/device/
tty.rs

1//! Linux-specific ioctl translation for TTY-like devices.
2//!
3//! This module maps Linux ioctls (e.g., termios/keyboard subset) onto Scarlet
4//! TTY control ops exposed via ControlOps on Device-backed file objects.
5
6use crate::device::char::tty::tty_ctl::{
7    SCTL_TTY_GET_KBMODE, SCTL_TTY_GET_WINSIZE, SCTL_TTY_SET_KBMODE, SCTL_TTY_SET_WINSIZE,
8};
9use crate::{
10    device::{DeviceCapability, manager::DeviceManager},
11    fs::FileType,
12    object::KernelObject,
13    task::mytask,
14};
15// errno module isn't public here; use literal -EFAULT encoding where needed.
16
17/// Linux keyboard ioctl command constants (subset)
18pub const KDGKBMODE: u32 = 0x4B44; // Get keyboard mode
19pub const KDGKBTYPE: u32 = 0x4B33; // Get keyboard type
20pub const KB_101: u32 = 0x02; // English 101/102-key keyboard
21pub const KDSKBMODE: u32 = 0x4B45; // Set keyboard mode
22/// Linux VT (virtual terminal) ioctl command constants (subset)
23pub const VT_OPENQRY: u32 = 0x5600; // Find available VT number
24pub const VT_GETMODE: u32 = 0x5601; // Get VT mode
25pub const VT_SETMODE: u32 = 0x5602; // Set VT mode
26pub const VT_GETSTATE: u32 = 0x5603; // Get VT state
27pub const VT_ACTIVATE: u32 = 0x5606; // Make VT active
28pub const VT_WAITACTIVE: u32 = 0x560C; // Wait until VT is active
29
30/// Termios/winsize ioctl constants (subset)
31pub const TIOCGWINSZ: u32 = 0x5413; // Get window size
32pub const TIOCSWINSZ: u32 = 0x5414; // Set window size
33pub const TCGETS: u32 = 0x5401; // Get termios
34pub const TCSETS: u32 = 0x5402; // Set termios (no wait)
35pub const TCSETSW: u32 = 0x5403; // Set termios (drain output)
36pub const TCSETSF: u32 = 0x5404; // Set termios (drain and flush)
37
38/// Linux keyboard mode values (subset)
39pub const K_RAW: u32 = 0x00;
40pub const K_XLATE: u32 = 0x01;
41// Additional keyboard modes sometimes requested by SDL (for example,
42// SDL 1.2 when running on fbcon). Treat these permissively as
43// raw-equivalent modes for compatibility.
44pub const K_MEDIUMRAW: u32 = 0x02;
45pub const K_UNICODE: u32 = 0x03;
46pub const K_OFF: u32 = 0x04;
47
48/// KD text/graphics mode (subset)
49pub const KDSETMODE: u32 = 0x4B3A; // Set text/graphics mode
50pub const KDGETMODE: u32 = 0x4B3B; // Get text/graphics mode
51pub const KD_TEXT: u32 = 0x00;
52pub const KD_GRAPHICS: u32 = 0x01;
53
54#[repr(C)]
55struct Winsize {
56    ws_row: u16,
57    ws_col: u16,
58    ws_xpixel: u16,
59    ws_ypixel: u16,
60}
61
62/// Minimal Linux termios (asm-generic) layout for TCGETS.
63/// This mirrors asm-generic: 4x tcflag_t (u32), 1x cc line (u8),
64/// c_cc[19] (u8), and ispeed/ospeed (u32 each).
65#[repr(C)]
66struct LinuxTermios {
67    c_iflag: u32,
68    c_oflag: u32,
69    c_cflag: u32,
70    c_lflag: u32,
71    c_line: u8,
72    c_cc: [u8; 19],
73    c_ispeed: u32,
74    c_ospeed: u32,
75}
76
77// Common termios cc index constants (asm-generic)
78const VEOF: usize = 4;
79const VTIME: usize = 5;
80const VMIN: usize = 6;
81
82/// Handle Linux TTY-related ioctls for a given kernel object representing an
83/// open file descriptor. Returns Ok(Some(ret)) if handled, Ok(None) if not
84/// applicable, and Err(()) on error (mapped to -1 by caller).
85pub fn handle_ioctl(
86    request: u32,
87    arg: usize,
88    kernel_object: &KernelObject,
89) -> Result<Option<usize>, ()> {
90    use crate::device::char::tty::tty_ctl::{
91        SCTL_TTY_GET_CANONICAL, SCTL_TTY_GET_ECHO, SCTL_TTY_GET_READ_POLICY,
92        SCTL_TTY_SET_CANONICAL, SCTL_TTY_SET_ECHO, SCTL_TTY_SET_READ_POLICY,
93    };
94
95    const LOG_TTY_IOCTL: bool = false;
96    match request {
97        KDGKBTYPE => {
98            // Always return KB_101
99            let task = mytask().ok_or(())?;
100            let vaddr = arg as usize;
101            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
102                unsafe { *(paddr as *mut u32) = KB_101; }
103                Ok(Some(0))
104            } else {
105                Ok(Some((-14_isize) as usize))
106            }
107        }
108        // Minimal termios support with Scarlet TTY mapping (canonical + read policy)
109        TCGETS | TCSETS | TCSETSW | TCSETSF => {
110            if LOG_TTY_IOCTL { crate::println!("[tty_ioctl] termios op {:#06x}", request); }
111            // Validate the FD is a char device with TTY capability
112            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
113                if let Ok(metadata) = file_obj.metadata() {
114                    if let FileType::CharDevice(info) = metadata.file_type {
115                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
116                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
117                        } else { false }
118                    } else { false }
119                } else { false }
120            } else { false };
121            if !is_tty { return Err(()); }
122
123            match request {
124                TCGETS => {
125                    let mut t = LinuxTermios {
126                        c_iflag: 0,
127                        c_oflag: 0,
128                        c_cflag: 0,
129                        c_lflag: 0,
130                        c_line: 0,
131                        c_cc: [0; 19],
132                        c_ispeed: 0,
133                        c_ospeed: 0,
134                    };
135                    let mut canonical = false;
136                    let mut echo = true;
137                    let mut min_ready: u16 = 1;
138                    let mut timeout_ms: u16 = 0;
139                    if let Some(control_ops) = kernel_object.as_control() {
140                        if let Ok(val) = control_ops.control(SCTL_TTY_GET_CANONICAL, 0) { canonical = val != 0; }
141                        if let Ok(val) = control_ops.control(SCTL_TTY_GET_ECHO, 0) { echo = val != 0; }
142                        if let Ok(packed) = control_ops.control(SCTL_TTY_GET_READ_POLICY, 0) {
143                            let packed_u = packed as u32;
144                            min_ready = (packed_u & 0xFFFF) as u16;
145                            timeout_ms = ((packed_u >> 16) & 0xFFFF) as u16;
146                        }
147                    }
148                    if canonical { t.c_lflag |= 0x0000_0002; }
149                    if echo { t.c_lflag |= 0x0000_0008; }
150                    t.c_cc[VMIN] = core::cmp::min(min_ready as usize, 255) as u8;
151                    let vtime_tenths = core::cmp::min(((timeout_ms as u32 + 99) / 100) as usize, 255) as u8;
152                    t.c_cc[VTIME] = vtime_tenths;
153                    t.c_cc[VEOF] = 0x04; // Ctrl-D
154                    let task = mytask().ok_or(())?;
155                    let vaddr = arg as usize;
156                    if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
157                        unsafe { core::ptr::write(paddr as *mut LinuxTermios, t); }
158                        Ok(Some(0))
159                    } else { Err(()) }
160                }
161                _ => {
162                    let task = mytask().ok_or(())?;
163                    let vaddr = arg as usize;
164                    if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
165                        let t = unsafe { core::ptr::read(paddr as *const LinuxTermios) };
166                        let canonical_new = (t.c_lflag & 0x0000_0002) != 0;
167                        let echo_new = (t.c_lflag & 0x0000_0008) != 0;
168                        let vmin = t.c_cc[VMIN] as u16;
169                        let vtime_tenths = t.c_cc[VTIME] as u16;
170                        let timeout_ms: u16 = vtime_tenths.saturating_mul(100);
171                        if let Some(control_ops) = kernel_object.as_control() {
172                            let prev_canonical = control_ops.control(SCTL_TTY_GET_CANONICAL, 0).unwrap_or(-1) != 0;
173                            let prev_echo = control_ops.control(SCTL_TTY_GET_ECHO, 0).unwrap_or(-1) != 0;
174                            let _ = control_ops.control(SCTL_TTY_SET_CANONICAL, if canonical_new { 1 } else { 0 });
175                            let _ = control_ops.control(SCTL_TTY_SET_ECHO, if echo_new { 1 } else { 0 });
176                            let packed = ((timeout_ms as u32) << 16) | (vmin as u32);
177                            let _ = control_ops.control(SCTL_TTY_SET_READ_POLICY, packed as usize);
178                            if LOG_TTY_IOCTL && (prev_canonical != canonical_new || prev_echo != echo_new) {
179                                crate::println!("[linux TTY] termios change: canonical={} echo={} vmin={} timeout_ms={}",
180                                    canonical_new, echo_new, vmin, timeout_ms);
181                            }
182                        }
183                        Ok(Some(0))
184                    } else { Err(()) }
185                }
186            }
187        }
188        // Virtual terminal: get current VT state
189        VT_GETSTATE => {
190            // Structure per linux/vt.h
191            #[repr(C)]
192            struct VtStat { v_active: u16, v_signal: u16, v_state: u16 }
193            let task = mytask().ok_or(())?;
194            let vaddr = arg as usize;
195            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
196                let state = VtStat { v_active: 1, v_signal: 0, v_state: 1 << 1 };
197                unsafe { core::ptr::write(paddr as *mut VtStat, state); }
198                Ok(Some(0))
199            } else {
200                Ok(Some((-14_isize) as usize))
201            }
202        }
203        // Virtual terminal: return a plausible available VT number (e.g., 1)
204        VT_OPENQRY => {
205            // Must be a TTY-capable device
206            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
207                if let Ok(metadata) = file_obj.metadata() {
208                    if let FileType::CharDevice(info) = metadata.file_type {
209                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id)
210                        {
211                            dev.capabilities()
212                                .iter()
213                                .any(|c| *c == DeviceCapability::Tty)
214                        } else {
215                            false
216                        }
217                    } else {
218                        false
219                    }
220                } else {
221                    false
222                }
223            } else {
224                false
225            };
226            if !is_tty {
227                return Err(());
228            }
229
230            // Write VT number into user pointer
231            let task = mytask().ok_or(())?;
232            let vaddr = arg as usize;
233            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
234                unsafe { *(paddr as *mut i32) = 1; }
235                Ok(Some(0))
236            } else {
237                Ok(Some((-14_isize) as usize))
238            }
239        }
240
241        // Virtual terminal: get mode (stub values)
242        VT_GETMODE => {
243            // Validate TTY FD
244            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
245                if let Ok(metadata) = file_obj.metadata() {
246                    if let FileType::CharDevice(info) = metadata.file_type {
247                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
248                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
249                        } else { false }
250                    } else { false }
251                } else { false }
252            } else { false };
253            if !is_tty { return Err(()); }
254
255            // Linux struct vt_mode has several fields; we write zeros as a benign default
256            let task = mytask().ok_or(())?;
257            let vaddr = arg as usize;
258            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
259                // Write 8 bytes of zeros (enough for minimal fields on common ABIs)
260                unsafe { core::ptr::write_bytes(paddr as *mut u8, 0, 8); }
261                Ok(Some(0))
262            } else {
263                Ok(Some((-14_isize) as usize))
264            }
265        }
266
267        // Virtual terminal: set mode (accept as no-op)
268        VT_SETMODE => {
269            // Validate TTY FD
270            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
271                if let Ok(metadata) = file_obj.metadata() {
272                    if let FileType::CharDevice(info) = metadata.file_type {
273                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
274                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
275                        } else { false }
276                    } else { false }
277                } else { false }
278            } else { false };
279            if !is_tty { return Err(()); }
280            Ok(Some(0))
281        }
282
283        // Activate a VT number (stub: accept VT 1)
284        VT_ACTIVATE => {
285            // Validate TTY FD
286            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
287                if let Ok(metadata) = file_obj.metadata() {
288                    if let FileType::CharDevice(info) = metadata.file_type {
289                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
290                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
291                        } else { false }
292                    } else { false }
293                } else { false }
294            } else { false };
295            if !is_tty { return Err(()); }
296
297            // Linux expects arg to be VT number; we accept 1..=1 for now
298            let vtno = arg as u32;
299            if vtno == 1 { Ok(Some(0)) } else { Ok(Some(0)) } // be permissive
300        }
301
302        // Wait until the specified VT becomes active (stub: succeed immediately)
303        0x5607 => { // VT_WAITACTIVE
304            // Validate TTY FD
305            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
306                if let Ok(metadata) = file_obj.metadata() {
307                    if let FileType::CharDevice(info) = metadata.file_type {
308                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
309                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
310                        } else { false }
311                    } else { false }
312                } else { false }
313            } else { false };
314            if !is_tty { return Err(()); }
315
316            Ok(Some(0))
317        }
318
319        // Unlock VT switching (stub: succeed)
320        0x560C => { // VT_UNLOCKSWITCH
321            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
322                if let Ok(metadata) = file_obj.metadata() {
323                    if let FileType::CharDevice(info) = metadata.file_type {
324                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
325                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
326                        } else { false }
327                    } else { false }
328                } else { false }
329            } else { false };
330            if !is_tty { return Err(()); }
331            Ok(Some(0))
332        }
333
334        // Get window size (rows/cols). Fall back to 80x25 if unavailable.
335        TIOCGWINSZ => {
336            if LOG_TTY_IOCTL { crate::println!("[tty_ioctl] TIOCGWINSZ"); }
337            if !is_tty_kernel_object(kernel_object) { return Err(()); }
338
339            let task = mytask().ok_or(())?;
340            let vaddr = arg as usize;
341            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
342                let (cols, rows) = if let Some(control_ops) = kernel_object.as_control() {
343                    match control_ops.control(SCTL_TTY_GET_WINSIZE, 0) {
344                        Ok(packed) => {
345                            let packed_u = packed as u32;
346                            let cols = (packed_u >> 16) as u16;
347                            let rows = (packed_u & 0xFFFF) as u16;
348                            (cols, rows)
349                        }
350                        Err(_) => (80, 25),
351                    }
352                } else {
353                    (80, 25)
354                };
355
356                let ws = Winsize { ws_row: rows, ws_col: cols, ws_xpixel: 0, ws_ypixel: 0 };
357                unsafe { core::ptr::write(paddr as *mut Winsize, ws); }
358                Ok(Some(0))
359            } else {
360                Ok(Some((-14_isize) as usize))
361            }
362        }
363
364        // Set window size from Linux winsize structure.
365        TIOCSWINSZ => {
366            if LOG_TTY_IOCTL { crate::println!("[tty_ioctl] TIOCSWINSZ"); }
367            if !is_tty_kernel_object(kernel_object) { return Err(()); }
368
369            let task = mytask().ok_or(())?;
370            let vaddr = arg as usize;
371            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
372                let ws = unsafe { core::ptr::read(paddr as *const Winsize) };
373                if let Some(control_ops) = kernel_object.as_control() {
374                    let packed = ((ws.ws_col as usize) << 16) | (ws.ws_row as usize);
375                    control_ops.control(SCTL_TTY_SET_WINSIZE, packed).map_err(|_| ())?;
376                } else {
377                    return Err(());
378                }
379                Ok(Some(0))
380            } else {
381                Ok(Some((-14_isize) as usize))
382            }
383        }
384
385        // Text/graphics mode stubs: accept and report KD_TEXT as default
386        KDGETMODE => {
387            if LOG_TTY_IOCTL { crate::println!("[tty_ioctl] KDGETMODE"); }
388            let task = mytask().ok_or(())?;
389            let vaddr = arg as usize;
390            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
391                unsafe { *(paddr as *mut u32) = KD_TEXT; }
392                Ok(Some(0))
393            } else {
394                Ok(Some((-14_isize) as usize))
395            }
396        }
397        KDSETMODE => {
398            if LOG_TTY_IOCTL { crate::println!("[tty_ioctl] KDSETMODE mode={:#x}", arg as u32); }
399            // Accept KD_TEXT/KD_GRAPHICS, no-op for now
400            let mode = arg as u32;
401            match mode {
402                KD_TEXT | KD_GRAPHICS => Ok(Some(0)),
403                _ => Err(()),
404            }
405        }
406
407        KDGKBMODE | KDSKBMODE => {
408            if LOG_TTY_IOCTL {
409                if request == KDGKBMODE { crate::println!("[tty_ioctl] KDGKBMODE"); } else { crate::println!("[tty_ioctl] KDSKBMODE -> {:#x}", arg as u32); }
410            }
411            // Validate the FD is a char device with TTY capability
412            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
413                if let Ok(metadata) = file_obj.metadata() {
414                    if let FileType::CharDevice(info) = metadata.file_type {
415                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id)
416                        {
417                            dev.capabilities()
418                                .iter()
419                                .any(|c| *c == DeviceCapability::Tty)
420                        } else {
421                            false
422                        }
423                    } else {
424                        false
425                    }
426                } else {
427                    false
428                }
429            } else {
430                false
431            };
432            if !is_tty {
433                return Err(());
434            }
435
436            // Must have ControlOps capability to talk to device
437            let control_ops = kernel_object.as_control().ok_or(())?;
438
439            if request == KDGKBMODE {
440                // Query Scarlet kb_mode and map to Linux value
441                match control_ops.control(SCTL_TTY_GET_KBMODE, 0) {
442                    Ok(v) => {
443                        let task = mytask().ok_or(())?;
444                        let mode: u32 = match v as u32 { 0 => K_XLATE, 1 => K_MEDIUMRAW, 2 => K_RAW, _ => K_RAW };
445                        let vaddr = arg as usize;
446                        if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
447                            unsafe { *(paddr as *mut u32) = mode; }
448                            Ok(Some(0))
449                        } else { Ok(Some((-14_isize) as usize)) }
450                    }
451                    Err(_) => Err(()),
452                }
453            } else {
454                // KDSKBMODE: arg holds the mode value directly
455                let mode = arg as u32;
456                // Be permissive: treat any non-XLATE mode as non-canonical ("raw-ish").
457                // This covers K_RAW, K_MEDIUMRAW, K_UNICODE, K_OFF commonly used by SDL.
458                let enable_canonical = match mode {
459                    K_XLATE => true,
460                    K_RAW | K_MEDIUMRAW | K_UNICODE | K_OFF => false,
461                    _ => false, // Unknown modes: accept but consider non-canonical
462                };
463                let arg_bool = if enable_canonical { 1usize } else { 0usize };
464                let prev_canonical = control_ops.control(SCTL_TTY_GET_CANONICAL, 0).unwrap_or(-1) != 0;
465                match control_ops.control(SCTL_TTY_SET_CANONICAL, arg_bool) {
466                    Ok(_) => {
467                        let new_canonical = enable_canonical;
468                        if LOG_TTY_IOCTL && prev_canonical != new_canonical {
469                            crate::println!("[linux TTY] keyboard mode -> canonical={}", new_canonical);
470                        }
471                        // Also set Scarlet kb_mode for read() semantics
472                        let kb = match mode { K_XLATE => 0usize, K_MEDIUMRAW => 1usize, K_RAW | K_UNICODE | K_OFF => 2usize, _ => 2usize };
473                        let _ = control_ops.control(SCTL_TTY_SET_KBMODE, kb);
474                        Ok(Some(0))
475                    },
476                    Err(_) => Err(()),
477                }
478            }
479        }
480        // Keyboard map entry get/set (subset)
481        0x4B46 /* KDGKBENT */ => {
482            #[repr(C)]
483            struct KbEntry { kb_table: u8, kb_index: u8, kb_value: u16 }
484
485            if !is_tty_kernel_object(kernel_object) { return Err(()); }
486
487            fn us_kmap(norm: bool, idx: usize) -> u16 {
488                // Linux console keymap encoding: K(t,v) = ((t<<8)|v)
489                const KT_FN: u16 = 1;
490                const KT_CUR: u16 = 6;
491                // Cursor keys
492                const K_DOWN: u16 = (KT_CUR << 8) | 0; // 0x0600
493                const K_LEFT: u16 = (KT_CUR << 8) | 1; // 0x0601
494                const K_RIGHT: u16 = (KT_CUR << 8) | 2; // 0x0602
495                const K_UP: u16 = (KT_CUR << 8) | 3; // 0x0603
496                // Function-like navigation
497                const K_FIND: u16 = (KT_FN << 8) | 20;   // Home
498                const K_INSERT: u16 = (KT_FN << 8) | 21; // Insert
499                const K_REMOVE: u16 = (KT_FN << 8) | 22; // Delete
500                const K_SELECT: u16 = (KT_FN << 8) | 23; // End
501                const K_PGUP: u16 = (KT_FN << 8) | 24;   // PageUp
502                const K_PGDN: u16 = (KT_FN << 8) | 25;   // PageDown
503                let ascii_passthrough = |i: usize| -> u16 {
504                    if (b'a' as usize..=b'z' as usize).contains(&i)
505                        || (b'0' as usize..=b'9' as usize).contains(&i)
506                        || i == b' ' as usize || i == b'\t' as usize || i == b'\n' as usize
507                        || [b'-', b'=', b'[', b']', b'\\', b';', b'\'', b'`', b',', b'.', b'/'].iter().any(|c| *c as usize == i)
508                    { i as u16 } else { 0 }
509                };
510                let ascii_shift = |i: usize| -> u16 {
511                    match i as u8 {
512                        b'a'..=b'z' => (i as u8).to_ascii_uppercase() as u16,
513                        b'1' => b'!' as u16, b'2' => b'@' as u16, b'3' => b'#' as u16,
514                        b'4' => b'$' as u16, b'5' => b'%' as u16, b'6' => b'^' as u16,
515                        b'7' => b'&' as u16, b'8' => b'*' as u16, b'9' => b'(' as u16,
516                        b'0' => b')' as u16,
517                        b'-' => b'_' as u16, b'=' => b'+' as u16,
518                        b'[' => b'{' as u16, b']' => b'}' as u16, b'\\' => b'|' as u16,
519                        b';' => b':' as u16, b'\'' => b'"' as u16, b'`' => b'~' as u16,
520                        b',' => b'<' as u16, b'.' => b'>' as u16, b'/' => b'?' as u16,
521                        other => other as u16,
522                    }
523                };
524                match (norm, idx) {
525                    // Special keys by Linux keycode index (from input-event-codes):
526                    // 102=HOME, 103=UP, 104=PGUP, 105=LEFT, 106=RIGHT, 107=END, 108=DOWN, 109=PGDN, 110=INSERT, 111=DELETE
527                    // Map to Linux console keysyms so SDL can resolve keysym.
528                    (_, 103) => K_UP,
529                    (_, 108) => K_DOWN,
530                    (_, 105) => K_LEFT,
531                    (_, 106) => K_RIGHT,
532                    (_, 102) => K_FIND,   // Home
533                    (_, 107) => K_SELECT, // End
534                    (_, 104) => K_PGUP,
535                    (_, 109) => K_PGDN,
536                    (_, 110) => K_INSERT,
537                    (_, 111) => K_REMOVE,
538                    (true, 2) => b'1' as u16, (true, 3) => b'2' as u16, (true, 4) => b'3' as u16,
539                    (true, 5) => b'4' as u16, (true, 6) => b'5' as u16, (true, 7) => b'6' as u16,
540                    (true, 8) => b'7' as u16, (true, 9) => b'8' as u16, (true, 10) => b'9' as u16,
541                    (true, 11) => b'0' as u16, (true, 12) => b'-' as u16, (true, 13) => b'=' as u16,
542                    (true, 15) => b'\t' as u16, (true, 28) => b'\n' as u16, (true, 57) => b' ' as u16,
543                    (true, 16) => b'q' as u16, (true, 17) => b'w' as u16, (true, 18) => b'e' as u16,
544                    (true, 19) => b'r' as u16, (true, 20) => b't' as u16, (true, 21) => b'y' as u16,
545                    (true, 22) => b'u' as u16, (true, 23) => b'i' as u16, (true, 24) => b'o' as u16,
546                    (true, 25) => b'p' as u16, (true, 26) => b'[' as u16, (true, 27) => b']' as u16,
547                    (true, 30) => b'a' as u16, (true, 31) => b's' as u16, (true, 32) => b'd' as u16,
548                    (true, 33) => b'f' as u16, (true, 34) => b'g' as u16, (true, 35) => b'h' as u16,
549                    (true, 36) => b'j' as u16, (true, 37) => b'k' as u16, (true, 38) => b'l' as u16,
550                    (true, 39) => b';' as u16, (true, 40) => b'\'' as u16, (true, 41) => b'`' as u16,
551                    (true, 44) => b'z' as u16, (true, 45) => b'x' as u16, (true, 46) => b'c' as u16,
552                    (true, 47) => b'v' as u16, (true, 48) => b'b' as u16, (true, 49) => b'n' as u16,
553                    (true, 50) => b'm' as u16, (true, 51) => b',' as u16, (true, 52) => b'.' as u16,
554                    (true, 53) => b'/' as u16,
555                    (false, 2) => b'!' as u16, (false, 3) => b'@' as u16, (false, 4) => b'#' as u16,
556                    (false, 5) => b'$' as u16, (false, 6) => b'%' as u16, (false, 7) => b'^' as u16,
557                    (false, 8) => b'&' as u16, (false, 9) => b'*' as u16, (false, 10) => b'(' as u16,
558                    (false, 11) => b')' as u16, (false, 12) => b'_' as u16, (false, 13) => b'+' as u16,
559                    (false, 15) => b'\t' as u16, (false, 28) => b'\n' as u16, (false, 57) => b' ' as u16,
560                    (false, 16) => b'Q' as u16, (false, 17) => b'W' as u16, (false, 18) => b'E' as u16,
561                    (false, 19) => b'R' as u16, (false, 20) => b'T' as u16, (false, 21) => b'Y' as u16,
562                    (false, 22) => b'U' as u16, (false, 23) => b'I' as u16, (false, 24) => b'O' as u16,
563                    (false, 25) => b'P' as u16, (false, 26) => b'{' as u16, (false, 27) => b'}' as u16,
564                    (false, 30) => b'A' as u16, (false, 31) => b'S' as u16, (false, 32) => b'D' as u16,
565                    (false, 33) => b'F' as u16, (false, 34) => b'G' as u16, (false, 35) => b'H' as u16,
566                    (false, 36) => b'J' as u16, (false, 37) => b'K' as u16, (false, 38) => b'L' as u16,
567                    (false, 39) => b':' as u16, (false, 40) => b'"' as u16, (false, 41) => b'~' as u16,
568                    (false, 44) => b'Z' as u16, (false, 45) => b'X' as u16, (false, 46) => b'C' as u16,
569                    (false, 47) => b'V' as u16, (false, 48) => b'B' as u16, (false, 49) => b'N' as u16,
570                    (false, 50) => b'M' as u16, (false, 51) => b'<' as u16, (false, 52) => b'>' as u16,
571                    (false, 53) => b'?' as u16,
572                    _ => {
573                        // Fallback: if SDL is passing ASCII in scancode, map it directly
574                        if norm { ascii_passthrough(idx) } else { ascii_shift(idx) }
575                    },
576                }
577            }
578
579            let task = mytask().ok_or(())?;
580            let vaddr = arg as usize;
581            if let Some(paddr) = task.vm_manager.translate_vaddr(vaddr) {
582                unsafe {
583                    let mut e = core::ptr::read(paddr as *const KbEntry);
584                    let idx = e.kb_index as usize;
585                    let val = match e.kb_table { 0 => us_kmap(true, idx), 1 => us_kmap(false, idx), _ => 0 };
586                    e.kb_value = val;
587                    core::ptr::write(paddr as *mut KbEntry, e);
588                }
589                Ok(Some(0))
590            } else {
591                Ok(Some((-14_isize) as usize))
592            }
593        }
594        0x4B47 /* KDSKBENT */ => {
595            crate::println!("[tty_ioctl] KDSKBENT");
596            // Accept and ignore (no-op)
597            let is_tty = if let Some(file_obj) = kernel_object.as_file() {
598                if let Ok(metadata) = file_obj.metadata() {
599                    if let FileType::CharDevice(info) = metadata.file_type {
600                        if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
601                            dev.capabilities().iter().any(|c| *c == DeviceCapability::Tty)
602                        } else { false }
603                    } else { false }
604                } else { false }
605            } else { false };
606            if !is_tty { return Err(()); }
607            Ok(Some(0))
608        }
609        _ => Ok(None),
610    }
611}
612
613fn is_tty_kernel_object(kernel_object: &KernelObject) -> bool {
614    if let Some(file_obj) = kernel_object.as_file() {
615        if let Ok(metadata) = file_obj.metadata() {
616            if let FileType::CharDevice(info) = metadata.file_type {
617                if let Some(dev) = DeviceManager::get_manager().get_device(info.device_id) {
618                    return dev
619                        .capabilities()
620                        .iter()
621                        .any(|c| *c == DeviceCapability::Tty);
622                }
623            }
624        }
625    }
626    false
627}