//! VGA text mode driver. //! //! In text mode the screen is a 25x80 grid of 2-byte cells memory-mapped at //! physical address 0xb8000: byte 0 = ASCII char, byte 1 = colour attribute //! (low nibble foreground, high nibble background). //! The bootloader identity-maps that address, so we can write to it directly. use core::fmt; use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; #[allow(dead_code)] #[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, } /// Foreground + background packed into one attribute byte. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] struct ColorCode(u8); impl ColorCode { fn new(fg: Color, bg: Color) -> ColorCode { ColorCode((bg as u8) << 4 | (fg as u8)) } } /// One on-screen cell. `repr(C)` guarantees the char-then-colour layout. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] struct ScreenChar { ascii_character: u8, color_code: ColorCode, } const BUFFER_HEIGHT: usize = 25; const BUFFER_WIDTH: usize = 80; /// The whole screen. `Volatile` tells the compiler these writes have side /// effects (they change what's on the monitor) so it must not drop them. #[repr(transparent)] struct Buffer { chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], } /// Always writes on the bottom row and scrolls everything up on newline. pub struct Writer { column_position: usize, color_code: ColorCode, buffer: &'static mut Buffer, } impl Writer { pub fn write_byte(&mut self, byte: u8) { match byte { b'\n' => self.new_line(), byte => { if self.column_position >= BUFFER_WIDTH { self.new_line(); } let row = BUFFER_HEIGHT - 1; let col = self.column_position; self.buffer.chars[row][col].write(ScreenChar { ascii_character: byte, color_code: self.color_code, }); self.column_position += 1; } } } pub fn write_string(&mut self, s: &str) { for byte in s.bytes() { match byte { // printable ASCII or newline 0x20..=0x7e | b'\n' => self.write_byte(byte), // VGA only knows code page 437; show anything else as a block _ => self.write_byte(0xfe), } } } fn new_line(&mut self) { for row in 1..BUFFER_HEIGHT { for col in 0..BUFFER_WIDTH { let c = self.buffer.chars[row][col].read(); self.buffer.chars[row - 1][col].write(c); } } self.clear_row(BUFFER_HEIGHT - 1); self.column_position = 0; } fn clear_row(&mut self, row: usize) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code }; for col in 0..BUFFER_WIDTH { self.buffer.chars[row][col].write(blank); } } } /// Lets us use Rust's formatting machinery (`write!`, `{}`) with the Writer. impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); Ok(()) } } lazy_static! { /// Global writer. The raw pointer cast to 0xb8000 is the one `unsafe` /// spot: we promise that address really is the VGA buffer. pub static ref WRITER: Mutex = Mutex::new(Writer { column_position: 0, color_code: ColorCode::new(Color::LightGreen, Color::Black), buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, }); } /// Write formatted text at a fixed screen position (row, col) in black on /// light grey, without moving the normal cursor. Used for status lines. /// Text that runs past the end of the row is cut off. pub fn print_at(row: usize, col: usize, args: fmt::Arguments) { /// A tiny fmt::Write target that writes cells left to right on one row. struct At<'a> { writer: &'a mut Writer, row: usize, col: usize, } impl fmt::Write for At<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { let color_code = ColorCode::new(Color::Black, Color::LightGray); for byte in s.bytes() { if self.col < BUFFER_WIDTH { let ascii_character = byte; self.writer.buffer.chars[self.row][self.col] .write(ScreenChar { ascii_character, color_code }); self.col += 1; } } Ok(()) } } use core::fmt::Write; x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); let _ = At { writer: &mut writer, row, col }.write_fmt(args); }); } /// Draw " uptime: N s " right-aligned on the top row. Called from the timer /// interrupt handler about once a second. pub fn show_uptime(seconds: u64) { print_at(0, BUFFER_WIDTH - 15, format_args!(" uptime: {:>4}s ", seconds)); } /// Erase the character before the cursor on the bottom row (Backspace). /// Stops at the start of the line (no going back to the previous row). pub fn backspace() { x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); if writer.column_position > 0 { writer.column_position -= 1; let col = writer.column_position; let blank = ScreenChar { ascii_character: b' ', color_code: writer.color_code }; writer.buffer.chars[BUFFER_HEIGHT - 1][col].write(blank); } }); } #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write; // Disable interrupts while we hold the lock. Otherwise an interrupt // handler that also prints would spin forever waiting for a lock held by // the code it interrupted (a deadlock). x86_64::instructions::interrupts::without_interrupts(|| { WRITER.lock().write_fmt(args).unwrap(); }); } /// `print!` for the VGA screen. #[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } /// `println!` for the VGA screen. #[macro_export] macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } #[test_case] fn test_println_simple() { println!("test_println_simple output"); } #[test_case] fn test_println_output() { // The printed line ends up on the second-to-last row (println adds "\n"). use core::fmt::Write; let s = "Some test string that fits on a single line"; // Hold the lock with interrupts off, so the timer can't redraw meanwhile. x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); writeln!(writer, "\n{}", s).expect("writeln failed"); for (i, c) in s.chars().enumerate() { let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); assert_eq!(char::from(screen_char.ascii_character), c); } }); }