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

veridian_kernel/arch/x86_64/
vga.rs

1use core::{fmt, ptr::write_volatile};
2
3use lazy_static::lazy_static;
4use spin::Mutex;
5
6/// VGA text-mode color palette. Not all variants are used but the full
7/// 16-color palette is defined per the VGA specification.
8#[allow(dead_code)] // Full VGA color palette per specification
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[repr(u8)]
11pub enum Color {
12    Black = 0,
13    Blue = 1,
14    Green = 2,
15    Cyan = 3,
16    Red = 4,
17    Magenta = 5,
18    Brown = 6,
19    LightGray = 7,
20    DarkGray = 8,
21    LightBlue = 9,
22    LightGreen = 10,
23    LightCyan = 11,
24    LightRed = 12,
25    Pink = 13,
26    Yellow = 14,
27    White = 15,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[repr(transparent)]
32struct ColorCode(u8);
33
34impl ColorCode {
35    fn new(foreground: Color, background: Color) -> ColorCode {
36        ColorCode(((background as u8) << 4) | (foreground as u8))
37    }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(C)]
42struct ScreenChar {
43    ascii_character: u8,
44    color_code: ColorCode,
45}
46
47const BUFFER_HEIGHT: usize = 25;
48const BUFFER_WIDTH: usize = 80;
49
50#[repr(transparent)]
51struct Buffer {
52    chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
53}
54
55pub struct Writer {
56    column_position: usize,
57    color_code: ColorCode,
58    buffer: &'static mut Buffer,
59}
60
61impl Writer {
62    pub fn write_byte(&mut self, byte: u8) {
63        match byte {
64            b'\n' => self.new_line(),
65            byte => {
66                if self.column_position >= BUFFER_WIDTH {
67                    self.new_line();
68                }
69
70                let row = BUFFER_HEIGHT - 1;
71                let col = self.column_position;
72
73                let color_code = self.color_code;
74                // SAFETY: The VGA buffer at 0xb8000 is memory-mapped I/O. write_volatile
75                // ensures the write is not optimized away. Row/col are bounds-checked by
76                // the new_line logic above ensuring we stay within the buffer.
77                unsafe {
78                    write_volatile(
79                        &mut self.buffer.chars[row][col],
80                        ScreenChar {
81                            ascii_character: byte,
82                            color_code,
83                        },
84                    );
85                }
86                self.column_position += 1;
87            }
88        }
89    }
90
91    fn new_line(&mut self) {
92        for row in 1..BUFFER_HEIGHT {
93            for col in 0..BUFFER_WIDTH {
94                // SAFETY: read_volatile and write_volatile access the VGA text buffer
95                // at 0xb8000. Row indices are bounded by BUFFER_HEIGHT (loop range 1..25),
96                // and col by BUFFER_WIDTH (0..80). row-1 is always >= 0 since row starts at 1.
97                let character = unsafe { core::ptr::read_volatile(&self.buffer.chars[row][col]) };
98                unsafe {
99                    write_volatile(&mut self.buffer.chars[row - 1][col], character);
100                }
101            }
102        }
103        self.clear_row(BUFFER_HEIGHT - 1);
104        self.column_position = 0;
105    }
106
107    fn clear_row(&mut self, row: usize) {
108        let blank = ScreenChar {
109            ascii_character: b' ',
110            color_code: self.color_code,
111        };
112        for col in 0..BUFFER_WIDTH {
113            // SAFETY: write_volatile to VGA text buffer. Row is passed from
114            // new_line (always BUFFER_HEIGHT-1) or caller. Col bounded by BUFFER_WIDTH.
115            unsafe {
116                write_volatile(&mut self.buffer.chars[row][col], blank);
117            }
118        }
119    }
120
121    pub fn write_string(&mut self, s: &str) {
122        for byte in s.bytes() {
123            match byte {
124                0x20..=0x7e | b'\n' => self.write_byte(byte),
125                _ => self.write_byte(0xfe),
126            }
127        }
128    }
129}
130
131impl fmt::Write for Writer {
132    fn write_str(&mut self, s: &str) -> fmt::Result {
133        self.write_string(s);
134        Ok(())
135    }
136}
137
138lazy_static! {
139    pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
140        column_position: 0,
141        color_code: ColorCode::new(Color::White, Color::Black),
142        // SAFETY: 0xb8000 is the well-known physical address of the VGA text
143        // buffer, identity-mapped in kernel space. The cast to &'static mut
144        // Buffer is valid because the VGA buffer has static lifetime and is
145        // protected by the enclosing Mutex<Writer>.
146        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
147    });
148}
149
150#[doc(hidden)]
151pub fn _print(args: fmt::Arguments) {
152    use core::fmt::Write;
153
154    use x86_64::instructions::interrupts;
155
156    interrupts::without_interrupts(|| {
157        WRITER.lock().write_fmt(args).expect("VGA write_fmt failed");
158    });
159}