//! The kernel as a library. //! //! Everything lives here so that both the bootable binary (src/main.rs) and //! the integration tests (tests/*.rs, each its own tiny kernel) can use it. //! //! Testing: Rust's normal test harness needs `std`, so we use the nightly //! `custom_test_frameworks` feature. `cargo test` builds each test kernel, //! boots it in QEMU (see `test-args` in Cargo.toml), results go to serial, //! and the kernel exits QEMU through the `isa-debug-exit` device. #![no_std] #![cfg_attr(test, no_main)] // when testing the lib itself, it is a kernel too #![feature(custom_test_frameworks)] // nightly: our own test runner #![feature(abi_x86_interrupt)] // nightly: `extern "x86-interrupt" fn` #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] // name the generated test entry fn // `alloc` = the heap-using half of the standard library (Box, Vec, Rc, ...). // It works once we register a global allocator (see allocator.rs). extern crate alloc; use core::panic::PanicInfo; pub mod acpi; // ACPI tables: just enough to list the CPUs (MADT) pub mod allocator; // heap: maps heap pages + the #[global_allocator] pub mod apic; // local APIC: per-CPU timer, inter-processor interrupts pub mod block; // block devices: BlockDevice trait, virtio-blk, partitions pub mod dev; // /dev: null, random, hvc0, fb0 pub mod elf; // ELF64 loader for user programs pub mod errno; // Linux-compatible error numbers pub mod fbcon; // text console drawn on the framebuffer pub mod font; // 8x16 bitmap font (Spleen) for fbcon pub mod fs; // VFS: Inode/FileSystem/File traits, mount table, ramfs pub mod gdt; // GDT (kernel + user segments) + TSS (RSP0, double-fault stack) pub mod interrupts; // IDT, exception handlers, PIC, timer + keyboard pub mod net; // networking: virtio-net driver + smoltcp TCP/IP, sockets pub mod memory; // paging, frame allocator, page tables, shared-frame counts pub mod mm; // a process's memory map: regions, demand paging, copy-on-write pub mod pci; // PCI bus enumeration (config space via ports 0xCF8/0xCFC) pub mod process; // processes: fork, execve, exit, wait4, process groups pub mod serial; // COM1 serial port pub mod signal; // signals: kill, handlers, signal frames, default actions pub mod smp; // multi-core: start the other CPUs, per-CPU data pub mod stack; // kernel stacks with guard pages pub mod syscall; // syscall/sysret entry + system call implementations pub mod task; // cooperative multitasking: async tasks + executor pub mod trap; // TrapFrame: saved user registers, the common return to ring 3 pub mod time; // microsecond clock (TSC calibrated against the PIT) pub mod thread; // threads + the multi-core scheduler (sleep/wake, wait queues) pub mod tty; // console: keyboard line discipline + fd 0/1/2 pub mod vga_buffer; // the console: VGA text mode, or fbcon on a GPU pub mod virtio; // modern virtio transport + virtio-rng, -console, -gpu /// Set up everything interrupt-related. Order matters: /// GDT/TSS first (the IDT refers to the IST stack), then the IDT, then the /// PICs, and only then enable interrupts (`sti`). pub fn init() { gdt::init(); syscall::init(); interrupts::init_idt(); unsafe { interrupts::PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); } /// Paging + heap + the global frame allocator, in one call (what the test /// kernels need; src/main.rs does the same steps one by one with demos). pub fn init_memory(boot_info: &'static bootloader::BootInfo) { let phys_mem_offset = x86_64::VirtAddr::new(boot_info.physical_memory_offset); let mut mapper = unsafe { memory::init(phys_mem_offset) }; let mut frame_allocator = unsafe { memory::BootInfoFrameAllocator::init(&boot_info.memory_map) }; allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); memory::init_frame_allocator(frame_allocator); } /// Multi-core bring-up: the local APIC and its timer (preemption), CPU 0's /// idle thread, and every other CPU the ACPI tables list (see smp.rs). /// Needs `init` and `init_memory`. Returns the number of running CPUs. pub fn init_smp() -> usize { smp::init() } /// Storage bring-up: scan the PCI bus, start the disk drivers, read the /// partition tables and mount FAT volumes under /mnt. Needs the heap, the /// frame allocator and `fs::init()`. Returns (PCI devices, log lines). pub fn init_storage() -> (alloc::vec::Vec, alloc::vec::Vec) { let devices = pci::scan(); let mut log = block::init(&devices); log.extend(fs::mount_disks()); (devices, log) } /// The stage-13 virtual devices: virtio-rng, virtio-console (hvc0) and /// virtio-gpu. With a GPU the text console moves onto its framebuffer. /// Call before `init_net` (smoltcp's random seed comes from the rng). /// Returns boot log lines. pub fn init_drivers() -> alloc::vec::Vec { let mut log = virtio::init(&pci::scan()); if let Some(fb) = virtio::gpu::framebuffer() { let (cols, rows) = vga_buffer::use_framebuffer(fb); log.push(alloc::format!("fbcon: text console on the framebuffer, {}x{} characters", cols, rows)); } log } /// Networking: find the virtio-net card, start smoltcp, DHCP and the netd /// thread. Needs the scheduler (call after `init_smp`). Returns the boot /// log line, or None without a network card. pub fn init_net() -> Option { net::init(&pci::scan()) } /// Print a line to *both* the VGA screen and the serial port. #[macro_export] macro_rules! kprintln { ($($arg:tt)*) => {{ $crate::println!($($arg)*); $crate::serial_println!($($arg)*); }}; } /// Sleep the CPU until the next interrupt, forever (cheaper than `loop {}`, /// which would keep a host CPU core at 100%). pub fn hlt_loop() -> ! { loop { x86_64::instructions::hlt(); } } /// Exit codes for QEMU's `isa-debug-exit` device. QEMU exits with /// status `(value << 1) | 1`, so Success = 0x10 -> shell exit code 33. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { Success = 0x10, Failed = 0x11, } /// Write to I/O port 0xf4 (where we put `isa-debug-exit`) to quit QEMU. 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); } } // --------------------------------------------------------------------------- // Test framework // --------------------------------------------------------------------------- /// Anything callable as a test: prints its name, runs it, prints [ok]. /// (If it panics, the panic handler prints [failed] instead.) pub trait Testable { fn run(&self); } impl Testable for T { fn run(&self) { serial_print!("{}...\t", core::any::type_name::()); self(); serial_println!("[ok]"); } } /// Called with every `#[test_case]` function in the crate being tested. pub fn test_runner(tests: &[&dyn Testable]) { serial_println!("Running {} tests", tests.len()); for test in tests { test.run(); } exit_qemu(QemuExitCode::Success); } /// Panic handler for test kernels: report the failure and exit QEMU. pub fn test_panic_handler(info: &PanicInfo) -> ! { serial_println!("[failed]\n"); serial_println!("Error: {}\n", info); exit_qemu(QemuExitCode::Failed); hlt_loop(); } // When running `cargo test --lib`, the library itself is booted as a kernel: #[cfg(test)] bootloader::entry_point!(test_kernel_main); #[cfg(test)] fn test_kernel_main(boot_info: &'static bootloader::BootInfo) -> ! { init(); init_memory(boot_info); // a heap, for unit tests that use Vec (btrfs, lzo) init_smp(); test_main(); hlt_loop(); } #[cfg(test)] #[panic_handler] fn panic(info: &PanicInfo) -> ! { test_panic_handler(info) } #[test_case] fn trivial_assertion() { assert_eq!(1 + 1, 2); }