RISC-V 内核启动要求与限制

作者:

Alexandre Ghiti <alexghiti@rivosinc.com>

日期:

2023年5月23日

本文描述了 RISC-V 内核对引导加载程序和固件的期望,以及任何开发者在修改早期启动过程时必须牢记的限制。就本文而言,early boot process 指的是在建立最终虚拟映射之前运行的任何代码。

内核启动前的要求与限制

RISC-V 内核对引导加载程序和平台固件有以下期望

寄存器状态

RISC-V 内核期望

  • $a0 包含当前核心的 hartid。

  • $a1 包含内存中设备树的地址。

CSR 状态

RISC-V 内核期望

  • $satp = 0:如果存在 MMU,则必须将其禁用。

驻留固件的保留内存

RISC-V 内核绝不能在直接映射中映射任何驻留内存或受 PMP 保护的内存,因此固件必须根据设备树规范和/或 UEFI 规范正确标记这些区域。

内核位置

RISC-V 内核期望被放置在 PMD 边界(对于 rv64 为 2MB 对齐,对于 rv32 为 4MB 对齐)。请注意,如果并非如此,EFI 存根将对内核进行物理重定位。

硬件描述

固件可以向 RISC-V 内核传递设备树或 ACPI 表。

设备树可以通过 $a1 寄存器从上一阶段直接传递给内核,或者在通过 UEFI 启动时,使用 EFI 配置表传递。

ACPI 表使用 EFI 配置表传递给内核。在这种情况下,EFI 存根仍然会创建一个微型设备树。有关此设备树的详细信息,请参阅下文的“EFI 存根与设备树”一节。

内核入口

在 SMP 系统上,进入内核有 2 种方法

  • RISCV_BOOT_SPINWAIT:固件释放内核中的所有 hart,其中一个 hart 在抽签中胜出并执行早期启动代码,而其他 hart 则处于等待初始化完成的停放状态。此方法主要用于支持没有 SBI HSM 扩展的旧固件以及 M 模式的 RISC-V 内核。

  • Ordered booting:固件仅释放一个 hart 来执行初始化阶段,然后使用 SBI HSM 扩展启动所有其他 hart。有序启动方法是启动 RISC-V 内核的首选启动方法,因为它能够支持 CPU 热插拔和 kexec。

UEFI

UEFI 内存映射

当使用 UEFI 启动时,RISC-V 内核将仅使用 EFI 内存映射来填充系统内存。

UEFI 固件必须解析 /reserved-memory 设备树节点的子节点,并遵循设备树规范,将这些子节点的属性(no-mapreusable)转换为与其对应的正确 EFI 等效项(参考设备树规范 v0.4-rc1 的“3.5.4 /reserved-memory and UEFI”节)。

RISCV_EFI_BOOT_PROTOCOL

使用 UEFI 启动时,EFI 存根需要引导 hartid,以便通过 $a1 将其传递给 RISC-V 内核。EFI 存根使用以下方法之一获取引导 hartid

  • RISCV_EFI_BOOT_PROTOCOL首选)。

  • boot-hartid 设备树子节点(已弃用)。

任何新固件都必须实现 RISCV_EFI_BOOT_PROTOCOL,因为基于设备树的方法现在已被弃用。

早期启动要求与限制

RISC-V 内核的早期启动过程在以下限制下运行

EFI 存根与设备树

当使用 UEFI 启动时,设备树由 EFI 存根进行补充(或创建),其参数与 arm64 相同,这些参数在 The Unified Extensible Firmware Interface (UEFI) 中的“UEFI kernel support on ARM”段落中有描述。

虚拟映射安装

在 RISC-V 内核中,虚拟映射的安装分为 2 个步骤完成

  1. setup_vm()early_pg_dir 中安装一个临时的内核映射,这允许发现系统内存。此时仅映射内核代码段/数据段。在建立此映射时,无法进行内存分配(因为系统内存尚未知),因此 early_pg_dir 页表是静态分配的(每个级别仅使用一个表)。

  2. setup_vm_final()swapper_pg_dir 中创建最终的内核映射,并利用已发现的系统内存来创建线性映射。在建立此映射时,内核可以分配内存但无法直接访问它(因为直接映射尚未存在),因此它使用 fixmap 区域中的临时映射来访问新分配的页表级别。

为了使 virt_to_phys()phys_to_virt() 能够正确地将直接映射地址转换为物理地址,它们需要知道 DRAM 的起始位置。这发生在步骤 1 之后、步骤 2 安装直接映射之前(参见 arch/riscv/mm/init.c 中的 setup_bootmem() 函数)。在安装最终虚拟映射之前对这些宏的任何使用都必须经过仔细审查。

通过 fixmap 进行设备树映射

由于 reserved_mem 数组是用由 setup_vm() 建立的虚拟地址初始化的,并与由 setup_vm_final() 建立的映射一起使用,因此 RISC-V 内核使用 fixmap 区域来映射设备树。这确保了两个虚拟映射都可以访问设备树。

MMU 启用前的执行

有一些代码甚至在建立第一个虚拟映射之前就需要运行。这些代码包括第一个虚拟映射本身的安装、早期替代项的修补以及内核命令行的早期解析。这些代码必须非常仔细地编译为

  • -fno-pie:这对于使用 -fPIE 的可重定位内核是必需的,否则,对全局符号的任何访问都会通过 GOT,而 GOT 仅被虚拟重定位。

  • -mcmodel=medany:对全局符号的任何访问必须是相对 PC 的,以避免在设置 MMU 之前发生任何重定位。

  • 必须禁用所有插桩(包括 KASAN、ftrace 等)。

由于使用来自不同编译单元的符号要求该单元必须使用这些标志进行编译,因此我们建议尽可能不要使用外部符号。