├── .gitignore ├── .gdbinit ├── test-types ├── Cargo.toml └── src │ └── lib.rs ├── src ├── util │ ├── mod.rs │ ├── testing.rs │ └── panic.rs ├── main.rs ├── cpu.rs ├── _arch │ ├── aarch64 │ │ ├── linker.ld │ │ └── cpu.rs │ ├── riscv64 │ │ ├── linker.ld │ │ └── cpu.rs │ └── x86_64 │ │ └── cpu.rs └── lib.rs ├── test-macros ├── Cargo.toml └── src │ └── lib.rs ├── .cargo └── config.toml ├── x86_64-unknown-none.json ├── tests ├── 000_kernel_init.rs ├── panic_exit_success │ └── mod.rs └── 001_should_panic.rs ├── Cargo.toml ├── LICENSE ├── Cargo.lock ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea -------------------------------------------------------------------------------- /.gdbinit: -------------------------------------------------------------------------------- 1 | display/i $pc 2 | 3 | # layout asm 4 | # layout regs 5 | set step-mode on 6 | 7 | -------------------------------------------------------------------------------- /test-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-types" 3 | version = "0.1.0" 4 | authors = ["Andre Richter "] 5 | edition = "2018" 6 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Miscellaneous support functions. 4 | 5 | #[cfg(not(test))] 6 | pub mod testing; 7 | 8 | mod panic; 9 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | extern crate libkernel; // yes, this is needed 7 | 8 | #[allow(unused_imports)] 9 | use libkernel::*; 10 | 11 | #[no_mangle] 12 | unsafe fn kernel_init() -> ! { 13 | loop {} 14 | } 15 | -------------------------------------------------------------------------------- /test-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-macros" 3 | version = "0.1.0" 4 | authors = ["Andre Richter "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | proc-macro2 = "1.0.17" 12 | quote = "1.0.6" 13 | syn = { version = "1.0.29", features = ["full"] } 14 | test-types = { path = "../test-types" } 15 | -------------------------------------------------------------------------------- /test-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright (c) 2019-2020 Andre Richter 4 | 5 | //! Types for the `custom_test_frameworks` implementation. 6 | 7 | #![no_std] 8 | 9 | /// Unit test container. 10 | pub struct UnitTest { 11 | /// Name of the test. 12 | pub name: &'static str, 13 | 14 | /// Function pointer to the test. 15 | pub test_func: fn(), 16 | } 17 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-none] 2 | rustflags = ["-Cpanic=abort"] 3 | runner = "bootimage runner" 4 | 5 | [target.aarch64-unknown-none-softfloat] 6 | rustflags = ["-Cpanic=abort", "-Clink-arg=-Tsrc/_arch/aarch64/linker.ld"] 7 | runner = "target/kernel_test_runner.sh aarch64" 8 | 9 | [target.riscv64gc-unknown-none-elf] 10 | rustflags = ["-Cpanic=abort", "-Clink-arg=-Tsrc/_arch/riscv64/linker.ld"] 11 | runner = "target/kernel_test_runner.sh riscv64" 12 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Processor code. 4 | 5 | #[cfg(target_arch = "x86_64")] 6 | #[path = "_arch/x86_64/cpu.rs"] 7 | mod arch_cpu; 8 | 9 | #[cfg(target_arch = "aarch64")] 10 | #[path = "_arch/aarch64/cpu.rs"] 11 | mod arch_cpu; 12 | 13 | #[cfg(target_arch = "riscv64")] 14 | #[path = "_arch/riscv64/cpu.rs"] 15 | mod arch_cpu; 16 | 17 | pub use arch_cpu::*; 18 | 19 | pub fn wait_forever() -> ! { 20 | loop {} 21 | } 22 | -------------------------------------------------------------------------------- /x86_64-unknown-none.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": "32", 8 | "os": "none", 9 | "executables": true, 10 | "linker-flavor": "ld.lld", 11 | "linker": "rust-lld", 12 | "panic-strategy": "abort", 13 | "disable-redzone": true, 14 | "features": "-mmx,-sse,+soft-float" 15 | } -------------------------------------------------------------------------------- /src/_arch/aarch64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | SECTIONS 3 | { 4 | /DISCARD/ : 5 | { 6 | *(.ARM.exidx*) 7 | } 8 | 9 | . = 0x40080000; 10 | 11 | .text : 12 | { 13 | *(.startup) 14 | *(.text*) 15 | } 16 | 17 | .rodata ALIGN(0x1000) : 18 | { 19 | *(.rodata*) 20 | } 21 | 22 | .data ALIGN(0x1000) : 23 | { 24 | *(.data*) 25 | } 26 | 27 | .bss ALIGN(0x1000) : 28 | { 29 | bss_base = .; 30 | *(.bss*) 31 | bss_end = .; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Kernel as library, to facilitate integration testing. 4 | //! ```rust,should_panic 5 | //! assert!(false); 6 | //! ``` 7 | 8 | #![no_std] 9 | #![feature(linkage)] // for weak linkage of panic::_panic_exit 10 | #![feature(naked_functions)] // for _reset 11 | #![feature(asm)] // used throughout archs 12 | 13 | pub mod cpu; 14 | pub mod util; 15 | 16 | #[cfg(test)] 17 | mod test { 18 | #[test] 19 | #[should_panic] 20 | fn panics_ok() { 21 | assert!(false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/000_kernel_init.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Can we execute code in a test and exit success? 4 | 5 | #![feature(custom_test_frameworks)] 6 | #![no_main] 7 | #![no_std] 8 | #![reexport_test_harness_main = "test_main"] 9 | #![test_runner(libkernel::util::testing::test_runner)] 10 | 11 | #[allow(unused_imports)] 12 | #[macro_use] 13 | extern crate libkernel; 14 | 15 | use test_macros::kernel_test; 16 | 17 | #[no_mangle] 18 | fn kernel_init() { 19 | test_main(); 20 | } 21 | 22 | #[kernel_test] 23 | fn kernel_init_runs() { 24 | assert!(true); 25 | } 26 | -------------------------------------------------------------------------------- /src/_arch/riscv64/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH("riscv") 2 | 3 | ENTRY(_start) 4 | SECTIONS 5 | { 6 | . = 0x80000000; 7 | 8 | .text : 9 | { 10 | *(.startup) 11 | *(.text*) 12 | } 13 | 14 | .rodata ALIGN(0x1000) : 15 | { 16 | PROVIDE(_global_pointer = .); 17 | *(.rodata*) 18 | } 19 | 20 | .data ALIGN(0x1000) : 21 | { 22 | *(.sdata*) 23 | *(.data*) 24 | } 25 | 26 | .bss ALIGN(0x1000) : 27 | { 28 | bss_start = .; 29 | *(.sbss*) 30 | *(.bss*) 31 | bss_end = .; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/panic_exit_success/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Change behaviour of QEMU exit code on panic 4 | 5 | extern crate libkernel; 6 | use libkernel::cpu; 7 | 8 | /// Overwrites libkernel's `panic::_panic_exit()` with success version. 9 | #[allow(unreachable_code)] 10 | #[no_mangle] 11 | fn _panic_exit() -> ! { 12 | cpu::qemu_exit_success(); 13 | } 14 | 15 | /// Point of exit for the integration test runner. Completing without panic is failure. 16 | #[allow(unreachable_code)] 17 | #[no_mangle] 18 | fn _test_complete() -> ! { 19 | cpu::qemu_exit_failure(); 20 | } 21 | -------------------------------------------------------------------------------- /tests/001_should_panic.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Can we pass an integration test on panic? 4 | 5 | #![feature(custom_test_frameworks)] 6 | #![no_main] 7 | #![no_std] 8 | #![reexport_test_harness_main = "test_main"] 9 | #![test_runner(libkernel::util::testing::test_runner)] 10 | #![feature(format_args_nl)] // for debug macros 11 | 12 | #[allow(unused_imports)] 13 | #[macro_use] 14 | extern crate libkernel; 15 | 16 | mod panic_exit_success; 17 | 18 | use test_macros::kernel_test; 19 | 20 | #[no_mangle] 21 | fn kernel_init() { 22 | test_main(); 23 | } 24 | 25 | #[kernel_test] 26 | fn panic_test() { 27 | panic!("intentional") 28 | } 29 | -------------------------------------------------------------------------------- /test-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright (c) 2019-2020 Andre Richter 4 | 5 | use proc_macro::TokenStream; 6 | use proc_macro2::Span; 7 | use quote::quote; 8 | use syn::{parse_macro_input, Ident, ItemFn}; 9 | 10 | #[proc_macro_attribute] 11 | pub fn kernel_test(_attr: TokenStream, input: TokenStream) -> TokenStream { 12 | let f = parse_macro_input!(input as ItemFn); 13 | 14 | let test_name = &format!("{}", f.sig.ident.to_string()); 15 | let test_ident = Ident::new( 16 | &format!("{}_TEST_CONTAINER", f.sig.ident.to_string().to_uppercase()), 17 | Span::call_site(), 18 | ); 19 | let test_code_block = f.block; 20 | 21 | quote!( 22 | #[test_case] 23 | const #test_ident: test_types::UnitTest = test_types::UnitTest { 24 | name: #test_name, 25 | test_func: || #test_code_block, 26 | }; 27 | ) 28 | .into() 29 | } 30 | -------------------------------------------------------------------------------- /src/_arch/x86_64/cpu.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Code executed on system reset. 4 | 5 | #[no_mangle] 6 | pub unsafe extern "C" fn _start() -> ! { 7 | extern "Rust" { 8 | fn kernel_init() -> !; 9 | } 10 | kernel_init(); 11 | } 12 | 13 | //-------------------------------------------------------------------------------------------------- 14 | // Testing 15 | //-------------------------------------------------------------------------------------------------- 16 | use qemu_exit::QEMUExit; 17 | 18 | /// Note: io_base must correspond to isa-debug-exit settings Cargo.toml within package.metadata.bootimage 19 | const QEMU_EXIT_HANDLE: qemu_exit::X86 = qemu_exit::X86::new(0xf4, 33); 20 | 21 | /// Make the host QEMU binary execute `exit(1)`. 22 | pub fn qemu_exit_failure() -> ! { 23 | QEMU_EXIT_HANDLE.exit_failure() 24 | } 25 | 26 | /// Make the host QEMU binary execute `exit(33)`. 27 | pub fn qemu_exit_success() -> ! { 28 | QEMU_EXIT_HANDLE.exit_success() 29 | } 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel" 3 | version = "0.1.0" 4 | authors = ["Alister Lee "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "libkernel" 9 | # NOTE: exclude cargo tests --lib from cargo tests --tests; see make tests 10 | test = false 11 | 12 | [[bin]] 13 | name = "kernel" 14 | test = false 15 | 16 | # Note: profile..panic is set in target definition and .cargo/config due to 17 | # https://github.com/rust-lang/cargo/issues/7359 18 | # [profile.dev|release] 19 | # panic = "abort" 20 | 21 | [dependencies] 22 | test-types = { path = "test-types" } 23 | qemu-exit = "^1" 24 | 25 | [dev-dependencies] 26 | test-macros = { path = "test-macros" } 27 | 28 | [target.'cfg(target_arch = "aarch64")'.dependencies] 29 | cortex-a = "^5.1" 30 | 31 | [target.'cfg(target_arch = "x86_64")'.dependencies] 32 | bootloader = "^0.9" 33 | 34 | [package.metadata.bootimage] 35 | test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"] 36 | test-success-exit-code = 33 # (0x10 << 1) | 1 37 | -------------------------------------------------------------------------------- /src/util/testing.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Support for integration testing, shared between tests. 4 | 5 | /// Point of exit for the integration test runner. By default, completing without panic is success. 6 | /// 7 | /// It is linked weakly, so that integration tests can overload it to exit `QEMU` with failure when 8 | /// a panic is the correct behaviour for the test. 9 | #[cfg(target_vendor = "unknown")] 10 | #[linkage = "weak"] 11 | #[no_mangle] 12 | #[allow(unreachable_code)] 13 | fn _test_complete() -> ! { 14 | crate::cpu::qemu_exit_success(); 15 | } 16 | 17 | /// The runner for integration tests. 18 | /// 19 | /// NOTE: This is not used for unit tests. 20 | #[cfg(target_vendor = "unknown")] 21 | pub fn test_runner(tests: &[&test_types::UnitTest]) { 22 | // info!("running {} tests", tests.len()); 23 | for test in tests { 24 | // info!("testing {}", test.name); 25 | (test.test_func)(); 26 | } 27 | // info!("test result: ok."); 28 | 29 | _test_complete(); 30 | } 31 | -------------------------------------------------------------------------------- /src/_arch/aarch64/cpu.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Code executed on system reset. 4 | 5 | #[link_section = ".startup"] 6 | #[no_mangle] 7 | #[naked] 8 | /// Entry point for OS 9 | /// 10 | /// Positioned at magic address by linker.ld. 11 | /// 12 | /// NOTE: must not use stack before SP set. 13 | pub unsafe extern "C" fn _start() -> ! { 14 | #[allow(dead_code)] 15 | extern "Rust" { 16 | fn kernel_init() -> !; 17 | } 18 | 19 | asm!( 20 | " mov x1, 0x50000000 // top of 256MB memory 21 | mov sp, x1 22 | b kernel_init", 23 | options(noreturn) 24 | ); 25 | } 26 | 27 | //-------------------------------------------------------------------------------------------------- 28 | // Testing 29 | //-------------------------------------------------------------------------------------------------- 30 | use qemu_exit::QEMUExit; 31 | 32 | pub const QEMU_EXIT_HANDLE: qemu_exit::AArch64 = qemu_exit::AArch64::new(); 33 | 34 | /// Make the host QEMU binary execute `exit(1)`. 35 | pub fn qemu_exit_failure() -> ! { 36 | QEMU_EXIT_HANDLE.exit_failure() 37 | } 38 | 39 | /// Make the host QEMU binary execute `exit(0)`. 40 | pub fn qemu_exit_success() -> ! { 41 | QEMU_EXIT_HANDLE.exit_success() 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/util/panic.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Panic handling. 4 | 5 | use crate::cpu; 6 | 7 | /// The point of exit for the "standard" (non-testing) `libkernel`. 8 | /// 9 | /// This code will be used by the release kernel binary and the `integration tests`. It is linked 10 | /// weakly, so that the integration tests can overload it to exit `QEMU` instead of spinning 11 | /// forever. 12 | #[linkage = "weak"] 13 | #[no_mangle] 14 | #[allow(unreachable_code)] 15 | fn _panic_exit() -> ! { 16 | #[cfg(target_vendor = "unknown")] 17 | cpu::qemu_exit_failure(); 18 | 19 | cpu::wait_forever(); 20 | } 21 | 22 | /// Log panic information and abnormal-exit emulator (or hang) 23 | #[cfg(not(test))] 24 | #[allow(unreachable_code)] 25 | #[panic_handler] 26 | fn panic(_info: &core::panic::PanicInfo) -> ! { 27 | // use crate::debug::Level; 28 | // 29 | // match info.location() { 30 | // None => log!( 31 | // Level::Fatal, 32 | // "Panic: {}", 33 | // info.message().unwrap_or(&format_args!("unknown")) 34 | // ), 35 | // Some(loc) => log!( 36 | // Level::Fatal, 37 | // "Panic: {} (at {}:{})", 38 | // info.message().unwrap_or(&format_args!("unknown")), 39 | // loc.file(), 40 | // loc.line() 41 | // ), 42 | // }; 43 | 44 | _panic_exit(); 45 | 46 | cpu::wait_forever(); 47 | } 48 | -------------------------------------------------------------------------------- /src/_arch/riscv64/cpu.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | //! Code executed on system reset. 4 | 5 | #[link_section = ".startup"] 6 | #[no_mangle] 7 | #[naked] 8 | /// Entry point for OS 9 | /// 10 | /// Positioned at magic address by linker.ld. 11 | /// 12 | /// NOTE: must not use stack before SP set. 13 | pub unsafe extern "C" fn _start() -> ! { 14 | asm!( 15 | " csrr t0, mhartid 16 | bnez t0, 4f 17 | csrw satp, zero 18 | .option push 19 | .option norelax 20 | la gp, _global_pointer 21 | .option pop 22 | li sp, 0x90000000 23 | li t0, (0b11 << 11) | (1 << 7) | (1 << 3) 24 | csrw mstatus, t0 25 | la t1, kernel_init 26 | csrw mepc, t1 27 | la t2, asm_trap_vector 28 | csrw mtvec, t2 29 | li t3, (1 << 3) | (1 << 7) | (1 << 11) 30 | csrw mie, t3 31 | la ra, 4f 32 | mret 33 | 4: 34 | asm_trap_vector: 35 | wfi 36 | j 4b", 37 | options(noreturn) 38 | ); 39 | } 40 | 41 | //-------------------------------------------------------------------------------------------------- 42 | // Testing 43 | //-------------------------------------------------------------------------------------------------- 44 | use qemu_exit::QEMUExit; 45 | 46 | const QEMU_EXIT_HANDLE: qemu_exit::RISCV64 = qemu_exit::RISCV64::new(0x10_0000); 47 | 48 | /// Make the host QEMU binary execute `exit(1)`. 49 | pub fn qemu_exit_failure() -> ! { 50 | QEMU_EXIT_HANDLE.exit_failure() 51 | } 52 | 53 | /// Make the host QEMU binary execute `exit(0)`. 54 | pub fn qemu_exit_success() -> ! { 55 | QEMU_EXIT_HANDLE.exit_success() 56 | } 57 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bootloader" 5 | version = "0.9.11" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "83732ad599045a978528e4311539fdcb20c30e406b66d1d08cd4089d4fc8d90f" 8 | 9 | [[package]] 10 | name = "cortex-a" 11 | version = "5.1.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "dc37a4862a4f5b40df96f5e0f3bd4ce6a1bd8bb59a965cd476844673faf83896" 14 | dependencies = [ 15 | "register", 16 | ] 17 | 18 | [[package]] 19 | name = "kernel" 20 | version = "0.1.0" 21 | dependencies = [ 22 | "bootloader", 23 | "cortex-a", 24 | "qemu-exit", 25 | "test-macros", 26 | "test-types", 27 | ] 28 | 29 | [[package]] 30 | name = "proc-macro2" 31 | version = "1.0.24" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 34 | dependencies = [ 35 | "unicode-xid", 36 | ] 37 | 38 | [[package]] 39 | name = "qemu-exit" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "b73ae13954572c7ca0ec48ba9fe6a59c0392066eba62f8cb384ffd5addf538c5" 43 | 44 | [[package]] 45 | name = "quote" 46 | version = "1.0.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 49 | dependencies = [ 50 | "proc-macro2", 51 | ] 52 | 53 | [[package]] 54 | name = "register" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "faf386c0c48fea132b7fbbf71c32d1b1d62e7c59043bbabdd15c9ed9f254517e" 58 | dependencies = [ 59 | "tock-registers", 60 | ] 61 | 62 | [[package]] 63 | name = "syn" 64 | version = "1.0.55" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a" 67 | dependencies = [ 68 | "proc-macro2", 69 | "quote", 70 | "unicode-xid", 71 | ] 72 | 73 | [[package]] 74 | name = "test-macros" 75 | version = "0.1.0" 76 | dependencies = [ 77 | "proc-macro2", 78 | "quote", 79 | "syn", 80 | "test-types", 81 | ] 82 | 83 | [[package]] 84 | name = "test-types" 85 | version = "0.1.0" 86 | 87 | [[package]] 88 | name = "tock-registers" 89 | version = "0.6.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "f521a79accce68c417c9c77ce22108056b626126da1932f7e2e9b5bbffee0cea" 92 | 93 | [[package]] 94 | name = "unicode-xid" 95 | version = "0.2.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 98 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HOST = $(shell rustc -Vv | grep host: | awk '/host: / {print $$2; exit}') 2 | 3 | BINTOOLS = rust 4 | OBJCOPY = $(BINTOOLS)-objcopy 5 | OBJDUMP = $(BINTOOLS)-objdump 6 | 7 | # This would be better with -Zmultitarget, except that unstable.build-std can't be set per target in .cargo/config.toml. 8 | x86_64_target = --target x86_64-unknown-none.json -Zunstable-options -Zbuild-std=core,compiler_builtins -Zbuild-std-features=compiler-builtins-mem 9 | riscv64_target = --target riscv64gc-unknown-none-elf 10 | aarch64_target = --target aarch64-unknown-none-softfloat 11 | 12 | doc_test: 13 | cargo test --doc --target=$(HOST) 14 | 15 | unit_test: doc_test 16 | cargo test --lib --target=$(HOST) 17 | 18 | build_all: 19 | cargo bootimage $(x86_64_target) 20 | cargo build $(riscv64_target) 21 | cargo build $(aarch64_target) 22 | 23 | define KERNEL_TEST_RUNNER 24 | #!/usr/local/bin/fish 25 | 26 | $(OBJCOPY) -O binary $$argv[2] $$argv[2].bin 27 | 28 | switch $$argv[1] 29 | case aarch64 30 | qemu-system-aarch64 -M virt -cpu cortex-a53 -m 256M -nographic -semihosting -kernel $$argv[2].bin > $$argv[2].out 31 | case riscv64 32 | qemu-system-riscv64 -M virt -cpu rv64 -smp 1 -m 256M -nographic -d guest_errors,unimp -bios none -kernel $$argv[2] > $$argv[2].out 33 | case x86_64 34 | echo Error: Handled by bootimage runner. 35 | end 36 | 37 | set result $$status 38 | 39 | if test $$result -ne 0 40 | # cat $$argv[2].out 41 | # $(OBJDUMP) -dS $$argv[2] > $$argv[2].code 42 | # $(OBJDUMP) -d $$argv[2] > $$argv[2].s 43 | end 44 | 45 | exit $$result 46 | endef 47 | 48 | export KERNEL_TEST_RUNNER 49 | target/kernel_test_runner.sh: Makefile 50 | @mkdir -p target 51 | @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh 52 | @chmod +x target/kernel_test_runner.sh 53 | 54 | test_runner: target/kernel_test_runner.sh 55 | 56 | test_all: unit_test test_runner 57 | cargo test $(x86_64_target) 58 | cargo test $(riscv64_target) 59 | cargo test $(aarch64_target) 60 | 61 | qemu_x86_64: build_all 62 | qemu-system-x86_64 -m 256M -nographic -s -S -device loader,file=target/x86_64-unknown-none/debug/kernel 63 | 64 | gdb_x86_64: build_all 65 | gdb -iex 'target remote localhost:1234' 66 | 67 | qemu_aarch64: build_all 68 | qemu-system-aarch64 -M virt -cpu cortex-a53 -m 256M -nographic -semihosting -s -S -kernel target/aarch64-unknown-none-softfloat/debug/kernel 69 | 70 | gdb_aarch64: build_all 71 | gdb -iex 'file target/aarch64-unknown-none-softfloat/debug/kernel' -iex 'target remote localhost:1234' 72 | 73 | qemu_riscv64: build_all 74 | qemu-system-riscv64 -machine virt -cpu rv64 -smp 4 -m 256M -nographic -serial mon:stdio -bios none -device virtio-rng-device -device virtio-gpu-device -device virtio-net-device -device virtio-tablet-device -device virtio-keyboard-device -s -S -kernel target/riscv64gc-unknown-none-elf/debug/kernel 75 | 76 | gdb_riscv64: build_all 77 | gdb -iex 'set architecture riscv:rv64' -iex 'target remote localhost:1234' 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-osdev-jumpstart 2 | Rust, cargo and QEMU setup for multi-architecture OS development. 3 | 4 | ## Goal 5 | This repo should give you a boost in starting a bare-metal OS development project in Rust. You can go backwards from here if you just want something working as a starting point (but have different ideas for foundation), or you can go forward and exploit the testing setup. 6 | 7 | ## Sources 8 | These three are the great Rust bare-metal OS development blogs, and I have borrowed liberally 9 | from their ideas and code. 10 | 11 | * [Philipp Oppermann](http://os.phil-opp.com/) - x86_64 12 | * [Andre Richter](https://github.com/rust-embedded/rust-raspi3-tutorial) - Arm 13 | * [Stephen Marz](http://osblog.stephenmarz.com/index.html) - RiscV 14 | 15 | ## Features 16 | 17 | * Doc tests and unit tests run your development host OS for speed. 18 | * Integration tests are run under QEMU with ```cargo test --target ```. 19 | * Integration tests utilise [custom test frameworks](https://rust-lang.github.io/rfcs/2318-custom-test-frameworks.html) feature for convenient test suites. 20 | * QEMU passes back test result to cargo using [qemu_exit](https://crates.io/crates/qemu-exit). 21 | * ```make test_all``` runs doc, unit and integration tests for all three architectures. 22 | * QEMU definitions largely follow the platform-specific blogs above. 23 | 24 | ## Example usage 25 | 26 | ``` 27 | $ make test_all 28 | cargo test --doc --target=x86_64-apple-darwin 29 | Finished test [unoptimized + debuginfo] target(s) in 0.01s 30 | Doc-tests libkernel 31 | 32 | running 1 test 33 | test src/lib.rs - (line 4) ... ok 34 | 35 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.47s 36 | 37 | cargo test --lib --target=x86_64-apple-darwin 38 | Finished test [unoptimized + debuginfo] target(s) in 0.01s 39 | Running target/x86_64-apple-darwin/debug/deps/libkernel-910fb71f27dff6a6 40 | 41 | running 1 test 42 | test test::panics_ok ... ok 43 | 44 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 45 | 46 | cargo test --target x86_64-unknown-none.json -Zunstable-options -Zbuild-std=core,compiler_builtins -Zbuild-std-features=compiler-builtins-mem 47 | Finished test [unoptimized + debuginfo] target(s) in 0.04s 48 | Running target/x86_64-unknown-none/debug/deps/000_kernel_init-f9ceeb28812f2f47 49 | Building bootloader 50 | Compiling bootloader v0.9.11 (~/.cargo/registry/src/github.com-1ecc6299db9ec823/bootloader-0.9.11) 51 | Finished release [optimized + debuginfo] target(s) in 1.14s 52 | Running: `qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/deps/bootimage-000_kernel_init-f9ceeb28812f2f47.bin -no-reboot -device isa 53 | Running target/x86_64-unknown-none/debug/deps/001_should_panic-6a56cc56ef6b6077 54 | Building bootloader 55 | Compiling bootloader v0.9.11 (~/.cargo/registry/src/github.com-1ecc6299db9ec823/bootloader-0.9.11) 56 | Finished release [optimized + debuginfo] target(s) in 1.11s 57 | Running: `qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/deps/bootimage-001_should_panic-6a56cc56ef6b6077.bin -no-reboot -device is 58 | cargo test --target riscv64gc-unknown-none-elf 59 | Finished test [unoptimized + debuginfo] target(s) in 0.01s 60 | Running target/riscv64gc-unknown-none-elf/debug/deps/000_kernel_init-af9782dbeaca1763 61 | Running target/riscv64gc-unknown-none-elf/debug/deps/001_should_panic-db251fb771fa34a8 62 | cargo test --target aarch64-unknown-none-softfloat 63 | Finished test [unoptimized + debuginfo] target(s) in 0.01s 64 | Running target/aarch64-unknown-none-softfloat/debug/deps/000_kernel_init-13b9d95425b9c0bb 65 | Running target/aarch64-unknown-none-softfloat/debug/deps/001_should_panic-e72d04dcc4d61eda 66 | ``` 67 | 68 | ## Known limitations and extensions 69 | 70 | Pull requests welcome! 71 | 72 | * Only tested on Mac OS 11 Big Sur. 73 | * Running the doc and unit tests on the host means those tests can't interact with hardware. Even to unit test the architecture-independent code would require either a mock ```_arch/test``` or lots of ```cfg(test)``` peppered through-out. 74 | * Utilising ```-Zmultitarget``` would be an improvement, but the ```-Zbuild-std``` options required for ```x86_64``` can't be pushed into ```[target.x86_64...]``` inside ```.cargo/config.toml```. You would probably still need ```make``` as a shorthand. 75 | * Logging would be a great inclusion, but might be quite opinionated with quite a bit of serial device code. 76 | * Device tree management in the Makefile for RiscV and Arm. 77 | 78 | ## Other influences 79 | 80 | * [Julia Evans](http://jvns.ca/blog/2014/03/21/my-rust-os-will-never-be-finished/) 81 | * [Bodil Stokke](https://skillsmatter.com/skillscasts/4484-build-your-own-lisp-for-great-justice) 82 | * [Lucas Hartmann](http://interim-os.com/) We follow in his footsteps! 83 | * [Dawid Ciężarkiewicz](https://github.com/dpc/titanos) And Dawid's too! 84 | * [rCoreOS](https://github.com/rcore-os/rCore) Multi-arch 85 | * [Ilya Kartashov](https://lowenware.com/leos/) 86 | * [Redox OS](https://www.redox-os.org) --------------------------------------------------------------------------------