evidence · 3.6 KiB

stage1-emergency-mode.md

Committed at docs/evidence/stage1-emergency-mode.md. This is what grounds the requirements that cite it.

Open raw source

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 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}
  && ...

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.