RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / arch / aarch64 / board / virt.rs
4061 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! 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<String> {
        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
    }
}