# VGA text Hello. Today I succesfully made a `println!` macro. ## std To following conventions I decided to make a `std` library. To do this I made a workspace in `kernel/`. `kernel/Cargo.toml` ```toml [package] name = "kernel" version = "0.1.0" edition = "2021" [workspace] members = ["std"] [dependencies] std = { path = "std" } ``` ## lib.rs To not have one big spaghetti file I separated out all the individal modules into different files. `kernel/std/src/lib.rs` ```rust #![no_std] pub mod vga_buffer; ``` ## vga_buffer.rs First step for a VGA text driver is to be able to represent data. `kernel/std/src/lib.rs` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Color { Black = 0, Blue = 1, Green = 2, Cyan = 3, Red = 4, Magenta = 5, Brown = 6, LightGray = 7, DarkGray = 8, LightBlue = 9, LightGreen = 10, LightCyan = 11, LightRed = 12, Pink = 13, Yellow = 14, White = 15, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] // Store this as a u8 pub struct ColorCode(u8); impl ColorCode { pub fn new(foreground: Color, background: Color) -> ColorCode { ColorCode((background as u8) << 4 | (foreground as u8)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] // Tells rust to store this like a struct in C, i.e. store this as 2 bytes struct ScreenChar { ascii_character: u8, color_code: ColorCode, } const BUFFER_HEIGHT: usize = 25; const BUFFER_WIDTH: usize = 80; #[repr(transparent)] struct Buffer { chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], } ``` Now I can represent the VGA text buffer. To write to this I want to make a writer struct that implements `fmt::Write`. ```rust pub struct Writer { col: usize, row: usize, buffer: &'static mut Buffer, } impl Writer { pub fn write_byte(&mut self, byte: u8) { self.write_byte_color(byte, ColorCode::new(Color::White, Color::Black)); } pub fn write_byte_color(&mut self, byte: u8, color: ColorCode) { match byte { b'\n' => self.new_line(), byte => { if self.col >= BUFFER_WIDTH { self.new_line(); } let row = self.row; let col = self.col; let color_code = color; self.buffer.chars[row][col] = ScreenChar { ascii_character: byte, color_code, }; self.col += 1; } } } pub fn write_string(&mut self, s: &str) { self.write_string_color(s, ColorCode::new(Color::White, Color::Black)); } pub fn write_string_color(&mut self, s: &str, color: ColorCode) { for char in s.bytes() { match char { 0x20..=0x7e | b'\n' => self.write_byte_color(char, color), // Ascii Character _ => self.write_byte(0xfe), // Non ascii } } } fn new_line(&mut self) { self.row += 1; self.col = 0; } } impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); Ok(()) } } ``` The problem now becomes that I can create a `Writer` but I don't have a global position, so it will just overwrite what I wrote last time. To solve this I can use a static variable. But a static variable won't be mutable. This can be solved with `Mutex` in the spin crate and `lazy_static`. `kernel/std/Cargo.toml` ```toml [package] name = "std" version = "0.1.0" edition = "2021" [dependencies] lazy_static = { version = "1.4.0", features = ["spin_no_std"] } spin = "0.9.8" ``` and to make a static variable I wrote (copied from [here](https://os.phil-opp.com/vga-text-mode/)) this ```rust lazy_static! { pub static ref WRITER: Mutex = Mutex::new(Writer { col: 0, row: 0, buffer: unsafe { &mut *(0xB8000 as *mut Buffer) }, }); } ``` in `kernel/std/src/vga_buffer.rs`. Last step is just to add a function and 2 macros ```rust #[doc(hidden)] pub fn _print(args: fmt::Arguments) { WRITER.lock().write_fmt(args).unwrap() } #[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } #[macro_export] macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } ``` Huge thanks to @phil-opp for [os.phil-opp.com](https://os.phil-opp.com/)