From 76b022be69a415932d90d278f4ee955acf4382f4 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 24 Sep 2019 13:38:25 -0400 Subject: [PATCH] post-04 --- Cargo.lock | 119 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 15 +++++- src/lib.rs | 56 ++++++++++++++++++++ src/macros.rs | 26 +++++++++ src/main.rs | 17 +++++- src/serial.rs | 20 +++++++ src/vga_buffer.rs | 52 +++++++++++++----- tests/basic_boot.rs | 28 ++++++++++ tests/should_panic.rs | 25 +++++++++ 9 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/serial.rs create mode 100644 tests/basic_boot.rs create mode 100644 tests/should_panic.rs diff --git a/Cargo.lock b/Cargo.lock index 12c54ff..913d9ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,23 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit_field" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "blog_os" version = "0.1.0" @@ -7,7 +25,9 @@ dependencies = [ "bootloader 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (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.7.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -15,6 +35,16 @@ name = "bootloader" version = "0.8.1" source = "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.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -23,18 +53,107 @@ dependencies = [ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nodrop" +version = "0.1.13" +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.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.45 (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 = "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 = "spin" version = "0.5.2" source = "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.2.0 (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 = "ux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "volatile" version = "0.2.6" source = "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.2.0 (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 = "x86_64" +version = "0.7.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.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] +"checksum array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" +"checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2" "checksum bootloader 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45dd858bd74a742ec0fe887722952c263abd0825aa8d33a3704917a97d7bd41e" +"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" +"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" +"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 spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +"checksum uart_16550 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b9392f60931fe3bf8f24e0a15ee4f51528770f1d64c48768ab66571334d95b0" +"checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" "checksum volatile 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29" +"checksum x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bb8f09c32a991cc758ebcb9b7984f530095d32578a4e7b85db6ee1f0bbe4c9c6" +"checksum x86_64 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "997837f3913aac8f67164683258756d712376849906c8d609f844084bc03ef84" diff --git a/Cargo.toml b/Cargo.toml index 82b7a36..fc94f7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ edition = "2018" [dependencies] bootloader = "0.8.1" spin = "0.5.2" +uart_16550 = "0.2.0" volatile = "0.2.6" +x86_64 = "0.7.5" [dependencies.lazy_static] version = "1.4.0" @@ -19,4 +21,15 @@ features = ["spin_no_std"] panic = "abort" [profile.release] -panic = "abort" \ No newline at end of file +panic = "abort" + +[package.metadata.bootimage] +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", + "-display", "none" +] +test-success-exit-code = 33 # (0x10 << 1) | 1 + +[[test]] +name = "should_panic" +harness = false \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fe8dde5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,56 @@ +#![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 macros; +pub mod vga_buffer; + +pub fn test_runner(tests: &[&dyn Fn()]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test(); + } + 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 xtest` +#[cfg(test)] +#[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); + } +} \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..59e4940 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,26 @@ +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +/// 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")); + ($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*))); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $(arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} diff --git a/src/main.rs b/src/main.rs index f4f8714..7df5839 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,33 @@ #![no_std] #![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(blog_os::test_runner)] +#![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; -mod vga_buffer; +use blog_os::println; /// 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) -> ! { + blog_os::test_panic_handler(info) +} + #[no_mangle] pub extern "C" fn _start() -> ! { - panic!("Some panic message"); + println!("Hello World{}", "!"); + + #[cfg(test)] + test_main(); loop {} } diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..2da8766 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,20 @@ +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"); +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 7252107..80f91e3 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -3,6 +3,11 @@ use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; +#[cfg(test)] +use crate::{serial_print, serial_println}; + +use crate::println; + #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] @@ -65,7 +70,7 @@ impl Writer { self.new_line(); } - let row = BUFFER_HEIGHT -1; + let row = BUFFER_HEIGHT - 1; let col = self.column_position; let color_code = self.color_code; @@ -118,7 +123,7 @@ impl fmt::Write for Writer { } } -lazy_static!{ +lazy_static! { pub static ref WRITER: Mutex = Mutex::new(Writer { column_position: 0, color_code: ColorCode::new(Color::Yellow, Color::Black), @@ -126,19 +131,38 @@ lazy_static!{ }); } -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); -} - -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); -} - #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); -} \ No newline at end of file +} + +#[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 _ in 0..200 { + println!("test_println_many output"); + } + 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]"); +} diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs new file mode 100644 index 0000000..07dfac4 --- /dev/null +++ b/tests/basic_boot.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(blog_os::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; + +use blog_os::{println, serial_print, serial_println}; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + blog_os::test_panic_handler(info) +} + +#[test_case] +fn test_println() { + serial_print!("test_println... "); + println!("test_println output"); + serial_println!("[ok]"); +} \ No newline at end of file diff --git a/tests/should_panic.rs b/tests/should_panic.rs new file mode 100644 index 0000000..cdbf6f7 --- /dev/null +++ b/tests/should_panic.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use blog_os::{QemuExitCode, exit_qemu, serial_print, serial_println}; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + should_fail(); + serial_println!("[test did not panic]"); + exit_qemu(QemuExitCode:: Failed); + loop {} +} + +fn should_fail() { + serial_print!("should_fail... "); + assert_eq!(0, 1); +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} \ No newline at end of file