How the QEMU boot and the hardware boot differ
Required by image/boot-chain: “The project SHALL record what differs between
the two paths, so that a failure on hardware after a success under emulation is
diagnosed against a written expectation rather than from memory.”
Started during a-riscv-nixos-closure-cross-builds, which can only observe the
QEMU side. The hardware column is completed by
the-board-boots-what-we-built.
Transcripts cited throughout: QEMU in qemu-boot.txt, hardware in
hardware-boot.txt (the boot chain, including the failures that preceded a
working one) and hardware-userspace.md (commands run on the booted board).
QEMU -machine k230 | The board | |
|---|---|---|
| Stage 1 | None. QEMU loads the kernel directly. | BootROM → U-Boot SPL (+ DDR PMU training blob) → U-Boot 2022.10 → OpenSBI v1.4 |
| How the kernel is found | -kernel on the command line | Vendored U-Boot runs its blinux environment variable, which ext4loads each file from the boot partition by name. Not extlinux |
| Root filesystem | Initrd only. The machine models no block device. | ext4 on the SD card, /dev/disk/by-label/NIXOS_SD |
| NixOS module | nix/qemu.nix (netboot-minimal) | nix/hardware.nix, with generic-extlinux-compatible explicitly disabled |
| Kernel | Stock nixpkgs riscv64 | Xuantie, once the-screen-comes-up-under-linux lands |
| Page tables | Standard RISC-V PTE bits required | T-HEAD C9xx MAEE extensions available |
| RAM | 2 GiB given, because the whole system is in the ramdisk | 1 GiB, fixed |
| Console | Emulated UART at ttyS0 | CH342 bridge → /dev/ttyACM0, 115200 8N1 |
| Kernel command line | -append, taken as given | Set by U-Boot, not by /chosen/bootargs in our DTB. See below |
| Cores | 1 | 1 under Linux. The SoC has two C908s, but no K230 device tree declares a cpu@1; the second is Canaan’s AMP core for RT-Smart. Confirmed by nproc on the board |
| Panel, touch, radios, SD | Not modelled at all | Present |
The two that will actually bite
Page tables. QEMU does not implement the T-HEAD C9xx MAEE page-table extensions that Canaan’s SDK kernels use. A kernel built for the board may therefore fail under QEMU even though it is correct, and the stock kernel that boots under QEMU is not the one the board will eventually run. These are not interchangeable artifacts, and a green QEMU run says nothing about the Xuantie kernel.
Stage 1 is entirely absent under QEMU. Every bug in the vendored chain — DDR training, the image header, U-Boot’s environment, where it looks for a kernel — is invisible here and appears for the first time on hardware. This is the single largest gap between the two, and it is why a QEMU boot is not evidence about the board.
What a QEMU boot does prove
That the closure cross-builds, that the kernel and initrd are coherent, that init runs, and that userspace reaches a prompt. That is genuinely worth having before touching hardware — it means a hardware failure is a boot chain problem rather than a userspace one.
Two QEMU constraints found while implementing
The k230 machine generates no FDT. -machine k230,dumpdtb=... answers
This machine doesn't have an FDT, so a device tree must be supplied with
-dtb. tools/qemu-k230.sh looks for one under the kernel’s dtbs/ and
fails with an explanation rather than booting without it. On hardware the
device tree is ext4loaded from the boot partition by name and handed to
bootm as its third argument.
The whole system must be in the initrd. The machine models no block
device, so nix/qemu.nix uses the netboot-minimal profile. On hardware
nix/hardware.nix mounts an ext4 root from the card. These produce different
closures, and only the hardware one is what eventually ships.
Why the QEMU boot uses virt and not k230
Found while implementing, and it is a fact about mainline rather than a convenience.
Linux 6.18.52 has partial K230 support: drivers/pinctrl/pinctrl-k230.c,
drivers/reset/reset-k230.c, and DT bindings for both. It has no bootable
K230 platform:
arch/riscv/boot/dts/canaan/contains only K210 device trees (k210.dtsi,canaan_kd233.dts,sipeed_maix_*). There is no K230 DTS.arch/riscv/Kconfig.socshasSOC_CANAAN_K210and noSOC_CANAAN_K230, and the K210 entry isdepends on !MMU.
So a stock nixpkgs kernel cannot boot QEMU’s k230 machine, which is also
why supplying a -dtb would not have rescued it.
A k230-machine boot needs the Xuantie kernel, built with
CONFIG_ERRATA_THEAD_PBMT=n — that errata is the T-Head memory-type
page-table extension (“non-standard memory type bits in page-table-entries on
T-Head SoCs”) that QEMU does not implement. Packaging that kernel belongs to
the-screen-comes-up-under-linux, which needs it anyway.
a-riscv-nixos-closure-cross-builds is asking whether the closure builds and
starts, which is machine-independent, so virt answers it today and
tools/qemu-k230.sh grows a MACHINE=k230 path for when the Xuantie kernel
lands.
Corrections made after the first hardware boots
Three claims above were written from the QEMU side before the board had ever run our image, and hardware disproved them. They are corrected in place; they are recorded here because each one cost a boot cycle.
OpenSBI is v1.4, not v0.9. output/k230_canmv_v3_defconfig/build/opensbi-1.4
and buildroot-overlay/boot/opensbi/opensbi-1.4-overlay.
There is no extlinux anywhere in this boot path. The vendored U-Boot runs
blinux, an environment variable holding a chain of ext4load commands that
fetch each file from the boot partition by literal filename, ending in bootm.
Nothing parses extlinux.conf, and nix/hardware.nix therefore sets
boot.loader.generic-extlinux-compatible.enable = false. Anything that assumes
extlinux — a generation menu, an APPEND line — does not exist here.
The device tree does not carry the kernel command line. This is the
sharpest difference from QEMU, where -append is simply honoured. On the board
board_fdt_chosen_bootargs() overwrites /chosen/bootargs with a hardcoded
vendor string unless the U-Boot environment defines bootargs. The full
analysis is in stage1-emergency-mode.md; the consequence for this table is
that the kernel command line is a property of the U-Boot environment and the
boot partition, not of the DTB we build.
Still unproven on hardware
This document cannot be finished until a boot reaches userspace. Outstanding:
Page tablesremains an expectation. No Xuantie kernel has been run under QEMU, so the MAEE/PBMT claim is untested in both columns. This is now the only row in the table that rests on reading rather than observation.
Two entries that were listed here as unproven have since been settled on
hardware, in hardware-userspace.md:
- Cores. Resolved, and not as expected — Linux gets one hart, by device tree design rather than by misconfiguration. The table row is corrected.
- Root filesystem. Resolved.
/proc/cmdlinecarries no explicitroot=, yet/is mounted from/dev/mmcblk1p2, so theNIXOS_SDlabel path did the work.