use core::ptr::write_volatile; /// VGA text mode display dimensions. /// /// The standard VGA text mode provides an 80×25 grid of character cells. /// Each cell is 2 bytes: [ASCII byte] [attribute byte]. /// The buffer starts at physical address 0xB8000 and is memory-mapped into /// the CPU's address space. The x86_64 bootloader identity-maps this region, /// so we can write to it with regular memory stores — but we must use volatile /// writes to prevent the compiler from optimizing away MMIO operations. pub const VGA_ADDR: *mut u8 = 0xB8000 as *mut u8; pub const WIDTH: usize = 80; pub const HEIGHT: usize = 25; /// Standard VGA text-mode colors. /// /// The attribute byte layout: /// bits 0-3: foreground color (0 = black, 15 = white) /// bits 4-6: background color (0-7, hardware limit; bit 7 = blink) #[allow(dead_code)] #[repr(u8)] #[derive(Clone, Copy, Debug)] 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, } /// A packed attribute byte encoding foreground + background color. #[derive(Clone, Copy)] pub struct ColorCode(u8); impl ColorCode { /// Constructs a color code from foreground and background colors. /// /// ## Hardware note /// The VGA attribute byte stores the background color in bits 4-6 (3 bits, /// limiting backgrounds to 0-7). Bit 7 is the blink enable bit, which we /// keep cleared. The foreground color uses bits 0-3 (including intensity /// on bit 3), giving 16 possible foreground colors. pub const fn new(fg: Color, bg: Color) -> ColorCode { ColorCode(((bg as u8) << 4) | (fg as u8)) } } /// Clears the entire VGA screen to the given background color. /// /// Writes a space character (0x20) with the appropriate attribute byte /// to every cell on the screen. pub fn clear_screen(bg: Color) { let color = ColorCode::new(Color::White, bg); for offset in (0..(WIDTH * HEIGHT * 2)).step_by(2) { unsafe { write_volatile(VGA_ADDR.add(offset), b' '); write_volatile(VGA_ADDR.add(offset + 1), color.0); } } } /// Writes a single character at position (row, col) with the given color. /// /// The position is bounds-checked against the 80×25 screen. Writes outside /// the visible area are silently ignored. pub fn write_char(row: usize, col: usize, ch: u8, color: ColorCode) { if row < HEIGHT && col < WIDTH { let offset = (row * WIDTH + col) * 2; unsafe { write_volatile(VGA_ADDR.add(offset), ch); write_volatile(VGA_ADDR.add(offset + 1), color.0); } } } /// Writes a string starting at position (row, col) with the given color. /// /// Stops at the right edge of the screen to avoid wrapping. pub fn write_str(row: usize, col: usize, s: &str, color: ColorCode) { for (i, &ch) in s.as_bytes().iter().enumerate() { if col + i < WIDTH { write_char(row, col + i, ch, color); } else { break; } } }