diff --git a/.gitignore b/.gitignore index 96ef6c0..3a6ee37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +/testing/target Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 2c9c03c..c601fdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -conquer-once = "0.2.0" -spinning_top = "0.1.0" +conquer-once = { version = "0.2.0", default-features = false } +spinning_top = { version = "0.1.0", features = ["nightly"] } x86_64 = "0.9.6" diff --git a/testing/.cargo/config b/testing/.cargo/config new file mode 100644 index 0000000..e29dc09 --- /dev/null +++ b/testing/.cargo/config @@ -0,0 +1,5 @@ +[build] +target = "x86_64-bare-metal.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" diff --git a/testing/cargo.toml b/testing/cargo.toml new file mode 100644 index 0000000..b76bfc2 --- /dev/null +++ b/testing/cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "testing" +version = "0.1.0" +authors = ["Ryan Kennedy "] +edition = "2018" + +[dependencies] +bootloader = "0.8.9" +conquer-once = { version = "0.2.0", default-features = false } +spinning_top = { version = "0.1.0", features = ["nightly"] } +uart_16550 = "0.2.4" +x86_64 = "0.9.6" + +[package.metadata.bootimage] +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", + "-display", "none" +] +test-success-exit-code = 33 diff --git a/testing/src/gdt.rs b/testing/src/gdt.rs new file mode 100644 index 0000000..6950df9 --- /dev/null +++ b/testing/src/gdt.rs @@ -0,0 +1,48 @@ +use conquer_once::spin::Lazy; +use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::VirtAddr; + +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +static TSS: Lazy = Lazy::new(|| { + let mut tss = TaskStateSegment::new(); + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { + const STACK_SIZE: usize = 4096; + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); + let stack_end = stack_start + STACK_SIZE; + stack_end + }; + tss +}); + +static GDT: Lazy<(GlobalDescriptorTable, Selectors)> = Lazy::new(|| { + let mut gdt = GlobalDescriptorTable::new(); + let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); + let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); + ( + gdt, + Selectors { + code_selector, + tss_selector, + }, + ) +}); + +struct Selectors { + code_selector: SegmentSelector, + tss_selector: SegmentSelector, +} + +pub fn init() { + use x86_64::instructions::segmentation::set_cs; + use x86_64::instructions::tables::load_tss; + + GDT.0.load(); + unsafe { + set_cs(GDT.1.code_selector); + load_tss(GDT.1.tss_selector); + } +} diff --git a/testing/src/lib.rs b/testing/src/lib.rs new file mode 100644 index 0000000..b4d9928 --- /dev/null +++ b/testing/src/lib.rs @@ -0,0 +1,55 @@ +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] +#![no_std] + +use core::panic::PanicInfo; + +#[macro_use] +pub mod serial; +pub mod gdt; + +#[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); + } +} + +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 {} +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} diff --git a/testing/src/serial.rs b/testing/src/serial.rs new file mode 100644 index 0000000..ceb4531 --- /dev/null +++ b/testing/src/serial.rs @@ -0,0 +1,33 @@ +use conquer_once::spin::Lazy; +use spinning_top::Spinlock; +use uart_16550::SerialPort; + +pub static SERIAL1: Lazy> = Lazy::new(|| { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Spinlock::new(serial_port) +}); + +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/testing/tests/basic_boot.rs b/testing/tests/basic_boot.rs new file mode 100644 index 0000000..1f61114 --- /dev/null +++ b/testing/tests/basic_boot.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![reexport_test_harness_main = "test_main"] +#![test_runner(testing::test_runner)] + +use core::panic::PanicInfo; +use testing::{serial_print, serial_println}; + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + testing::test_panic_handler(info) +} + +#[test_case] +fn basic_boot() { + serial_print!("basic_boot... "); + assert_eq!(0, 0); + serial_println!("[ok]"); +} diff --git a/testing/x86_64-bare-metal.json b/testing/x86_64-bare-metal.json new file mode 100644 index 0000000..23da62b --- /dev/null +++ b/testing/x86_64-bare-metal.json @@ -0,0 +1,15 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-sse,+soft-float" +}