rusty_os/
vga_buffer.rs

1use core::fmt;
2use lazy_static::lazy_static;
3use spin::Mutex;
4use volatile::Volatile;
5
6lazy_static! {
7    // A global `Writer` instance that can be used for printing to the VGA text buffer
8    // Used by the `print!` and `println` macros
9    pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
10        column_position: 0,
11        color_code: ColorCode::new(Color::Yellow, Color::Black),
12        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
13    });
14}
15
16// Standard color palette in VGA text mode
17#[allow(dead_code)]
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u8)]
20pub enum Color {
21    Black = 0,
22    Blue = 1,
23    Green = 2,
24    Cyan = 3,
25    Red = 4,
26    Magenta = 5,
27    Brown = 6,
28    LightGray = 7,
29    DarkGray = 8,
30    LightBlue = 9,
31    LightGreen = 10,
32    LightCyan = 11,
33    LightRed = 12,
34    Pink = 13,
35    Yellow = 14,
36    White = 15,
37}
38
39// A combination of a foreground and a background color
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(transparent)]
42struct ColorCode(u8);
43
44impl ColorCode {
45    // Create new `ColorCode` with the given foreground and background
46    fn new(foreground: Color, background: Color) -> ColorCode {
47        ColorCode((background as u8) << 4 | (foreground as u8))
48    }
49}
50
51// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[repr(C)]
54struct ScreenChar {
55    ascii_character: u8,
56    color_code: ColorCode,
57}
58
59// The height and width of the text buffer (nomarlly 25 lines with 80 colums)
60const BUFFER_HEIGHT: usize = 25;
61const BUFFER_WIDTH: usize = 80;
62
63// A structure representing the VGA text buffer
64#[repr(transparent)]
65struct Buffer {
66    chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
67}
68
69// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`
70// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the
71// `core::fmt::Write` trait
72pub struct Writer {
73    column_position: usize,
74    color_code: ColorCode,
75    buffer: &'static mut Buffer,
76}
77
78impl Writer {
79    // Writes an ASCII byte to the buffer
80    // Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character
81    pub fn write_byte(&mut self, byte: u8) {
82        match byte {
83            b'\n' => self.new_line(),
84            byte => {
85                if self.column_position >= BUFFER_WIDTH {
86                    self.new_line();
87                }
88
89                let row = BUFFER_HEIGHT - 1;
90                let col = self.column_position;
91
92                let color_code = self.color_code;
93                self.buffer.chars[row][col].write(ScreenChar {
94                    ascii_character: byte,
95                    color_code,
96                });
97                self.column_position += 1;
98            }
99        }
100    }
101
102    // Writes the given ASCII string to the buffer
103    // Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Doesn't support
104    // strings with non-ASCII characters, since they can't be printed in VGA text mode
105    fn write_string(&mut self, s: &str) {
106        for byte in s.bytes() {
107            match byte {
108                0x20..=0x7e | b'\n' => self.write_byte(byte), // printable ASCII or newline
109                _ => self.write_byte(0xfe),                   // not a printable ASCII character
110            }
111        }
112    }
113
114    // Shifts all lines on line up and clears last row
115    fn new_line(&mut self) {
116        for row in 1..BUFFER_HEIGHT {
117            for col in 0..BUFFER_WIDTH {
118                let character = self.buffer.chars[row][col].read();
119                self.buffer.chars[row - 1][col].write(character);
120            }
121        }
122        self.clear_row(BUFFER_HEIGHT - 1);
123        self.column_position = 0;
124    }
125
126    // Clears a row by overwriting it with blank characters
127    fn clear_row(&mut self, row: usize) {
128        let blank = ScreenChar {
129            ascii_character: b' ',
130            color_code: self.color_code,
131        };
132        for col in 0..BUFFER_WIDTH {
133            self.buffer.chars[row][col].write(blank);
134        }
135    }
136}
137
138impl fmt::Write for Writer {
139    fn write_str(&mut self, s: &str) -> fmt::Result {
140        self.write_string(s);
141        Ok(())
142    }
143}
144
145// Like the `print!` macro in the standard library, but prints to the VGA text buffer\
146#[macro_export]
147macro_rules! print {
148    ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
149}
150
151// Like the `println!` macro in the standard library, but prints to the VGA text buffer
152#[macro_export]
153macro_rules! println {
154    () => ($crate::print!("\n"));
155    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
156}
157
158// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance
159#[doc(hidden)]
160pub fn _print(args: fmt::Arguments) {
161    use core::fmt::Write;
162    WRITER.lock().write_fmt(args).unwrap();
163}
164
165#[test_case]
166fn test_println_simple() {
167    println!("test_println_simple output");
168}
169
170#[test_case]
171fn test_println_many() {
172    for _ in 0..200 {
173        println!("test_println_many output");
174    }
175}
176
177#[test_case]
178fn test_println_output() {
179    let s = "Some test string that fits on a single line";
180    println!("{}", s);
181    for (i, c) in s.chars().enumerate() {
182        let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
183        assert_eq!(char::from(screen_char.ascii_character), c);
184    }
185}