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

veridian_kernel/drivers/
terminal.rs

1//! Terminal state tracking for /dev/console and /dev/tty*
2//!
3//! Provides POSIX-compatible terminal attributes (struct termios equivalent)
4//! and window size tracking. The terminal state is queried and modified via
5//! SYS_IOCTL (syscall 112) with TCGETS/TCSETS/TIOCGWINSZ requests.
6//!
7//! The state is used by the console read path to implement canonical vs raw
8//! mode, echo control, and control character handling.
9
10use spin::Mutex;
11
12use crate::sync::once_lock::OnceLock;
13
14// =========================================================================
15// Terminal ioctl request codes (matching Linux values for ABI compatibility)
16// =========================================================================
17
18/// Get terminal attributes (struct termios).
19pub const TCGETS: usize = 0x5401;
20/// Set terminal attributes immediately.
21pub const TCSETS: usize = 0x5402;
22/// Set terminal attributes after draining output.
23pub const TCSETSW: usize = 0x5403;
24/// Set terminal attributes after draining output and flushing input.
25pub const TCSETSF: usize = 0x5404;
26/// Get terminal window size (struct winsize).
27pub const TIOCGWINSZ: usize = 0x5413;
28/// Set terminal window size.
29pub const TIOCSWINSZ: usize = 0x5414;
30/// Get foreground process group ID.
31pub const TIOCGPGRP: usize = 0x540F;
32/// Set foreground process group ID.
33pub const TIOCSPGRP: usize = 0x5410;
34
35// =========================================================================
36// termios flag constants (matching POSIX / Linux values)
37// =========================================================================
38
39// c_iflag bits
40pub const ICRNL: u32 = 0o0000400;
41pub const IXON: u32 = 0o0002000;
42
43// c_oflag bits
44pub const OPOST: u32 = 0o0000001;
45pub const ONLCR: u32 = 0o0000004;
46
47// c_cflag bits
48pub const CS8: u32 = 0o0000060;
49pub const CREAD: u32 = 0o0000200;
50pub const HUPCL: u32 = 0o0002000;
51
52// c_lflag bits
53pub const ISIG: u32 = 0o0000001;
54pub const ICANON: u32 = 0o0000002;
55pub const ECHO: u32 = 0o0000010;
56pub const ECHOE: u32 = 0o0000020;
57pub const ECHOK: u32 = 0o0000040;
58pub const IEXTEN: u32 = 0o0100000;
59
60// c_cc indices
61pub const VINTR: usize = 0;
62pub const VQUIT: usize = 1;
63pub const VERASE: usize = 2;
64pub const VKILL: usize = 3;
65pub const VEOF: usize = 4;
66pub const VTIME: usize = 5;
67pub const VMIN: usize = 6;
68pub const VSTART: usize = 8;
69pub const VSTOP: usize = 9;
70pub const VSUSP: usize = 10;
71
72/// Number of control characters.
73pub const NCCS: usize = 32;
74
75// =========================================================================
76// Terminal state structures (repr(C) to match user-space struct termios)
77// =========================================================================
78
79/// Linux `struct termios` layout (60 bytes).
80///
81/// Field order MUST match the kernel ABI that musl's TCGETS/TCSETS expect:
82///   c_iflag(4), c_oflag(4), c_cflag(4), c_lflag(4), c_line(1),
83///   c_cc[32](32), pad(3), c_ispeed(4), c_ospeed(4).
84/// The `c_line` field is required -- without it, c_cc starts at the wrong
85/// offset and musl reads corrupted control characters.
86#[repr(C)]
87#[derive(Clone, Copy)]
88pub struct KernelTermios {
89    pub c_iflag: u32,
90    pub c_oflag: u32,
91    pub c_cflag: u32,
92    pub c_lflag: u32,
93    pub c_line: u8,
94    pub c_cc: [u8; NCCS],
95    pub c_ispeed: u32,
96    pub c_ospeed: u32,
97}
98
99// Compile-time assertion: Linux struct termios is 60 bytes (with NCCS=32).
100const _: () = assert!(core::mem::size_of::<KernelTermios>() == 60);
101
102impl KernelTermios {
103    /// Create default terminal attributes (cooked mode, echo on).
104    ///
105    /// These match the standard POSIX defaults for a serial console:
106    /// canonical mode, echo enabled, signal processing on.
107    pub const fn default_console() -> Self {
108        let mut cc = [0u8; NCCS];
109        cc[VINTR] = 3; // Ctrl-C
110        cc[VQUIT] = 28; // Ctrl-backslash
111        cc[VERASE] = 127; // DEL
112        cc[VKILL] = 21; // Ctrl-U
113        cc[VEOF] = 4; // Ctrl-D
114        cc[VTIME] = 0;
115        cc[VMIN] = 1;
116        cc[VSTART] = 17; // Ctrl-Q
117        cc[VSTOP] = 19; // Ctrl-S
118        cc[VSUSP] = 26; // Ctrl-Z
119
120        Self {
121            c_iflag: ICRNL | IXON,
122            c_oflag: OPOST | ONLCR,
123            c_cflag: CS8 | CREAD | HUPCL,
124            c_lflag: ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN,
125            c_line: 0, // N_TTY line discipline
126            c_cc: cc,
127            c_ispeed: 38400,
128            c_ospeed: 38400,
129        }
130    }
131
132    /// Check if canonical (line-buffered) mode is enabled.
133    #[inline]
134    pub fn is_canonical(&self) -> bool {
135        self.c_lflag & ICANON != 0
136    }
137
138    /// Check if echo is enabled.
139    #[inline]
140    pub fn is_echo(&self) -> bool {
141        self.c_lflag & ECHO != 0
142    }
143
144    /// Get the VMIN value (minimum characters for non-canonical read).
145    #[inline]
146    pub fn vmin(&self) -> u8 {
147        self.c_cc[VMIN]
148    }
149
150    /// Get the VTIME value (timeout for non-canonical read, in tenths of a
151    /// second).
152    #[inline]
153    pub fn vtime(&self) -> u8 {
154        self.c_cc[VTIME]
155    }
156
157    /// Get the erase character (typically DEL or backspace).
158    #[inline]
159    pub fn verase(&self) -> u8 {
160        self.c_cc[VERASE]
161    }
162
163    /// Get the kill character (typically Ctrl-U).
164    #[inline]
165    pub fn vkill(&self) -> u8 {
166        self.c_cc[VKILL]
167    }
168
169    /// Get the EOF character (typically Ctrl-D).
170    #[inline]
171    pub fn veof(&self) -> u8 {
172        self.c_cc[VEOF]
173    }
174}
175
176/// Terminal window size, matching the C `struct winsize` layout.
177#[repr(C)]
178#[derive(Clone, Copy)]
179pub struct KernelWinsize {
180    pub ws_row: u16,
181    pub ws_col: u16,
182    pub ws_xpixel: u16,
183    pub ws_ypixel: u16,
184}
185
186impl KernelWinsize {
187    /// Default 80x24 terminal (standard VT100 size).
188    pub const fn default_console() -> Self {
189        Self {
190            ws_row: 24,
191            ws_col: 80,
192            ws_xpixel: 0,
193            ws_ypixel: 0,
194        }
195    }
196}
197
198/// Combined terminal state for a single console device.
199pub struct TerminalState {
200    /// Terminal attributes (termios).
201    pub termios: KernelTermios,
202    /// Window size.
203    pub winsize: KernelWinsize,
204}
205
206impl Default for TerminalState {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl TerminalState {
213    /// Create a new terminal state with POSIX defaults.
214    pub const fn new() -> Self {
215        Self {
216            termios: KernelTermios::default_console(),
217            winsize: KernelWinsize::default_console(),
218        }
219    }
220}
221
222// =========================================================================
223// Global terminal state (single console for now)
224// =========================================================================
225
226/// Global terminal state for /dev/console.
227///
228/// Protected by a spin::Mutex since it is accessed from syscall context.
229/// Future: per-device terminal state when multiple TTYs are supported.
230static CONSOLE_TERMINAL: OnceLock<Mutex<TerminalState>> = OnceLock::new();
231
232/// Initialize the global console terminal state.
233///
234/// Called during boot (before any user-space programs run).
235pub fn init() {
236    let _ = CONSOLE_TERMINAL.set(Mutex::new(TerminalState::new()));
237}
238
239/// Get the global console terminal state.
240///
241/// Returns `None` if the terminal subsystem has not been initialized.
242pub fn get_console_terminal() -> Option<&'static Mutex<TerminalState>> {
243    CONSOLE_TERMINAL.get()
244}
245
246// =========================================================================
247// Query helpers for the console read path
248// =========================================================================
249
250/// Check if the console is in canonical (line-buffered) mode.
251///
252/// Returns `true` if ICANON is set (the default). Returns `true` if the
253/// terminal subsystem has not been initialized (conservative default).
254pub fn is_canonical_mode() -> bool {
255    match get_console_terminal() {
256        Some(term) => term.lock().termios.is_canonical(),
257        None => true, // Default to canonical before init
258    }
259}
260
261/// Check if echo is enabled on the console.
262///
263/// Returns `true` if ECHO is set (the default). Returns `true` if the
264/// terminal subsystem has not been initialized.
265pub fn is_echo_enabled() -> bool {
266    match get_console_terminal() {
267        Some(term) => term.lock().termios.is_echo(),
268        None => true,
269    }
270}
271
272/// Get the VMIN value from the console terminal state.
273///
274/// VMIN specifies the minimum number of characters for a non-canonical
275/// read to return. Default is 1.
276pub fn get_vmin() -> u8 {
277    match get_console_terminal() {
278        Some(term) => term.lock().termios.vmin(),
279        None => 1,
280    }
281}
282
283/// Get the VTIME value from the console terminal state.
284///
285/// VTIME specifies the timeout in tenths of a second for non-canonical
286/// read. Default is 0 (no timeout, block until VMIN characters).
287pub fn get_vtime() -> u8 {
288    match get_console_terminal() {
289        Some(term) => term.lock().termios.vtime(),
290        None => 0,
291    }
292}
293
294/// Get the erase character from the console terminal state.
295pub fn get_verase() -> u8 {
296    match get_console_terminal() {
297        Some(term) => term.lock().termios.verase(),
298        None => 127, // DEL
299    }
300}
301
302/// Get a snapshot of the current termios (for the ioctl handler).
303pub fn get_termios_snapshot() -> KernelTermios {
304    match get_console_terminal() {
305        Some(term) => term.lock().termios,
306        None => KernelTermios::default_console(),
307    }
308}
309
310/// Get a snapshot of the current winsize (for the ioctl handler).
311pub fn get_winsize_snapshot() -> KernelWinsize {
312    match get_console_terminal() {
313        Some(term) => term.lock().winsize,
314        None => KernelWinsize::default_console(),
315    }
316}
317
318/// Set the terminal attributes (from TCSETS/TCSETSW/TCSETSF ioctl).
319pub fn set_termios(new_termios: &KernelTermios) {
320    if let Some(term) = get_console_terminal() {
321        term.lock().termios = *new_termios;
322    }
323}
324
325/// Set the window size (from TIOCSWINSZ ioctl).
326pub fn set_winsize(new_ws: &KernelWinsize) {
327    if let Some(term) = get_console_terminal() {
328        term.lock().winsize = *new_ws;
329    }
330}