From 4d923d2bc7a40a9c2d86c1dee35d83abc3c3bc99 Mon Sep 17 00:00:00 2001 From: ishanjain28 Date: Thu, 2 May 2019 22:43:39 +0530 Subject: [PATCH] Added unit tests, Serial interface to talk to qemu device and log output from qemu device to console --- Cargo.lock | 73 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 +++++++++-- src/main.rs | 67 ++++++++++++++++++++++++++++++++++++++++--- src/serial.rs | 38 ++++++++++++++++++++++++ src/vga_buffer.rs | 34 ++++++++++++++++++++++ 5 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 src/serial.rs diff --git a/Cargo.lock b/Cargo.lock index f74b595..047f3a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,16 @@ dependencies = [ "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cast" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fixedvec" version = "0.2.3" @@ -82,7 +92,9 @@ dependencies = [ "bootloader 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "uart_16550 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "volatile 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -128,6 +140,16 @@ name = "rand_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "raw-cpuid" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -144,6 +166,27 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "skeptic" version = "0.5.0" @@ -172,6 +215,15 @@ dependencies = [ "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uart_16550" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-width" version = "0.1.5" @@ -224,6 +276,19 @@ dependencies = [ "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "x86_64" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "xmas-elf" version = "0.6.2" @@ -242,6 +307,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1fa8ad26490b0a5cfec99089952250301b6716cdeaa7c9ab229598fb82ab66" "checksum bootloader 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8654b1ebbd38d2a8687a451ad53466d01b5edc9d75ec63d676525a6103d77151" +"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" +"checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d" "checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" "checksum font8x8 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b81d84c3c978af7d05d31a2198af4b9ba956d819d15d8f6d58fc150e33f8dc1f" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" @@ -255,12 +322,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum uart_16550 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b9392f60931fe3bf8f24e0a15ee4f51528770f1d64c48768ab66571334d95b0" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" @@ -269,5 +341,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum x86_64 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f9258d7e2dd25008d69e8c9e9ee37865887a5e1e3d06a62f1cb3f6c209e6f177" +"checksum x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bb8f09c32a991cc758ebcb9b7984f530095d32578a4e7b85db6ee1f0bbe4c9c6" "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml index ca286d2..3f9efda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" volatile = "0.2.3" spin = "0.4.6" bootloader = "0.6.0" +x86_64 = "0.5.2" +uart_16550 = "0.2.0" [dependencies.lazy_static] version = "1.0" @@ -15,9 +17,17 @@ features = ["spin_no_std"] [package.metadata.bootimage] default-target = "neptune-x86_64.json" - -[build] -target = "neptune-x86_64.json" +# iobase specifies the port address on which device should be available +# iosize specifies the port size +# https://wiki.osdev.org/I/O_Ports#The_list +# +# Redirect serial output to console/terminal stdout +# +# Since, We are getting all test output in console, No need to create window when running tests +test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "mon:stdio", "-display", "none"] +# test framework assumes every response code other than 0 to be an error +# Change that default to 33 (0x10 << 1) | 1 +test-success-exit-code = 33 [profile.dev] panic = "abort" diff --git a/src/main.rs b/src/main.rs index 699d454..6ab467d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,87 @@ -#![no_std] // don't include rust std lib -#![no_main] // disable all rust entry point +// don't include rust std lib +#![no_std] +// disable all rust entry point +#![no_main] +// Utilise custom test frameworks feature in rust, Since the default one doesn't works in no_std environments +#![feature(custom_test_frameworks)] +// Specify the runner, aka, The function that will execute all the tests +#![test_runner(crate::test_runner)] +// custom test frameworks creates an entry point called `main`. Since we are using no_main and have +// set a custom entry point, The entry point created by custom test framework gets ignored. +// This changes the name of the entry point to test_main which can then be called in _start +#![reexport_test_harness_main = "test_main"] #[macro_use] extern crate lazy_static; +mod serial; mod vga_buffer; use core::panic::PanicInfo; -use vga_buffer::{Buffer, Color, ColorCode, Writer}; /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] pub fn panic(info: &PanicInfo) -> ! { println!("{}", info); loop {} } +#[cfg(test)] +#[panic_handler] +pub fn panic(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +// u32 because iosize is set to 4 bytes +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + #[no_mangle] // don't change the name of function in symbol table pub extern "C" fn _start() -> ! { use core::fmt::Write; vga_buffer::WRITER.lock().write_str("Hello again").unwrap(); println!(", some numbers: {} {}", 42, 1.337); - panic!("panic message goes right here"); + #[cfg(test)] + test_main(); loop {} } + +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + // iobase address + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + println!("Running {} tests", tests.len()); + serial_println!("Running {} tests", tests.len()); + + for test in tests { + test(); + } + exit_qemu(QemuExitCode::Success); +} + +#[test_case] +fn trivial_assertion() { + print!("trivial assertion... "); + serial_print!("trivial assertion... "); + assert_eq!(1, 1); + println!("[ok]"); + serial_println!("[ok]"); +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..dcd4fde --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,38 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use uart_16550::SerialPort; + +lazy_static! { + pub static ref SERIAL: Mutex = { + // 0x3F8 is a standard port addr for UART. + // It requires multiple ports but SerialPort implementation calculates rest of the ports + // automatically + 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; + SERIAL + .lock() + .write_fmt(args) + .expect("Printing to serial failed!"); +} + +// Print to Serial Interface +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +#[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 8f09785..5256fdb 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -2,6 +2,9 @@ use core::fmt; use spin::Mutex; use volatile::Volatile; +#[cfg(test)] +use crate::{serial_print, serial_println}; + #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] @@ -151,3 +154,34 @@ pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); } + +#[test_case] +fn test_println_simple() { + serial_print!("test_println.. "); + println!("test_println_simple output"); + serial_println!("[ok]"); +} + +#[test_case] +fn test_println_many() { + serial_print!("test_println_many.. "); + for _i in 0..200 { + print!("test_println_many output goes here"); + } + serial_println!("[ok]"); +} + +#[test_case] +fn test_println_output() { + serial_print!("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); + } + + serial_println!("[ok]"); +}