//! 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. //! //! Since stage 13 the same `Writer` can draw on the virtio-gpu framebuffer //! instead (fbcon.rs): `use_framebuffer` switches over at boot when there //! is a GPU; without one, this VGA text mode stays the console. use crate::fbcon::{FbConsole, Rect}; 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. /// /// Two possible screens behind it: the VGA text buffer (always there in /// QEMU, and what the test kernels use), or -- once virtio-gpu is up and /// `use_framebuffer` was called -- the framebuffer console (fbcon.rs), /// which has more and smaller cells. Everything below goes through /// `put`/`scroll`/`width`/`height`, so it works the same on both. pub struct Writer { column_position: usize, color_code: ColorCode, buffer: &'static mut Buffer, fb: Option, } impl Writer { fn width(&self) -> usize { self.fb.as_ref().map_or(BUFFER_WIDTH, |f| f.cols()) } fn height(&self) -> usize { self.fb.as_ref().map_or(BUFFER_HEIGHT, |f| f.rows()) } /// Set one cell. fn put(&mut self, row: usize, col: usize, ascii_character: u8, color_code: ColorCode) { match &mut self.fb { Some(fb) => fb.put(row, col, ascii_character, color_code.0), None => self.buffer.chars[row][col].write(ScreenChar { ascii_character, color_code }), } } pub fn write_byte(&mut self, byte: u8) { match byte { b'\n' => self.new_line(), byte => { if self.column_position >= self.width() { self.new_line(); } let row = self.height() - 1; let col = self.column_position; self.put(row, col, byte, self.color_code); self.column_position += 1; } } } pub fn write_string(&mut self, s: &str) { for c in s.chars() { let byte = match (c, self.fb.is_some()) { ('\n', _) => b'\n', // printable ASCII is the same everywhere (' '..='~', _) => c as u8, // the framebuffer font has Latin-1 and a few more (c, true) => crate::font::glyph_index(c), // VGA only knows code page 437; show anything else as a block (_, false) => 0xfe, }; self.write_byte(byte); } } fn new_line(&mut self) { match &mut self.fb { Some(fb) => fb.scroll_up(self.color_code.0), None => { 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) { for col in 0..self.width() { self.put(row, col, b' ', self.color_code); } } /// After a print: framebuffer only -- draw the cursor and send the /// changed pixels to the screen. (VGA needs nothing: the card reads its /// buffer itself, 70 times a second.) fn finish(&mut self) { let (row, col) = (self.height() - 1, self.column_position); if let Some(fb) = &mut self.fb { fb.set_cursor(Some((row, col))); fb.flush(); } } } /// 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) }, fb: None, }); } /// Switch the console to the framebuffer (called once virtio-gpu is up). /// The text already on the VGA screen comes along, so the boot messages /// stay visible. Returns the new size in characters. pub fn use_framebuffer(fb: crate::virtio::gpu::FbInfo) -> (usize, usize) { x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); let mut con = FbConsole::new(fb); let (cols, rows) = (con.cols(), con.rows()); // VGA's 25 rows go to the bottom 25 rows of the new screen. for row in 0..BUFFER_HEIGHT.min(rows) { for col in 0..BUFFER_WIDTH.min(cols) { let c = writer.buffer.chars[row][col].read(); con.put(rows - BUFFER_HEIGHT.min(rows) + row, col, c.ascii_character, c.color_code.0); } } writer.fb = Some(con); writer.finish(); (cols, rows) }) } /// Is the console on the framebuffer? pub fn on_framebuffer() -> bool { x86_64::instructions::interrupts::without_interrupts(|| WRITER.lock().fb.is_some()) } /// Confine the framebuffer console to a rectangle of the screen (the /// `tile` demo puts it in one tile); a zero-sized rect = the whole screen. /// Returns the new (columns, rows), or None on the VGA console. pub fn set_viewport(mut view: Rect) -> Option<(usize, usize)> { x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); let attr = writer.color_code.0; let fb = writer.fb.as_mut()?; if view.w == 0 || view.h == 0 { let info = crate::virtio::gpu::framebuffer()?; view = Rect { x: 0, y: 0, w: info.width, h: info.height }; } fb.set_view(view, attr); let size = (fb.cols(), fb.rows()); writer.column_position = writer.column_position.min(size.0); writer.finish(); Some(size) }) } /// The console's current rectangle in pixels (framebuffer only). pub fn viewport() -> Option { x86_64::instructions::interrupts::without_interrupts(|| WRITER.lock().fb.as_ref().map(|f| f.view())) } /// Text size of the console in characters (columns, rows). pub fn size() -> (usize, usize) { x86_64::instructions::interrupts::without_interrupts(|| { let writer = WRITER.lock(); (writer.width(), writer.height()) }) } /// 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 < self.writer.width() && self.row < self.writer.height() { self.writer.put(self.row, self.col, byte, 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); if let Some(fb) = &mut writer.fb { fb.flush(); } }); } /// 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) { let width = size().0; print_at(0, width.saturating_sub(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 (row, col) = (writer.height() - 1, writer.column_position); let color_code = writer.color_code; writer.put(row, col, b' ', color_code); } writer.finish(); }); } /// Clear the screen (rows 1..; row 0 is the status line) and start /// writing at the left of the bottom row again. pub fn clear_screen() { x86_64::instructions::interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); for row in 1..writer.height() { writer.clear_row(row); } writer.column_position = 0; writer.finish(); }); } #[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(|| { let mut writer = WRITER.lock(); writer.write_fmt(args).unwrap(); writer.finish(); }); } /// `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); } }); }