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

veridian_kernel/drivers/
console.rs

1//! Console Device Drivers
2//!
3//! Implements console drivers for VGA text mode and serial console.
4
5// Console drivers
6
7use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
8
9use spin::Mutex;
10
11use crate::{
12    error::KernelError,
13    services::driver_framework::{DeviceClass, DeviceInfo, Driver},
14    sync::once_lock::OnceLock,
15};
16
17/// Console colors (VGA text mode)
18#[repr(u8)]
19pub enum ConsoleColor {
20    Black = 0,
21    Blue = 1,
22    Green = 2,
23    Cyan = 3,
24    Red = 4,
25    Magenta = 5,
26    Brown = 6,
27    LightGray = 7,
28    DarkGray = 8,
29    LightBlue = 9,
30    LightGreen = 10,
31    LightCyan = 11,
32    LightRed = 12,
33    Pink = 13,
34    Yellow = 14,
35    White = 15,
36}
37
38/// Console character with color attributes
39#[derive(Debug, Clone, Copy)]
40#[repr(C)]
41pub struct ConsoleChar {
42    pub ascii: u8,
43    pub color: u8,
44}
45
46impl ConsoleChar {
47    pub fn new(ascii: u8, foreground: ConsoleColor, background: ConsoleColor) -> Self {
48        Self {
49            ascii,
50            color: ((background as u8) << 4) | (foreground as u8),
51        }
52    }
53}
54
55/// Console device trait
56pub trait ConsoleDevice: Send + Sync {
57    /// Get console name
58    fn name(&self) -> &str;
59
60    /// Get console dimensions
61    fn dimensions(&self) -> (usize, usize); // (width, height)
62
63    /// Clear the screen
64    fn clear(&mut self) -> Result<(), KernelError>;
65
66    /// Write a character at position
67    fn write_char(&mut self, x: usize, y: usize, ch: ConsoleChar) -> Result<(), KernelError>;
68
69    /// Write a string at position
70    fn write_string(&mut self, x: usize, y: usize, s: &str, color: u8) -> Result<(), KernelError>;
71
72    /// Scroll up by one line
73    fn scroll_up(&mut self) -> Result<(), KernelError>;
74
75    /// Set cursor position
76    fn set_cursor(&mut self, x: usize, y: usize) -> Result<(), KernelError>;
77
78    /// Get cursor position
79    fn get_cursor(&self) -> (usize, usize);
80
81    /// Show/hide cursor
82    fn set_cursor_visible(&mut self, visible: bool) -> Result<(), KernelError>;
83}
84
85/// VGA text mode console driver
86pub struct VgaConsole {
87    buffer: *mut ConsoleChar,
88    width: usize,
89    height: usize,
90    cursor_x: usize,
91    cursor_y: usize,
92    cursor_visible: bool,
93    #[allow(dead_code)] // VGA attribute byte for text mode rendering
94    default_color: u8,
95}
96
97// SAFETY: VgaConsole is safe to send between threads as the buffer
98// is a fixed hardware address and all methods requiring mutation take &mut self
99unsafe impl Send for VgaConsole {}
100
101// SAFETY: VgaConsole is safe to share between threads as the buffer
102// is a fixed hardware address and mutation is protected by &mut self
103unsafe impl Sync for VgaConsole {}
104
105impl Default for VgaConsole {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl VgaConsole {
112    /// Create a new VGA console
113    pub fn new() -> Self {
114        Self {
115            buffer: 0xB8000 as *mut ConsoleChar,
116            width: 80,
117            height: 25,
118            cursor_x: 0,
119            cursor_y: 0,
120            cursor_visible: true,
121            default_color: ((ConsoleColor::Black as u8) << 4) | (ConsoleColor::LightGray as u8),
122        }
123    }
124
125    /// Get buffer index for position
126    fn buffer_index(&self, x: usize, y: usize) -> usize {
127        y * self.width + x
128    }
129
130    /// Update hardware cursor
131    fn update_cursor(&self) {
132        let pos = self.cursor_y * self.width + self.cursor_x;
133
134        // SAFETY: I/O port writes to the VGA CRT controller (0x3D4/0x3D5)
135        // are standard VGA cursor position updates. We are in kernel
136        // mode with I/O privileges. These ports are always safe to access.
137        unsafe {
138            // Cursor low byte
139            crate::arch::outb(0x3D4, 0x0F);
140            crate::arch::outb(0x3D5, (pos & 0xFF) as u8);
141
142            // Cursor high byte
143            crate::arch::outb(0x3D4, 0x0E);
144            crate::arch::outb(0x3D5, ((pos >> 8) & 0xFF) as u8);
145        }
146    }
147}
148
149impl ConsoleDevice for VgaConsole {
150    fn name(&self) -> &str {
151        "vga"
152    }
153
154    fn dimensions(&self) -> (usize, usize) {
155        (self.width, self.height)
156    }
157
158    fn clear(&mut self) -> Result<(), KernelError> {
159        let blank = ConsoleChar::new(b' ', ConsoleColor::LightGray, ConsoleColor::Black);
160
161        // SAFETY: self.buffer points to the VGA text buffer at 0xB8000,
162        // which is width * height ConsoleChar entries (80*25 = 2000).
163        // We write exactly that many entries, staying within bounds.
164        unsafe {
165            for i in 0..(self.width * self.height) {
166                *self.buffer.add(i) = blank;
167            }
168        }
169
170        self.cursor_x = 0;
171        self.cursor_y = 0;
172        self.update_cursor();
173
174        Ok(())
175    }
176
177    fn write_char(&mut self, x: usize, y: usize, ch: ConsoleChar) -> Result<(), KernelError> {
178        if x >= self.width || y >= self.height {
179            return Err(KernelError::InvalidArgument {
180                name: "position",
181                value: "out of bounds",
182            });
183        }
184
185        let index = self.buffer_index(x, y);
186        // SAFETY: Bounds are checked above (x < width, y < height),
187        // so index is within the VGA buffer at 0xB8000.
188        unsafe {
189            *self.buffer.add(index) = ch;
190        }
191
192        Ok(())
193    }
194
195    fn write_string(&mut self, x: usize, y: usize, s: &str, color: u8) -> Result<(), KernelError> {
196        let mut pos_x = x;
197        let pos_y = y;
198
199        if pos_y >= self.height {
200            return Err(KernelError::InvalidArgument {
201                name: "y_position",
202                value: "out of bounds",
203            });
204        }
205
206        for byte in s.bytes() {
207            if pos_x >= self.width {
208                break; // Don't wrap lines
209            }
210
211            let ch = ConsoleChar { ascii: byte, color };
212            self.write_char(pos_x, pos_y, ch)?;
213            pos_x += 1;
214        }
215
216        Ok(())
217    }
218
219    fn scroll_up(&mut self) -> Result<(), KernelError> {
220        // SAFETY: All buffer accesses use buffer_index(x, y) where
221        // x < self.width and y < self.height. The VGA buffer at
222        // 0xB8000 is 80*25 = 2000 ConsoleChar entries. Moving lines
223        // and clearing the last line stays within bounds.
224        unsafe {
225            // Move all lines up by one
226            for y in 1..self.height {
227                for x in 0..self.width {
228                    let src_index = self.buffer_index(x, y);
229                    let dst_index = self.buffer_index(x, y - 1);
230                    *self.buffer.add(dst_index) = *self.buffer.add(src_index);
231                }
232            }
233
234            // Clear the last line
235            let blank = ConsoleChar::new(b' ', ConsoleColor::LightGray, ConsoleColor::Black);
236            for x in 0..self.width {
237                let index = self.buffer_index(x, self.height - 1);
238                *self.buffer.add(index) = blank;
239            }
240        }
241
242        Ok(())
243    }
244
245    fn set_cursor(&mut self, x: usize, y: usize) -> Result<(), KernelError> {
246        if x >= self.width || y >= self.height {
247            return Err(KernelError::InvalidArgument {
248                name: "cursor_position",
249                value: "out of bounds",
250            });
251        }
252
253        self.cursor_x = x;
254        self.cursor_y = y;
255        self.update_cursor();
256
257        Ok(())
258    }
259
260    fn get_cursor(&self) -> (usize, usize) {
261        (self.cursor_x, self.cursor_y)
262    }
263
264    fn set_cursor_visible(&mut self, visible: bool) -> Result<(), KernelError> {
265        self.cursor_visible = visible;
266
267        // SAFETY: I/O port writes to VGA CRT controller (0x3D4/0x3D5)
268        // for cursor shape control. Standard VGA register access.
269        unsafe {
270            // Set cursor shape
271            crate::arch::outb(0x3D4, 0x0A);
272            if visible {
273                crate::arch::outb(0x3D5, 0x0E); // Cursor on
274            } else {
275                crate::arch::outb(0x3D5, 0x20); // Cursor off
276            }
277        }
278
279        Ok(())
280    }
281}
282
283/// Serial console driver
284pub struct SerialConsole {
285    port: u16,
286    name: String,
287    cursor_x: usize,
288    cursor_y: usize,
289    width: usize,
290    height: usize,
291}
292
293impl SerialConsole {
294    /// Create a new serial console
295    pub fn new(port: u16) -> Self {
296        let mut console = Self {
297            port,
298            name: format!(
299                "serial{}",
300                match port {
301                    0x3F8 => 0, // COM1
302                    0x2F8 => 1, // COM2
303                    0x3E8 => 2, // COM3
304                    0x2E8 => 3, // COM4
305                    _ => 9,
306                }
307            ),
308            cursor_x: 0,
309            cursor_y: 0,
310            width: 80,
311            height: 25,
312        };
313
314        console.init();
315        console
316    }
317
318    /// Initialize serial port
319    fn init(&mut self) {
320        // SAFETY: Standard 16550 UART initialization sequence via I/O
321        // ports. We are in kernel mode with I/O privileges. The port
322        // base address (self.port) is set at construction to a valid
323        // COM port (0x3F8, 0x2F8, 0x3E8, or 0x2E8).
324        unsafe {
325            // Disable interrupts
326            crate::arch::outb(self.port + 1, 0x00);
327
328            // Enable DLAB (set baud rate divisor)
329            crate::arch::outb(self.port + 3, 0x80);
330
331            // Set divisor to 3 (38400 baud)
332            crate::arch::outb(self.port, 0x03);
333            crate::arch::outb(self.port + 1, 0x00);
334
335            // 8 bits, no parity, one stop bit
336            crate::arch::outb(self.port + 3, 0x03);
337
338            // Enable FIFO, clear them, with 14-byte threshold
339            crate::arch::outb(self.port + 2, 0xC7);
340
341            // IRQs enabled, RTS/DSR set
342            crate::arch::outb(self.port + 4, 0x0B);
343        }
344    }
345
346    /// Write a byte to serial port
347    fn write_byte(&self, byte: u8) {
348        // SAFETY: Reading the line status register (port+5) and writing
349        // to the transmit register (port) are standard 16550 UART I/O
350        // operations. Kernel mode with I/O privileges.
351        unsafe {
352            // Wait for transmit holding register empty
353            while (crate::arch::inb(self.port + 5) & 0x20) == 0 {
354                core::hint::spin_loop();
355            }
356
357            crate::arch::outb(self.port, byte);
358        }
359    }
360
361    /// Read a byte from serial port (non-blocking)
362    #[allow(dead_code)] // Serial read API for future interactive console
363    fn read_byte(&self) -> Option<u8> {
364        // SAFETY: Reading line status (port+5) and data register (port)
365        // are standard 16550 UART I/O operations.
366        unsafe {
367            if (crate::arch::inb(self.port + 5) & 0x01) != 0 {
368                Some(crate::arch::inb(self.port))
369            } else {
370                None
371            }
372        }
373    }
374
375    /// Write string to serial port
376    fn write_str(&self, s: &str) {
377        for byte in s.bytes() {
378            if byte == b'\n' {
379                self.write_byte(b'\r'); // Convert LF to CRLF
380            }
381            self.write_byte(byte);
382        }
383    }
384}
385
386impl ConsoleDevice for SerialConsole {
387    fn name(&self) -> &str {
388        &self.name
389    }
390
391    fn dimensions(&self) -> (usize, usize) {
392        (self.width, self.height)
393    }
394
395    fn clear(&mut self) -> Result<(), KernelError> {
396        // Send ANSI clear screen sequence
397        self.write_str("\x1b[2J\x1b[H");
398        self.cursor_x = 0;
399        self.cursor_y = 0;
400        Ok(())
401    }
402
403    fn write_char(&mut self, x: usize, y: usize, ch: ConsoleChar) -> Result<(), KernelError> {
404        // Position cursor and write character
405        self.write_str(&alloc::format!(
406            "\x1b[{};{}H{}",
407            y + 1,
408            x + 1,
409            ch.ascii as char
410        ));
411        Ok(())
412    }
413
414    fn write_string(&mut self, x: usize, y: usize, s: &str, _color: u8) -> Result<(), KernelError> {
415        // Position cursor and write string
416        self.write_str(&alloc::format!("\x1b[{};{}H{}", y + 1, x + 1, s));
417        Ok(())
418    }
419
420    fn scroll_up(&mut self) -> Result<(), KernelError> {
421        // Send ANSI scroll up sequence
422        self.write_str("\x1b[S");
423        Ok(())
424    }
425
426    fn set_cursor(&mut self, x: usize, y: usize) -> Result<(), KernelError> {
427        if x >= self.width || y >= self.height {
428            return Err(KernelError::InvalidArgument {
429                name: "cursor_position",
430                value: "out of bounds",
431            });
432        }
433
434        self.cursor_x = x;
435        self.cursor_y = y;
436
437        // Send ANSI cursor position sequence
438        self.write_str(&alloc::format!("\x1b[{};{}H", y + 1, x + 1));
439        Ok(())
440    }
441
442    fn get_cursor(&self) -> (usize, usize) {
443        (self.cursor_x, self.cursor_y)
444    }
445
446    fn set_cursor_visible(&mut self, visible: bool) -> Result<(), KernelError> {
447        if visible {
448            self.write_str("\x1b[?25h"); // Show cursor
449        } else {
450            self.write_str("\x1b[?25l"); // Hide cursor
451        }
452        Ok(())
453    }
454}
455
456/// Console driver that manages multiple console devices
457pub struct ConsoleDriver {
458    devices: Vec<Box<dyn ConsoleDevice>>,
459    active_device: usize,
460    name: String,
461}
462
463impl Default for ConsoleDriver {
464    fn default() -> Self {
465        Self::new()
466    }
467}
468
469impl ConsoleDriver {
470    /// Create a new console driver
471    pub fn new() -> Self {
472        Self {
473            devices: Vec::new(),
474            active_device: 0,
475            name: String::from("console"),
476        }
477    }
478
479    /// Add a console device
480    pub fn add_device(&mut self, device: Box<dyn ConsoleDevice>) {
481        crate::println!("[CONSOLE] Added console device: {}", device.name());
482        self.devices.push(device);
483    }
484
485    /// Set active console device
486    pub fn set_active_device(&mut self, index: usize) -> Result<(), KernelError> {
487        if index >= self.devices.len() {
488            return Err(KernelError::InvalidArgument {
489                name: "device_index",
490                value: "out of range",
491            });
492        }
493
494        self.active_device = index;
495        crate::println!(
496            "[CONSOLE] Switched to console device: {}",
497            self.devices[index].name()
498        );
499        Ok(())
500    }
501
502    /// Get active console device
503    pub fn get_active_device(&mut self) -> Option<&mut dyn ConsoleDevice> {
504        match self.devices.get_mut(self.active_device) {
505            Some(device) => Some(device.as_mut()),
506            None => None,
507        }
508    }
509
510    /// Write to all console devices
511    pub fn write_to_all(&mut self, s: &str) {
512        for device in &mut self.devices {
513            let (x, y) = device.get_cursor();
514            device.write_string(x, y, s, 0x07).ok(); // Light gray on black
515        }
516    }
517}
518
519impl Driver for ConsoleDriver {
520    fn name(&self) -> &str {
521        &self.name
522    }
523
524    fn supported_classes(&self) -> Vec<DeviceClass> {
525        vec![DeviceClass::Display, DeviceClass::Serial]
526    }
527
528    fn supports_device(&self, device: &DeviceInfo) -> bool {
529        matches!(device.class, DeviceClass::Display | DeviceClass::Serial)
530    }
531
532    fn probe(&mut self, _device: &DeviceInfo) -> Result<(), KernelError> {
533        crate::println!("[CONSOLE] Probing device: {}", _device.name);
534        Ok(())
535    }
536
537    fn attach(&mut self, device: &DeviceInfo) -> Result<(), KernelError> {
538        crate::println!("[CONSOLE] Attaching to device: {}", device.name);
539
540        match device.class {
541            DeviceClass::Display => {
542                // Add VGA console
543                let vga_console = VgaConsole::new();
544                self.add_device(Box::new(vga_console));
545            }
546            DeviceClass::Serial => {
547                // Add serial console (COM1 by default)
548                let serial_console = SerialConsole::new(0x3F8);
549                self.add_device(Box::new(serial_console));
550            }
551            _ => {
552                return Err(KernelError::OperationNotSupported {
553                    operation: "attach unsupported device class",
554                })
555            }
556        }
557
558        Ok(())
559    }
560
561    fn detach(&mut self, _device: &DeviceInfo) -> Result<(), KernelError> {
562        crate::println!("[CONSOLE] Detaching from device: {}", _device.name);
563
564        // Find and remove the console device matching this DeviceInfo name
565        let device_name = match _device.class {
566            DeviceClass::Display => "vga",
567            DeviceClass::Serial => "serial0",
568            _ => "",
569        };
570
571        if let Some(pos) = self.devices.iter().position(|d| d.name() == device_name) {
572            self.devices.remove(pos);
573            // Adjust active device index if needed
574            if self.active_device >= self.devices.len() && !self.devices.is_empty() {
575                self.active_device = self.devices.len() - 1;
576            }
577            crate::println!("[CONSOLE] Removed console device: {}", device_name);
578            Ok(())
579        } else {
580            Err(KernelError::NotFound {
581                resource: "console_device",
582                id: 0,
583            })
584        }
585    }
586
587    fn suspend(&mut self) -> Result<(), KernelError> {
588        crate::println!("[CONSOLE] Suspending console driver");
589        Ok(())
590    }
591
592    fn resume(&mut self) -> Result<(), KernelError> {
593        crate::println!("[CONSOLE] Resuming console driver");
594        Ok(())
595    }
596
597    fn handle_interrupt(&mut self, _irq: u8) -> Result<(), KernelError> {
598        crate::println!("[CONSOLE] Handling interrupt {} for console", _irq);
599        Ok(())
600    }
601
602    fn read(&mut self, _offset: u64, _buffer: &mut [u8]) -> Result<usize, KernelError> {
603        // Read buffered keystrokes from the keyboard driver.
604        // The keyboard driver decodes PS/2 scancodes into ASCII bytes and
605        // stores them in a lock-free ring buffer. We drain available bytes
606        // into the caller's buffer.
607        let mut bytes_read = 0usize;
608
609        for slot in _buffer.iter_mut() {
610            match crate::drivers::keyboard::read_key() {
611                Some(byte) => {
612                    *slot = byte;
613                    bytes_read += 1;
614                }
615                None => break,
616            }
617        }
618
619        Ok(bytes_read)
620    }
621
622    fn write(&mut self, _offset: u64, data: &[u8]) -> Result<usize, KernelError> {
623        if let Ok(s) = core::str::from_utf8(data) {
624            self.write_to_all(s);
625            Ok(data.len())
626        } else {
627            Err(KernelError::InvalidArgument {
628                name: "data",
629                value: "invalid UTF-8",
630            })
631        }
632    }
633
634    fn ioctl(&mut self, cmd: u32, arg: u64) -> Result<u64, KernelError> {
635        match cmd {
636            0x2000 => {
637                // Get active device index
638                Ok(self.active_device as u64)
639            }
640            0x2001 => {
641                // Set active device index
642                self.set_active_device(arg as usize)?;
643                Ok(0)
644            }
645            0x2002 => {
646                // Get device count
647                Ok(self.devices.len() as u64)
648            }
649            0x2003 => {
650                // Clear screen
651                if let Some(device) = self.get_active_device() {
652                    device.clear()?;
653                }
654                Ok(0)
655            }
656            _ => Err(KernelError::InvalidArgument {
657                name: "ioctl_cmd",
658                value: "unknown",
659            }),
660        }
661    }
662}
663
664/// Global console driver instance
665static CONSOLE_DRIVER: OnceLock<Mutex<ConsoleDriver>> = OnceLock::new();
666
667/// Initialize console subsystem
668pub fn init() {
669    let mut console_driver = ConsoleDriver::new();
670
671    // Add VGA console
672    let vga_console = VgaConsole::new();
673    console_driver.add_device(Box::new(vga_console));
674
675    // Add serial console (COM1)
676    let serial_console = SerialConsole::new(0x3F8);
677    console_driver.add_device(Box::new(serial_console));
678
679    // Initialize VGA console
680    if let Some(device) = console_driver.get_active_device() {
681        device.clear().ok();
682        device.set_cursor_visible(true).ok();
683    }
684
685    let _ = CONSOLE_DRIVER.set(Mutex::new(console_driver));
686
687    // Register with driver framework
688    let driver_framework = crate::services::driver_framework::get_driver_framework();
689    let console_instance = ConsoleDriver::new();
690
691    if let Err(_e) = driver_framework.register_driver(Box::new(console_instance)) {
692        crate::println!("[CONSOLE] Failed to register console driver: {}", _e);
693    } else {
694        crate::println!("[CONSOLE] Console subsystem initialized");
695    }
696}
697
698/// Get the global console driver
699pub fn get_console_driver() -> &'static Mutex<ConsoleDriver> {
700    CONSOLE_DRIVER
701        .get()
702        .expect("Console driver not initialized")
703}