RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / arch / aarch64 / main.rs
5931 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! 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 <that ELF file>
//!
//! `_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<u64> = (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::<u64>());

    // 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)
}