RISC-V Software Design: Bootloader - Edgeboard RISC-V Series
Intro
This article is a part of the Edgeboard RISC-V series. Check out other articles as well if you came in through a search engine.
Following the complete of setting up debug access, we can finally start the configuration for the first piece of software that will run on the RISC-V platform. The bootloader in this build consists of three parts: BootROM, OpenSBI, and U-Boot. Linux kernel will be the payload.
Note that this article will not cover all of the trial-and-errors appeared during the development process; only the important parts will be explained.
The BootROM (first stage)
Per this config line, the reset vector of the generated RISC-V processor will be 0x10000
, which is located at the beginning of the BootROM which Rocket Chip generates. The original BootROM from Rocket Chip is simply a bootloop application: a wfi
loop, which is anything but useful. A real BootROM with some code borrowed from the CNRV repository, is created, and does the following:
- Enable interrupt and probe the count of harts present on the system
- Set up BootROM stack for each hart
- Hart 0 also sets up a mark for synchronization
- Jump to C code
- Harts other than 0 wait until hart 0 signals initialization done
- Hart 0 continues initialization work
- Initialize UART
- Set up machine trap (for debugging)
- Load OpenSBI ELF from BRAM to DRAM
- Signal the rest harts, branch to start of OpenSBI
The OpenSBI ELF, with device tree (FDT) and U-Boot embedded inside as its payload, is stored in a piece of block RAM outside the Rocket Chip. The block RAM is generated via Xilinx's Block Memory Generator IP. Compared to using Sifive's TLROM
, Xilinx's BMG tends to correctly infer BRAM cells, while TLROM
usually results in significant LUTRAM usage. The BMG IP accepts a coe
file to initialize the memory cells and require re-synthesizing the IP to change the contents. The coe
file is regenerated automatically whenever the BootROM is recompiled.
OpenSBI
SBI serves as a bootloader and firmware for a RISC-V platform: it runs in M-mode, loads further stages of S-mode bootloaders (or the OS payload), hosts M-mode traps (timer, pseudo-instruction emulation, etc.), and provides service routines (ecall
from S-mode) to operating systems. OpenSBI is the reference implementation of the SBI standard. A fork of the original OpenSBI repository contains the platform definition for Edgeboard, namely the initialization functions, device tree, and payload definition. The build routine is also slightly modified to accomodate U-Boot build routine in.
Device tree
The device tree is based on the DTS generated by Rocket Chip, with the following additions to reflex peripherals outside the Rocket Chip SoC over AXI. The DTB, embedded inside OpenSBI, will have its start address passed to the next stage (U-Boot) per platform specification.
UART
Two AXI 16550 are present: e0000000
(ttyS0
) is used for RISC-V console (stdin/stdout/stderr), while e1000000
(ttyS1
) is connected to UART1 in Zynq's processing system for a PPP connection (will be explained in a follow-up article).
axi_uart0: serial@e0000000 {
clock-frequency = <100000000>;
compatible = "ns16550a";
current-speed = <115200>;
device_type = "serial";
interrupt-parent = <&plic>;
interrupts = <1>;
reg = <0xe0000000 0x10000>;
reg-offset = <0x1000>;
reg-shift = <2>;
};
Note that the ns16550a
compatible string is used instead of ns16550
, otherwise the FIFOs will not be enabled, resulting in input overruns. Credit for this goes to @imi415.
MMC SDHCI
A SDHCI controller is borrowed from Zynq PS to gain access to the SD card from RISC-V: this is used for a persistent storage to hold Linux rootfs.
clk200: clk200 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <200000000>;
u-boot,dm-pre-reloc;
};
sdhci0: mmc@ff170000 {
u-boot,dm-pre-reloc;
compatible = "arasan,sdhci-8.9a";
reg = <0xff170000 0x1000>;
clocks = <&clk200 &clk200>;
clock-names = "clk_xin", "clk_ahb";
interrupt-parent = <&plic>;
interrupts = <4>;
no-1-8-v;
disable-wp;
};
The no-1-8-v
and disable-wp
properties are borrowed from the stock FDT in the Edgeboard BSP, to work around problematic voltage switching and lack of Write Protect detection for MicroSD cards. The clk200
node reflects the fixed AHB clock inside PS.
The "chosen" node
The "chosen" node provides some default values for software, notably U-Boot and Linux. Note that earlycon=sbi
signals the Linux kernel to use SBI calls for early stage printing before kernel gets a chance to initialize the serial console (they're not used forever as it is expensive to do SBI calls).
chosen {
bootargs = "earlycon=sbi root=/dev/mmcblk0p2 rootwait";
stdout-path = "/soc/serial@e0000000:115200";
};
timebase-frequency
property
The Linux kernel expects the timebase-frequency
property to be under the /cpus
node, but Rocket Chip somehow generates the property under each CPU core (e.g. /cpus/cpu@0
), and this mismatch will result in an early panic. The property is moved to the correct location.
Physical Memory Protection (PMP)
The default PMP initialization of current OpenSBI is not suitable for our setup: the whole firmware will be masked totally inaccessible from S-mode and U-mode, but we have our device tree embedded inside OpenSBI. A patch lifts the limitation to read-only firmware; in this way, the DTB will be readable by the following U-Boot payload, and will then be relocated to somewhere else in DRAM.
U-Boot
A U-Boot fork has been created containing the board definition of the platform, along with a minimal defconfig
. A special environment variable needs to be set prior to booting a Linux kernel to prevent erroneous device tree relocation. Credit for this goes to @Icenowy.
After putting things together, as well as correctly setting up PS (described in a follow-up article), the BootROM prompt, OpenSBI information, and U-Boot prompt shall appear in order on the serial console.