███████
███▒▒▒▒▒███
███ ▒▒███ ████████ █████ ████ █████ █████
▒███ ▒███▒▒███▒▒███ ▒▒███ ▒███ ▒▒███ ▒▒███
▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒▒█████▒
▒▒███ ███ ▒███ ▒███ ▒███ ▒███ ███▒▒▒███
▒▒▒███████▒ ████ █████ ▒▒███████ █████ █████
▒▒▒▒▒▒▒ ▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒███ ▒▒▒▒▒ ▒▒▒▒▒
███ ▒███
▒▒██████
▒▒▒▒▒▒
███████████ █████
▒▒███▒▒▒▒▒███ ▒▒███
▒███ ▒███ ██████ ██████ ███████
▒██████████ ███▒▒███ ███▒▒███▒▒▒███▒
▒███▒▒▒▒▒███▒███ ▒███▒███ ▒███ ▒███
▒███ ▒███▒███ ▒███▒███ ▒███ ▒███ ███
███████████ ▒▒██████ ▒▒██████ ▒▒█████
▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒
Minimalist RISC-V 64-bit bootloader written in freestanding C++
OnyxBoot - tiny bare-metal bootloader for RISC-V 64-bit (rv64gc). Discovers hardware via FDT (device tree from a1 register), reads kernel.elf from VirtIO or SDHCI, parses ELF64, and jumps to entry point. ~9.5 KB binary, zero runtime dependencies.
First-stage loader for OnyxOS.
$ make CROSS=riscv64-elf
$ qemu-system-riscv64 -M virt -m 256M -bios bootloader.bin \
-drive file=disk.img,format=raw,if=none,id=drive0 \
-device virtio-blk-device,drive=drive0 \
-nographic -serial mon:stdioOr use the test script:
$ bash test/run_qemu.sh| Tool | Purpose |
|---|---|
riscv64-elf-g++ or riscv64-unknown-elf-g++ |
Cross-compiler |
riscv64-elf-objcopy |
Binary extraction |
make |
Build |
qemu-system-riscv64 |
Test (optional) |
Override toolchain:
make CROSS=riscv64-unknown-elf- QEMU or MaskROM loads bootloader at 0x80000000, passes FDT pointer in a1.
_start(boot_entry.cpp): clears BSS, sets up 8KB stack, callsboot_main.boot_mainparses FDT:- Finds NS16550A UART, inits serial output.
- Reads board model from
/modelproperty. - Scans for
virtio,mmioand SDHCI compatible devices. - Probes each VirtIO slot (v1 legacy or v2 modern), keeps only working ones.
- Boot menu: shows available devices, auto-selects after timeout (~20M nops).
- Reads kernel.elf from selected device (up to 4096 sectors, limited by actual DRAM size).
- Validates ELF header, checks no segment overlaps bootloader memory.
- Copies ELF segments to target addresses via
load_all(). - Jumps to entry point with hartid=0 and FDT address.
| File | What |
|---|---|
src/boot_entry.cpp |
Naked _start: BSS, stack, hartid check via a0 |
src/boot_main.cpp |
Main boot: FDT, devices, ELF load, jump |
include/uart.hpp |
NS16550A UART with reg-shift from FDT |
include/virtio.hpp |
VirtIO v1 (legacy) + v2 (modern) MMIO block driver |
include/sdhci.hpp |
SDHCI SD card driver |
include/bootmenu.hpp |
Boot menu with timeout auto-select |
include/elf.hpp |
ELF64 parser + check_safe overlap detection |
include/fdt.hpp |
FDT core parser (DTB tokenizer) |
include/fdt_dev.hpp |
Device finders: UART, VirtIO, SDHCI, memory, model |
include/debug.hpp |
Conditional debug output (guarded by -DDEBUG) |
linker.ld |
Linker script: 0x80000000, PHDRS (RX/RW), 8KB stack |
Makefile |
Cross-compilation, overridable CROSS |
$ make test
# or: bash test/run_qemu.sh$ make test-all
# or: bash test/test_all.shRuns four QEMU scenarios: MBR+FAT32, GPT+FAT32, MBR+ext4, and FAT32→ext4 fallback. Each creates a disk image, loads kernel.elf, and checks for "Hello from test kernel!" in the serial output.
Expected output (MBR+FAT32):
OnyxBoot v0.4 [riscv-virtio,qemu]
OnyxBoot boot menu
--------------------
0: VirtIO @ 0x0000000010008000
--------------------
Select device (0-0, or enter for auto): loading kernel.elf [########]
jumping to kernel
Hello from test kernel!
CI at .github/workflows/test.yml builds and runs all four tests on every push.
- Freestanding C++20 - no libc, no libstdc++, no RTTI, no exceptions.
- No assembly files - all privileged code via inline asm in boot_entry.cpp.
- FDT driven - DTB from a1, no hardcoded addresses. Parses /model, /memory, UART, VirtIO, SDHCI from device tree.
- VirtIO v1+v2 - detects MMIO version at probe time. Legacy uses GuestPageSize
- QueuePFN. Uses static page-aligned VirtQueue (alignas 4096).
- UART reg-shift - reads reg-shift from FDT. Works with byte stride (QEMU) and 32-bit stride (real hardware like Sophgo SG2000).
- S-mode compatible - uses a0 for hartid instead of csrr mhartid. Works both in M-mode (-bios) and S-mode (after OpenSBI).
- Boot menu - timeout ~20M nops, auto-selects first device. Accepts number key.
- ELF integrity - check_safe validates no segment overlaps bootloader memory.
- Binary - ~9.5 KB text, 240 B BSS, 8 KB stack. Budget 32 KB.
GPL-3.0-or-later. See LICENSE.