//! Writing an [OS] in Rust: [Testing] //! //! [os]: https://os.phil-opp.com //! [testing]: https://os.phil-opp.com/testing/ #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; #[no_mangle] pub extern "C" fn _start() -> ! { println!("Hello world!"); #[cfg(test)] test_main(); loop {} } #[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("{}", info); loop {} } #[cfg(test)] #[panic_handler] fn panic(info: &PanicInfo) -> ! { serial_println!("[failed]\n"); serial_println!("Error: {}\n", info); exit_qemu(QemuExitCode::Failed); loop {} } #[cfg(test)] fn test_runner(tests: &[&dyn Fn()]) { serial_println!("Running {} tests", tests.len()); for test in tests { test(); } exit_qemu(QemuExitCode::Success); } extern crate lazy_static; extern crate spin; extern crate volatile; use core::fmt::{self, Write}; use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; #[macro_export] macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } #[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*))); } #[doc(hidden)] pub fn _print(args: fmt::Arguments) { WRITER.lock().write_fmt(args).unwrap(); } lazy_static! { pub static ref WRITER: Mutex = Mutex::new(Writer { column_position: 0, color_code: ColorCode::new(Color::Yellow, Color::Black), buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, }); } pub struct Writer { column_position: usize, color_code: ColorCode, buffer: &'static mut Buffer, } #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Color { Black = 0, Blue = 1, Green = 2, Cyan = 4, Magenta = 5, Brown = 6, LightGray = 7, DarkGray = 8, LightBlue = 9, LightGreen = 10, LightSyan = 11, LightRed = 12, Pink = 13, Yellow = 14, White = 15, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] struct ColorCode(u8); impl ColorCode { fn new(foreground: Color, background: Color) -> Self { Self((background as u8) << 4 | (foreground as u8)) } } #[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], } impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { for byte in s.bytes() { match byte { // printable ASCII byte or newline 0x20..=0x7e | b'\n' => self.write_byte(byte), _ => self.write_byte(0xfe), } } Ok(()) } } impl Writer { 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; self.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 { let character = self.buffer.chars[row][col].read(); self.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 { self.buffer.chars[row][col].write(blank); } } } #[cfg(test)] mod tests { use super::{serial_print, serial_println}; #[test_case] fn println() { serial_print!("println... "); println!("here is the output from println"); serial_println!("[ok]"); } #[test_case] fn println_many() { serial_print!("println_many... "); for _ in 0..200 { println!("let's println a lot"); } serial_println!("[ok]"); } #[test_case] fn println_output() { use super::*; serial_print!("println_output... "); let s = "Some test string that fits on a single line"; println!("{}", s); for (i, c) in s.chars().enumerate() { let got = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read(); assert_eq!(char::from(got.ascii_character), c); } serial_println!("[ok]"); } } extern crate uart_16550; extern crate x86_64; use uart_16550::SerialPort; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { Success = 0x10, Failed = 0x11, } pub fn exit_qemu(exit_code: QemuExitCode) { use x86_64::instructions::port::Port; unsafe { let mut port = Port::new(0xf4); port.write(exit_code as u32); } } #[macro_export] macro_rules! serial_println { () => ($crate::serial_print!("\n")); ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( concat!($fmt, "\n"), $($arg)*)); } #[macro_export] macro_rules! serial_print { ($($arg:tt)*) => ($crate::_serial_print(format_args!($($arg)*))); } #[doc(hidden)] pub fn _serial_print(args: fmt::Arguments) { SERIAL1.lock().write_fmt(args).unwrap(); } lazy_static! { pub static ref SERIAL1: Mutex = { let mut serial_port = unsafe { SerialPort::new(0x3F8) }; serial_port.init(); Mutex::new(serial_port) }; }