Why stage 1 dropped to emergency mode, and why the DTB could not fix it
Observed on hardware. The initrd ran, mounted the root filesystem, and then failed anyway:
[ OK ] Finished Create Volatile Files and Directories in the Real Root.
You are in emergency mode. ...
Cannot open access to console, the root account is locked.
Two facts are visible in those three lines:
- The root filesystem is mounted. “in the Real Root” is the
/sysrootvariant of the tmpfiles unit; it cannot run beforesysroot.mount. - The failing unit ordered immediately after it, and it is fatal to
initrd.target.
The failing unit
initrd-find-nixos-closure.service, from
nixos/modules/system/boot/systemd/initrd.nix:
closure=
for o in $(< /proc/cmdline); do
case $o in
init=*) ... closure="${initParam[1]}" ;;
esac
done
if [ -z "${closure:-}" ]; then
echo 'No init= parameter on the kernel command line' >&2
exit 1
fi
It is declared RequiresMountsFor = "/sysroot/nix/store" (hence running right
after the sysroot tmpfiles unit) and requiredBy = [ "initrd.target" ] — so
exit 1 fails initrd.target, which is emergency mode. sulogin then cannot
offer a prompt because NixOS leaves root locked, which is why the boot stops
dead rather than dropping to a shell.
So the whole failure reduces to: init= was not on the kernel command line.
Why writing it into the DTB was not enough
nix/sd-image.nix does fdtput -t s ... /chosen bootargs, and that is genuinely
required — an earlier boot with no /chosen/bootargs at all was silent on both
UARTs. But it is overwritten. board/canaan/common/k230_img.c:110:
char *board_fdt_chosen_bootargs(void){
char *bootargs = env_get("bootargs");
if(NULL == bootargs) {
...
bootargs = "root=/dev/mmcblk1p2 loglevel=8 rw rootdelay=4 rootfstype=ext4 console=ttyS0,115200 earlycon=sbi";
}
printf("g_bootmod = %d, bootargs=%s\n", g_bootmod, bootargs);
return bootargs;
}
The vendor environment never defines bootargs, so U-Boot took the fallback
branch and replaced /chosen/bootargs with that hardcoded string. The
g_bootmod = 3 line seen on every boot is line 126 of this function.
This also explains a coincidence worth naming: the boot got as far as it did
because that fallback string happens to carry root=/dev/mmcblk1p2, which
matches our partition layout. The root mount that appeared to work was the
vendor’s doing, not ours.
The fix
env_get("bootargs") is checked first, so defining it wins outright. The
command line names the NixOS closure and therefore changes on every rebuild,
while the stage 1 environment image must not — so the value is not baked into
env.env. Instead blinux loads it from the boot partition:
blinux=k230_set_dtb
&& ext4load mmc ${mmc_boot_dev_num}:1 0x7000000 /bootargs.txt
&& env import -t 0x7000000 ${filesize}
&& ...
CONFIG_CMD_IMPORTENV=yin this U-Boot build.ext4loadsetsfilesize(fs/fs.c:743), consumed by the next command.0x7000000is above the ~60 MB kernel at0x200000and below fw_jump at0x8000000.
nix/sd-image.nix writes bootargs.txt from config.boot.kernelParams plus
init=, the same string it puts in the DTB, so the two cannot disagree.
What this does not yet prove
root=fstab (set by nixpkgs’ systemd-initrd module) resolves the root device
through the initrd’s fstab, i.e. /dev/disk/by-label/NIXOS_SD. Because the
vendor fallback supplied an explicit root= until now, the by-label path has
never actually been exercised on this board. If sysroot fails to mount on the
next boot, that is the thing to suspect, not the init= fix.