diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c7387d9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,70 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; + +pub mod serial; +pub mod vga_buffer; + +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); + self(); + serial_println!("[ok]"); + } +} + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +// Entry point for `cargo test` +#[cfg(test)] +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + test_main(); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info); +} + +#[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); + } +} diff --git a/src/main.rs b/src/main.rs index b1d6211..22fb183 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,32 @@ #![no_std] // don't link the Rust standart library #![no_main] // disable all Rust-level entry points +#![feature(custom_test_frameworks)] +#![test_runner(amix::test_runner)] +#![reexport_test_harness_main = "test_main"] +use amix::println; use core::panic::PanicInfo; -mod vga_buffer; - -#[unsafe(no_mangle)] // don't mangle the name of this function +#[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { - // this function is the entry point, since the linker looks for a function - // named `_start` by default println!("Welcome to Amix{}", "!"); - panic!("ERROR!!!"); + + #[cfg(test)] + test_main(); loop {} } // This function is called on panic. +#[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("{}", info); - loop {} } + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + amix::test_panic_handler(info) +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..d0aa59b --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,37 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use uart_16550::SerialPort; + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); +} + +// Prints to the host through the serial interface. +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +// Prints to the host through the serial interface, appending a newline. +#[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)*)); +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index e2fee68..12392f1 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -68,7 +68,7 @@ impl Writer { let row = BUFFER_HEIGHT - 1; let col = self.column_position; - let color_code = self.color_code; + //let color_code = self.color_code; self.buffer.chars[row][col].write(ScreenChar { ascii_character: byte, color_code: self.color_code, @@ -121,7 +121,7 @@ impl fmt::Write for Writer { lazy_static! { pub static ref WRITER: Mutex = Mutex::new(Writer { column_position: 0, - color_code: ColorCode::new(Color::Yellow, Color::Black), + color_code: ColorCode::new(Color::White, Color::Black), buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, }); } @@ -142,3 +142,25 @@ pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); } + +#[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().buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } +}