Arm 相关#

ch1 主要完成 boot, 串口输出和关机的功能。本节依次介绍这三个功能的实现。

boot#

我们观察 qemu 启动之后执行的第一段指令,可以看到:

0x40000000:  ldr     x0, 0x40000018
0x40000004:  mov     x1, xzr
0x40000008:  mov     x2, xzr
0x4000000c:  mov     x3, xzr
0x40000010:  ldr     x4, 0x40000020
0x40000014:  br      x4

0x40000018:  .inst   0x44000000
0x4000001c:  .inst   0x00000000
0x40000020:  .inst   0x40080000

这段代码设置了一些初始寄存器的值,然后跳转到了 x4 指向的值,也就是 0x40080000 继续执行,因此 0x40080000 也就是我们内核的入口地址。

x0 储存了设备树的地址,这里我们用不上,所以忽略这个参数。

参见 linker.ld:

ENTRY(_start)
BASE_ADDRESS = 0x40080000;

SECTIONS
{
    . = BASE_ADDRESS;
    skernel = .;

    .text : {
        stext = .;
        *(.text.entry)
        *(.text .text.*)
        . = ALIGN(4K);
        etext = .;
    }

    # ....
}

linker.ld 定义把 *(.text.entry) 段所标注的代码放在刚才所说的起始地址。注意这里的 ENTRY 其实标注了 elf 的入口地址,在这里是没有用的。entry 代码参见 entry.rs:

#[no_mangle]
#[link_section = ".text.entry"]
unsafe extern "C" fn _start() -> ! {
    asm!("
        mov     sp, {boot_stack_top}
        b       {rust_main}",
        boot_stack_top = in(reg) BOOT_STACK.as_ptr_range().end,
        rust_main = sym crate::rust_main,
        options(noreturn),
    )
}

这一段代码就是设立了一个静态分配的初始栈,然后跳转到 rust_main 函数,至此就 boot 结束了。在 rust_main 函数中,我们会进行一些最基本的初始化,然后输出 os 各个代码段范围,然后关机退出。接下来分别介绍串口输出和关机的实现方式。

串口输出#

不像 riscv 一样有 sbi 的支持,在 arm 上我们需要实现简单的串口输出。由 qemu源码可知,qemu 虚拟的 uart 位于 0x900_0000 位置,在此基础上,我们可以实现一个简易的串口驱动:

/// pl011.rs
use core::ptr::{read_volatile, write_volatile};

const UART_FIFO_DR: usize = 0x0900_0000;
const UART_FIFO_FR: usize = 0x0900_0018;

pub fn console_putchar(c: u8) {
    unsafe {
        while read_volatile(UART_FIFO_FR as *const u32) & (1 << 5) != 0 {}
        write_volatile(UART_FIFO_DR as *mut u32, c as u32);
    }
}

感兴趣的同学也可以实现功能更加完整强大的驱动,可参考 qemu_plo11驱动。该设备完整资料可见 完整资料

关机#

arm 特权级分为四层,见下图。

arm特权级别

arm特权级别#

应用程序位于 el0,os 主要位于 el1,更高特权级该实验很少涉及。在 qemu boot 后我们处在 el1(这一点不同的硬件平台设定可能不一致)。与其他架构类似,当应用程序(el0) 想要调用系统调用时,可以通过 svc (supervisor call) 来实现,os 想要调用更高特权级指令的时候,可以通过 hvc (hypervisor call) 来实现。ch1 通过 hvc 实现关机。

/// psci.rs
const PSCI_SYSTEM_OFF: u32 = 0x8400_0008;
fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize {
    let ret;
    unsafe {
        asm!(
            "hvc #0",
            inlateout("x0") func as usize => ret,
            in("x1") arg0,
            in("x2") arg1,
            in("x3") arg2,
        )
    }
    ret
}

pub fn shutdown() -> ! {
    psci_hvc_call(PSCI_SYSTEM_OFF, 0, 0, 0);
    unreachable!("It should shutdown!")
}