//! Stage 22: the arm64 kernel binary (`rustbelt-aarch64`). //! //! Built and run by scripts/run-aarch64.sh: //! cargo build --bin rustbelt-aarch64 --features aarch64 \ //! --target aarch64-unknown-none-softfloat //! qemu-system-aarch64 -M virt -cpu cortex-a53 -semihosting \ //! -nographic -kernel //! //! `_start` (src/arch/aarch64/boot.rs) calls `kmain`, which brings the //! machine up one piece at a time, printing as it goes, and then ends the //! QEMU run with status 0. No processes, no scheduler, no shell: those //! still only exist on the PC. #![no_std] #![no_main] extern crate alloc; use alloc::vec::Vec; use core::panic::PanicInfo; use core::sync::atomic::{AtomicU64, Ordering}; use linked_list_allocator::LockedHeap; use rustbelt::arch::aarch64::{self as arch, board, console, dtb, exceptions, mmio, paging, timer}; use rustbelt::println; /// The bring-up's heap: 1 MiB inside the kernel's .bss. (No frame /// allocator on arm64 yet; this is enough for Vec and String.) const HEAP_SIZE: usize = 1 << 20; #[repr(C, align(4096))] struct HeapSpace([u8; HEAP_SIZE]); static mut HEAP_SPACE: HeapSpace = HeapSpace([0; HEAP_SIZE]); #[global_allocator] static HEAP: LockedHeap = LockedHeap::empty(); /// Where QEMU puts the device tree for an ELF kernel on virt. const VIRT_DTB: usize = 0x4000_0000; const TICK_HZ: u64 = 100; /// A word to read back through the higher half. static MARKER: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] pub extern "C" fn kmain(dtb_arg: usize, boot_el: u64) -> ! { let b = board::current(); console::puts_raw(arch::BANNER); exceptions::init(); unsafe { paging::init(b) }; // From here on: caches on, aligned or not, core::fmt is safe. println!("[boot] {} board, Cortex-A53; entered at EL{}, running at EL{}", b.name(), boot_el, arch::current_el()); println!("[boot] exception vectors installed (VBAR_EL1)"); // The higher half: the same RAM, seen at HIGH_BASE + physical. MARKER.store(0x5ca1_ab1e, Ordering::SeqCst); let through_high = unsafe { core::ptr::read_volatile(paging::high(&MARKER as *const _ as usize) as *const u64) }; mmio::use_high_half(paging::HIGH_BASE); println!( "[mmu] on ({}): 4 KiB granule, identity map + higher half at {:#x}; a word at {:#x} reads {:#x} through {:#x}", if paging::enabled() { "SCTLR_EL1.M = 1" } else { "NOT ON?" }, paging::HIGH_BASE, &MARKER as *const _ as usize, through_high, paging::high(&MARKER as *const _ as usize) ); println!("[mmu] devices are now reached through the higher half (this line went to the UART at {:#x})", mmio::virt(0x0900_0000)); // The heap. unsafe { HEAP.lock().init(&raw mut HEAP_SPACE as usize, HEAP_SIZE) }; let numbers: Vec = (1..=1000).collect(); println!("[heap] {} KiB at {:#x}; a Vec of 1..=1000 sums to {}", HEAP_SIZE >> 10, &raw const HEAP_SPACE as usize, numbers.iter().sum::()); // The device tree. let dtb_addr = if dtb_arg != 0 { dtb_arg } else { VIRT_DTB }; let fdt = unsafe { dtb::Fdt::at(dtb_addr) }; match &fdt { Some(f) => println!("[dtb] device tree at {:#x}, {} bytes", dtb_addr, f.size()), None => println!("[dtb] no device tree at {:#x}", dtb_addr), } for line in b.configure(fdt.as_ref()) { println!("[dtb] {}", line); } if let Some(f) = &fdt { list_virtio(f); } #[cfg(feature = "aarch64-fault")] { // Nothing is mapped at 64 GiB: a translation fault, on purpose. println!("[fault] reading 0x10_0000_0000 on purpose (--features aarch64-fault)"); let v = unsafe { core::ptr::read_volatile(0x10_0000_0000 as *const u64) }; println!("[fault] read {:#x}?! the exception handler should have stopped us", v); } // Interrupts: the controller, the timer, then unmask. b.irq().init(); b.irq().enable(b.timer_irq()); timer::start(TICK_HZ); exceptions::enable(); println!("[timer] generic timer at {} Hz, ticking {} times a second (INTID {})", timer::frequency(), TICK_HZ, b.timer_irq()); let start = timer::counter(); let mut shown = 0; while timer::ticks() < 100 { unsafe { core::arch::asm!("wfi") }; let t = timer::ticks(); if t > shown && shown < 5 { shown = t; println!("[timer] tick {}", t); } } let ms = (timer::counter() - start) * 1000 / timer::frequency(); println!("[timer] 100 ticks took {} ms (should be about 1000)", ms); println!("[exit] bring-up done; leaving QEMU through semihosting SYS_EXIT, status 0"); arch::exit(0) } /// QEMU virt has 32 virtio-mmio slots; most are empty (device ID 0). fn list_virtio(f: &dtb::Fdt) { let mut slots = 0; let mut found = Vec::new(); f.for_each_node(|n| { if n.is_compatible("virtio,mmio") { if let Some((base, _)) = n.reg(0) { slots += 1; let base = base as usize; let (magic, version, id) = (mmio::read32(base), mmio::read32(base + 4), mmio::read32(base + 8)); if magic == 0x7472_6976 && id != 0 { found.push((base, version, id)); } } } }); found.sort(); println!("[virtio] {} virtio-mmio slots, {} in use", slots, found.len()); for (base, version, id) in found { let name = match id { 1 => "network card", 2 => "block device", 3 => "console", 4 => "entropy source (rng)", 16 => "GPU", 18 => "input", _ => "other", }; println!("[virtio] {:#x}: device ID {} ({}), virtio-mmio version {}", base, id, name, version); } } #[panic_handler] fn panic(info: &PanicInfo) -> ! { unsafe { console::force_unlock() }; println!("[panic] {}", info); arch::exit(2) }