//! The bootable kernel binary. All the real code is in the library //! (src/lib.rs and friends); this file just runs the demos in order. //! //! Boot flow: BIOS -> `bootloader` crate (switches to 64-bit long mode, sets up //! paging, maps all physical memory) -> `kernel_main` below, with a //! `BootInfo` describing the memory map. #![no_std] // don't link the Rust standard library (it needs an OS) #![no_main] // no normal `main`: the bootloader calls our entry point extern crate alloc; // Box, Vec, Rc... once the heap is set up use alloc::{boxed::Box, rc::Rc, vec, vec::Vec}; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use rust_kernel::{allocator, kprintln, memory, println, serial_println}; use x86_64::{VirtAddr, structures::paging::{Page, Translate}}; // `entry_point!` defines the real `_start` for us and checks at compile time // that `kernel_main` has the right signature (takes `&'static BootInfo`). entry_point!(kernel_main); fn kernel_main(boot_info: &'static BootInfo) -> ! { kprintln!("Hello from Tyler's Rust kernel!"); rust_kernel::init(); // GDT, IDT, PICs, enable interrupts // Fire a breakpoint exception on purpose: the handler prints a message, // returns, and we carry on as if nothing happened. x86_64::instructions::interrupts::int3(); kprintln!("Survived the breakpoint, still running."); #[cfg(feature = "stack-overflow")] { serial_println!("Overflowing the stack on purpose..."); stack_overflow(); } paging_demo(boot_info); kprintln!("Type something! (keyboard echo)"); serial_println!("Timer + keyboard interrupts enabled."); // Nothing else to do: sleep until the next interrupt, forever. The timer // and keyboard handlers do their work in between. rust_kernel::hlt_loop(); } /// Chapters "Introduction to Paging", "Paging Implementation", "Heap Allocation". fn paging_demo(boot_info: &'static BootInfo) { use x86_64::registers::control::Cr3; // --- Introduction to Paging ------------------------------------------- // CR3 = physical address of the level-4 page table currently in use. let (level_4_frame, _) = Cr3::read(); kprintln!("[paging] CR3 (level 4 table) at phys {:#x}", level_4_frame.start_address().as_u64()); #[cfg(feature = "page-fault")] { kprintln!("[paging] writing to unmapped address 0xdeadbeaf..."); unsafe { *(0xdeadbeaf as *mut u8) = 42 }; // -> page fault handler } // --- Paging Implementation -------------------------------------------- let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); serial_println!("[paging] physical memory mapped at virt {:#x}", phys_mem_offset.as_u64()); serial_println!( "[memory] {} memory map regions, {} KiB usable RAM", boot_info.memory_map.iter().count(), memory::usable_memory_bytes(&boot_info.memory_map) / 1024 ); // Safety: the bootloader mapped all physical memory at that offset, and // this is the only place we call `init`. let mut mapper = unsafe { memory::init(phys_mem_offset) }; let mut frame_allocator = unsafe { memory::BootInfoFrameAllocator::init(&boot_info.memory_map) }; // Translate some virtual addresses to physical ones by walking the tables. let addresses = [ 0xb8000, // VGA buffer: identity-mapped 0x201008, // somewhere in our kernel's code 0x0100_0020_1a10, // somewhere in our stack boot_info.physical_memory_offset, // -> physical address 0 ]; for &address in &addresses { let virt = VirtAddr::new(address); let phys = mapper.translate_addr(virt); serial_println!("[paging] translate {:?} -> {:?}", virt, phys); } // Map a brand-new page (0xdeadbeaf000, previously unmapped) to the VGA // buffer frame, then write "New!" (white on red) through it. let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); memory::create_example_mapping(page, &mut mapper, &mut frame_allocator); let page_ptr: *mut u64 = page.start_address().as_mut_ptr(); // Offset 400 u64s = 3200 bytes = row 20 of the screen (160 bytes/row). unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e) }; kprintln!("[paging] mapped page 0xdeadbeaf000 -> VGA frame, wrote \"New!\" through it"); // --- Heap Allocation -------------------------------------------------- allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); kprintln!( "[heap] {} KiB heap at {:#x} (fixed-size-block allocator)", allocator::HEAP_SIZE / 1024, allocator::HEAP_START ); let heap_value = Box::new(41); kprintln!("[heap] Box::new(41) = {} at {:p}", heap_value, heap_value); let mut vec = Vec::new(); for i in 0..500 { vec.push(i); } let sum: u64 = vec.iter().sum(); kprintln!("[heap] Vec of 500 numbers at {:p}, sum = {}", vec.as_ptr(), sum); let reference_counted = Rc::new(vec![1, 2, 3]); let cloned_reference = reference_counted.clone(); kprintln!("[heap] Rc count after clone = {}", Rc::strong_count(&cloned_reference)); core::mem::drop(reference_counted); kprintln!("[heap] Rc count after drop = {}", Rc::strong_count(&cloned_reference)); } /// Recurse forever until we run off the end of the stack into the guard page. #[cfg(feature = "stack-overflow")] #[allow(unconditional_recursion)] fn stack_overflow() { stack_overflow(); // Stop the compiler turning the recursion into a loop (tail call). unsafe { core::ptr::read_volatile(&0u8) }; } /// Called on `panic!`. Print the message to both outputs, then stop. #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("{}", info); serial_println!("KERNEL PANIC: {}", info); #[cfg(feature = "headless")] rust_kernel::exit_qemu(rust_kernel::QemuExitCode::Failed); rust_kernel::hlt_loop(); }