use super::*; use crate::sync::interface::Mutex; use crate::sync::NullLock; use core::fmt; use volatile::Volatile; #[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; #[repr(transparent)] struct Buffer { chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], } pub struct VgaWriter { column_position: usize, color_code: ColorCode, buffer: Option<&'static mut Buffer>, } impl VgaWriter { 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; let color_code = self.color_code; if let Some(buffer) = &mut self.buffer { buffer.chars[row][col].write(ScreenChar { ascii_character: byte, color_code, }); } self.column_position += 1; } } } fn new_line(&mut self) { for row in 1..BUFFER_HEIGHT { for col in 0..BUFFER_WIDTH { if let Some(buffer) = &mut self.buffer { let character = buffer.chars[row][col].read(); buffer.chars[row - 1][col].write(character); } } } 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 { if let Some(buffer) = &mut self.buffer { buffer.chars[row][col].write(blank); } } } pub fn write_string(&mut self, s: &str) { for byte in s.bytes() { match byte { // printable ASCII byte or newline 0x20..=0x7e | b'\n' => self.write_byte(byte), // not part of printable ASCII range _ => self.write_byte(0xfe), } } } } impl NullLock { #[allow(dead_code)] pub fn write_byte(&self, byte: u8) { self.lock(|writer| { writer.write_byte(byte); }); } #[allow(dead_code)] fn new_line(&self) { self.lock(|writer| { writer.new_line(); }); } #[allow(dead_code)] fn clear_row(&self, row: usize) { self.lock(|writer| { writer.clear_row(row); }); } #[allow(dead_code)] pub fn write_string(&self, s: &str) { self.lock(|writer| { writer.write_string(s); }); } } impl fmt::Write for VgaWriter { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); Ok(()) } } impl NullLock { pub fn init(&self) { self.lock(|writer| { writer.buffer = Some(unsafe { &mut *(0xb8000 as *mut Buffer) }); }) } } #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock(|writer| { writer.write_fmt(args).unwrap(); }); } #[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::vga::_print(format_args!($($arg)*))); } #[macro_export] macro_rules! println { () => (print!("\n")); ($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*))); } pub static WRITER: NullLock = NullLock::new(VgaWriter { column_position: 0, color_code: ColorCode::new(Color::LightBlue, Color::Black), buffer: None, }); #[test_case] fn test_println_simple() { println!("test_println_simple output"); } #[test_case] fn test_println_many() { for _ in 0..200 { println!("test_println_many output"); } } #[test_case] fn test_println_output() { let s = "Some test string that fits on a single line"; println!("{}", s); for (i, c) in s.chars().enumerate() { let screen_char = WRITER.lock(|writer| { if let Some(buffer) = &writer.buffer { return buffer.chars[BUFFER_HEIGHT - 2][i].read(); } else { return ScreenChar { ascii_character: b' ', color_code: ColorCode::new(Color::LightBlue, Color::Black), }; } }); assert_eq!(char::from(screen_char.ascii_character), c); } }