//! QEMU's `virt` board (`qemu-system-aarch64 -M virt -cpu cortex-a53`). //! //! Its memory map (QEMU's hw/arm/virt.c), all inside the first 2 GiB: //! //! ```text //! 0x0800_0000 GICv2 distributor (GICD) } the first GiB: //! 0x0801_0000 GICv2 CPU interface (GICC) } devices //! 0x0900_0000 PL011 UART } //! 0x0a00_0000 32 virtio-mmio slots, 0x200 each //! 0x4000_0000 RAM (128 MiB by default) } the second GiB: RAM //! QEMU puts the device tree here for an ELF kernel; //! we load at 0x4020_0000 (link.ld) //! ``` //! //! `configure` reads the real addresses from the device tree; the constants //! below are only the fallback (and what the first line is printed with). use super::pl011::Pl011; use super::{Board, IrqController, Kind, Uart}; use crate::arch::aarch64::dtb::Fdt; use crate::arch::aarch64::gic::GicV2; use alloc::format; use alloc::string::String; use alloc::vec::Vec; use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; pub const UART_BASE: usize = 0x0900_0000; pub const GICD_BASE: usize = 0x0800_0000; pub const GICC_BASE: usize = 0x0801_0000; /// The EL1 virtual timer: PPI 11 = INTID 27. pub const TIMER_IRQ: u32 = 27; pub const RAM_BASE: u64 = 0x4000_0000; pub struct Virt { uart: Pl011, gic: GicV2, timer_irq: AtomicU32, ram_size: AtomicU64, } pub static VIRT: Virt = Virt { uart: Pl011::new(UART_BASE), gic: GicV2::new(GICD_BASE, GICC_BASE), timer_irq: AtomicU32::new(TIMER_IRQ), ram_size: AtomicU64::new(128 << 20), }; /// First GiB devices, second GiB RAM (QEMU allows more RAM above, but the /// bring-up only maps the first GiB of it). static MAP: [(u64, Kind); 2] = [(0, Kind::Device), (0x4000_0000, Kind::Normal)]; impl Virt { pub fn ram_size(&self) -> u64 { self.ram_size.load(Ordering::Relaxed) } } impl Board for Virt { fn name(&self) -> &'static str { "QEMU virt" } fn uart(&self) -> &dyn Uart { &self.uart } fn irq(&self) -> &dyn IrqController { &self.gic } fn timer_irq(&self) -> u32 { self.timer_irq.load(Ordering::Relaxed) } fn memory_map(&self) -> &'static [(u64, Kind)] { &MAP } fn configure(&self, dtb: Option<&Fdt>) -> Vec { let mut log = Vec::new(); let Some(fdt) = dtb else { log.push(format!("no device tree; using virt's usual addresses (TODO: other boards need one)")); return log; }; fdt.for_each_node(|n| { if n.is_compatible("arm,pl011") { if let Some((base, _)) = n.reg(0) { if base as usize != self.uart.base() { self.uart.set_base(base as usize); } log.push(format!("{}: PL011 UART at {:#x}", n.name(), base)); } } else if n.is_compatible("arm,cortex-a15-gic") || n.is_compatible("arm,gic-400") { if let (Some((d, _)), Some((c, _))) = (n.reg(0), n.reg(1)) { self.gic.set_bases(d as usize, c as usize); log.push(format!("{}: GICv2, distributor at {:#x}, CPU interface at {:#x}", n.name(), d, c)); } } else if n.is_compatible("arm,armv8-timer") { // 4 interrupts of 3 cells (type, number, flags): secure // physical, physical, *virtual*, hypervisor. PPI n = INTID 16 + n. if let Some(ppi) = n.interrupt_cell(2 * 3 + 1) { self.timer_irq.store(16 + ppi, Ordering::Relaxed); log.push(format!("{}: generic timer, virtual timer = PPI {} (INTID {})", n.name(), ppi, 16 + ppi)); } } else if n.device_type == b"memory\0" { if let Some((base, size)) = n.reg(0) { self.ram_size.store(size, Ordering::Relaxed); log.push(format!("{}: RAM at {:#x}, {} MiB", n.name(), base, size >> 20)); } } }); log } }