evidence · 17.6 KiB
Committed at docs/evidence/stage1-from-nix.txt. This is what grounds the requirements
that cite it.
Stage 1 built by the flake: what came out, and what was checked (tasks 2.2, 3.1-3.4, 4.1-4.2)
===========================================================================================
Built 2026-09-22 on solomon from the worktree for every-blob-is-built-from-source-or-named,
nixpkgs 20b1ddd1aa5ace70c9468305030aa4f9ef79671b, with --max-jobs 8 --cores 0. Everything
below is pasted from the build logs and from commands run on the results. NONE OF THIS IS
A BOOT: no stage 1 this flake compiled has been put on a card yet. The boot transcript,
when it exists, is docs/evidence/stage1-from-source.txt.
Sources, pinned by hash (nix/uboot-k230.nix, nix/opensbi-k230.nix, nix/k230-sdk-src.nix)
------------------------------------------------------------------------------------------
u-boot-2022.10.tar.bz2 sha256-ULRIKlBbwoG6hHDDmaPCbhReKbI1ALw1xQ3r1/pGvfg= (50b4482a...)
riscv-software-src/opensbi v1.4 sha256-T8ZeAzjM9aeTXitjE7s+m+jjGGtDo2jK1qO5EuKiVLU=
kendryte/k230_linux_sdk 1104236 sha256-P3XkeyJPkpe/h0oHAHCiz8a0VXofw1zsQ3YSa6UcG8w= (sparse: boot/uboot, boot/opensbi, k230-soc/env, configs)
The sparse SDK fetch, checked file by file:
$ find -L result-k230-sdk-src -type f -print0 | xargs -0 file --mime-type -b | sort | uniq -c
103 text/plain
174 text/x-c
2 text/x-makefile
3 text/x-script.python
5 text/x-shellscript
$ diff -r result-k230-sdk-src/buildroot-overlay/boot .build/k230_linux_sdk/buildroot-overlay/boot && echo identical
identical
1. nix build .#uboot-k230 (U-Boot 2022.10 + Canaan overlay, k230_canmv_v3_defconfig, nixpkgs GCC 15.3.0 cross)
----------------------------------------------------------------------------------------------------------------
built first time, no compiler pin needed. From the build log:
SPL size check: spl/u-boot-spl.bin is 222816 bytes (0x36660) against CONFIG_SPL_SIZE_LIMIT=0x80000 (524288 bytes)
$ ls -l result-uboot-k230/u-boot.bin result-uboot-k230/spl/u-boot-spl.bin
222816 result-uboot-k230/spl/u-boot-spl.bin
721392 result-uboot-k230/u-boot.bin
$ cat result-uboot-k230/readelf-A.txt (the ISA the compiler emitted; no vendor extension)
File: u-boot
Attribute Section: riscv
File Attributes
Tag_RISCV_stack_align: 16-bytes
Tag_RISCV_arch: "rv64i2p1_m2p0_a2p1_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0"
Tag_RISCV_priv_spec: 1
Tag_RISCV_priv_spec_minor: 11
File: spl/u-boot-spl
Attribute Section: riscv
File Attributes
Tag_RISCV_stack_align: 16-bytes
Tag_RISCV_arch: "rv64i2p1_m2p0_a2p1_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0"
Tag_RISCV_priv_spec: 1
Tag_RISCV_priv_spec_minor: 11
$ riscv64-unknown-linux-gnu-objdump -d result-uboot-k230/spl/u-boot-spl | grep -c 'th\.'
/bin/bash: line 31: /nix/store/80r48xbrknf3d5c02p7h24k9cd2aq6rv-riscv64-unknown-linux-gnu-binutils-wrapper-2.46-man/bin/riscv64-unknown-linux-gnu-objdump: No such file or directory
0
$ strings result-uboot-k230/spl/u-boot-spl.bin | grep -c 'PMU Major Msg'
18
The DDR PMU training firmware, inside the SPL we compiled. The same bytes docs/blob-inventory.md
§A5-A6 hashed inside the vendor SPL, at a new offset (the compiler laid the image out differently):
imem (32768 B): sha256 517aa534255e88c941882be40f5e5735349cd1e3b144b536155e51bdc6309c8b
in the vendor fn_u-boot-spl.bin at 0x1fc74, in our u-boot-spl.bin at 0x23f80, in our fn_u-boot-spl.bin at 0x24194
dmem (1660 B): sha256 1c0819e81446a8944a3ecf95304642ecec2071451d430e21925e5d7daea47313
in the vendor fn_u-boot-spl.bin at 0x1f5f4, in our u-boot-spl.bin at 0x23900, in our fn_u-boot-spl.bin at 0x23b14
imem is 14.7% of our 222816-byte SPL
2. nix build .#opensbi-k230 (OpenSBI 1.4 + Canaan's nine-file overlay, generic, FW_TEXT_START=0)
---------------------------------------------------------------------------------------------------
First attempt, GCC 15.3.0, failed: GCC 15 defaults to C23, where bool is a keyword, and
include/sbi/sbi_types.h:47 says 'typedef int bool;' under -Werror:
error: 'bool' cannot be defined via 'typedef'
note: 'bool' is a keyword with '-std=c23' onwards
Pinned GCC 13 (useGcc13 = true in nix/opensbi-k230.nix), as the design says to do rather than patch.
Second attempt, from the build log:
opensbi-k230: fw_jump.bin is 270744 bytes; carries canaan,kendryte-k230 and thead_disable_maee
$ grep THEAD result-opensbi-k230/config
CONFIG_PLATFORM_THEAD=y
CONFIG_THEAD_C9XX_PMU=y
CONFIG_THEAD_C9XX_ERRATA=y
$ strings result-opensbi-k230/fw_jump.bin | grep -n 'canaan,kendryte-k230'
776:canaan,kendryte-k230
$ riscv64-unknown-linux-gnu-nm result-opensbi-k230/fw_jump.elf | grep -i 'maee\|fw_next_arg1'
/bin/bash: line 63: /nix/store/80r48xbrknf3d5c02p7h24k9cd2aq6rv-riscv64-unknown-linux-gnu-binutils-wrapper-2.46-man/bin/riscv64-unknown-linux-gnu-nm: No such file or directory
$ riscv64-unknown-linux-gnu-objdump -d result-opensbi-k230/fw_jump.elf | grep -A2 '<fw_next_arg1>:' (FW_JUMP_FDT_ADDR, as the vendor derives it)
/bin/bash: line 65: /nix/store/80r48xbrknf3d5c02p7h24k9cd2aq6rv-riscv64-unknown-linux-gnu-binutils-wrapper-2.46-man/bin/riscv64-unknown-linux-gnu-objdump: No such file or directory
Size against the vendor's build of the same sources: ours 270744 bytes, vendor 270728 (Xuantie GCC 14.1.1).
3. nix build .#stage1-packaging over the VENDOR-compiled u-boot.bin (task 2.2: the packaging reproduces the vendor's bytes)
--------------------------------------------------------------------------------------------------------------------------
Inputs: the SDK's Docker build of u-boot.bin (07bca1a1d64f26e7...) and spl/u-boot-spl.bin (fd2f3a34733b4258...)
$ K230_UBOOT_DIR=<that dir> nix build --impure .#stage1-packaging
$ sha256sum result-stage1-packaging-vendor/*.bin firmware/stage1/*.bin (the latter: tools/gen-stage1.sh's output, what the last image carried)
3872df5a4e60c53b163a49b31d7b41ee0a407d08d06d18fefe6f8102b3866a94 result-stage1-packaging-vendor/fn_u-boot-spl.bin
c0fb8d95a983c33f3d0a1d7f18de721314878cb3322eaf62fbb1d2788d26b0c4 result-stage1-packaging-vendor/fn_ug_u-boot.bin
3872df5a4e60c53b163a49b31d7b41ee0a407d08d06d18fefe6f8102b3866a94 (shared checkout) firmware/stage1/fn_u-boot-spl.bin
c0fb8d95a983c33f3d0a1d7f18de721314878cb3322eaf62fbb1d2788d26b0c4 (shared checkout) firmware/stage1/fn_ug_u-boot.bin
Byte-identical, both files. nixpkgs gzip 1.14 + nixpkgs mkimage 2026.07 + the U-Boot tree's firmware_gen_no_securiy.py,
SOURCE_DATE_EPOCH=1700000000, no vendor executable.
What the vendor's one-byte sed changes. The same packaging with cmByte = false (the CM byte left at 0x08):
$ sha256sum result-stage1-packaging-vendor-cm08/fn_ug_u-boot.bin
322a829eae456e5483a57932f5b8e7f0c9469328931f814d417d25f7d4cc40d2 result-stage1-packaging-vendor-cm08/fn_ug_u-boot.bin
$ cmp -l result-stage1-packaging-vendor-cm08/fn_ug_u-boot.bin result-stage1-packaging-vendor/fn_ug_u-boot.bin (1-based offset, octal bytes)
13 201 340
14 161 60
15 107 153
16 221 174
17 4 136
18 304 173
19 173 126
20 300 270
21 200 137
23 76 20
24 61 5
25 275 32
26 21 273
27 170 124
28 243 323
29 322 1
30 142 263
31 237 123
32 14 6
33 22 144
34 52 106
35 147 333
36 244 3
37 260 175
38 357 65
39 63 122
40 232 161
41 13 256
42 175 221
43 301 232
44 26 364
537 55 365
538 151 335
539 325 122
540 373 272
557 250 201
558 51 145
559 265 221
560 63 42
599 10 11
40 bytes differ:
offset 599 the gzip CM byte, 010 -> 011 (0x08 -> 0x09); the gzip stream starts at 0-based 596, after the 532-byte K230 header and the 64-byte uImage header
offsets 557-560 the uImage header's data CRC (ih_dcrc), which covers the gzip stream
offsets 537-540 the uImage header's header CRC (ih_hcrc), which covers ih_dcrc
offsets 13-44 the K230 firmware header's SHA-256 over the payload (31 of 32 bytes differ; byte 22 happens to coincide)
Three digests, not two: the task said 'the two checksums that cover it' and there is a third, the K230 header's SHA-256.
4. nix build .#stage1 (the five files the card carries, from this flake's own U-Boot and OpenSBI)
-----------------------------------------------------------------------------------------------------
$ cat result-stage1/SHA256SUMS
fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 fn_u-boot-spl.bin
805bd5434a48e79ba604f309c12d3273a78babc993eae67eff3065cc9e38a2b9 fn_ug_u-boot.bin
cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 env.env
7936a5aebba364663e65c04028f95d7b1d90c93082ea710c65ccd6f23fee77b3 fw_jump.bin
42db0259fca9e81a32469ae749754378c587ef3a8fdef519925f2d8c12ffb2d4 fw_jump_add_uboot_head.bin
$ ls -l result-stage1
$ head -c4 result-stage1/fn_u-boot-spl.bin; head -c4 result-stage1/fn_ug_u-boot.bin (the K230 magic the BootROM looks for)
K230 K230
$ xxd -s 596 -l 4 result-stage1/fn_ug_u-boot.bin (gzip magic 1f 8b, CM 09)
00000254: 1f8b 0900 ....
$ file result-stage1/fw_jump_add_uboot_head.bin
result-stage1/fw_jump_add_uboot_head.bin: u-boot legacy uImage, linux, Linux/RISC-V, OS Kernel Image (Not compressed), 270744 bytes, Tue Nov 14 22:13:20 2023, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0X1A0FAB6D, Data CRC: 0X8E89CE9F
$ mkimage -l result-stage1/fw_jump_add_uboot_head.bin
Image Name: linux
Created: Tue Nov 14 16:13:20 2023
Image Type: RISC-V Linux Kernel Image (uncompressed)
Data Size: 270744 Bytes = 264.40 KiB = 0.26 MiB
Load Address: 00000000
Entry Point: 00000000
The environment (task 3.4), from the build log of the k230-stage1-env derivation:
default.env -> f522ba13aa8a2e643e61e4fde9f2babb604e86b2f38a487be37c7bdc0b14c957 (matches)
cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 env.env
mkenvimage over the SDK's unmodified default.env gives f522ba13..., the hash task 3.4 names, and the
derivation fails if it ever does not. The env.env that ships is mkenvimage over
firmware/stage1/tdisplay.env -- default.env with the load addresses moved above a 57 MiB kernel and
the bootargs.txt import, both observed on hardware (docs/evidence/hardware-boot.txt) -- and its hash
cf108755... is byte-identical to the env.env tools/gen-stage1.sh produced for the last image.
$ diff .build/k230_linux_sdk/buildroot-overlay/board/canaan/k230-soc/env/default.env firmware/stage1/tdisplay.env
27c27
< blinux=k230_set_dtb && ext4load mmc ${mmc_boot_dev_num}:1 0x3000000 /fw_jump_add_uboot_head.bin && ext4load mmc ${mmc_boot_dev_num}:1 0x200000 /${k} && ext4load mmc ${mmc_boot_dev_num}:1 0x2200000 /${dtb} && bootm 0x3000000 - 0x2200000;
---
> blinux=k230_set_dtb && ext4load mmc ${mmc_boot_dev_num}:1 0x7000000 /bootargs.txt && env import -t 0x7000000 ${filesize} && ext4load mmc ${mmc_boot_dev_num}:1 0x8000000 /fw_jump_add_uboot_head.bin && ext4load mmc ${mmc_boot_dev_num}:1 0x200000 /${k} && ext4load mmc ${mmc_boot_dev_num}:1 0x8400000 /${dtb} && ext4load mmc ${mmc_boot_dev_num}:1 0x9000000 /initrd.uimg && bootm 0x8000000 0x9000000 0x8400000;
Not done here: booting any of it. The compilation step is the one that cannot be checked against a hash,
and it is the only one that has not been checked at all.
5. nix build .#sdImage, pure (the card image with the stage 1 above, for task 5.1)
-----------------------------------------------------------------------------------
/nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img
2210918400 bytes. From the build log:
Device Boot Start End Sectors Size Id Type
/nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img1 8192 237567 229376 112M 83 Linu
/nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img2 262144 4316151 4054008 1.9G 83 Linu
--- stage 1, raw, into the gap before partition 1
Device Boot Start End Sectors Size Id Type
/nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img1 8192 237567 229376 112M 83 Linu
/nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img2 262144 4316151 4054008 1.9G 83 Linu
$ xxd at the raw offsets, then sha256 over each slot for the file's length:
0x100000 4b32 3330
0x180000 4b32 3330
0x200000 4b32 3330
0x100000 fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 = fn_u-boot-spl.bin
0x180000 fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 = fn_u-boot-spl.bin
0x200000 805bd5434a48e79ba604f309c12d3273a78babc993eae67eff3065cc9e38a2b9 = fn_ug_u-boot.bin
0x300000 cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 = env.env
0x320000 cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 = env.env
$ debugfs -R 'dump /fw_jump_add_uboot_head.bin' <partition 1>; sha256sum
42db0259fca9e81a32469ae749754378c587ef3a8fdef519925f2d8c12ffb2d4 = fw_jump_add_uboot_head.bin
Every slot holds the bytes result-stage1/SHA256SUMS lists. This image has not been written to a card.
6. Rebuilt with OpenSBI passing the device tree through (task 4.2b; supersedes §2 and §4-5 for what the card should carry)
------------------------------------------------------------------------------------------------------------------------------
§2 above recorded fw_next_arg1 returning 0x2200000, the vendor's FW_JUMP_FDT_ADDR, and docs/evidence/
opensbi-fdt-lands-in-kernel-image.md then proved on the board that this copies the DTB into the kernel's
.BTF section. nix/opensbi-k230.nix now leaves FW_JUMP_FDT_ADDR undefined (FW_JUMP_FDT_ADDR= FW_PAYLOAD=n
on the make command line; docs/evidence/opensbi-fdt-passthrough.txt has the mechanism and the disassembly
before and after). §2 through §5 are left as written: they are the byte-identical-packaging checkpoint and
the image at /nix/store/05p8mf1p5k4ss0xw7dnmqs644gbvs8rf-k230-sd-image.img is the faithful-to-vendor fallback.
$ nix build .#opensbi-k230 .#stage1 .#sdImage (pure)
opensbi-k230: /nix/store/7fmfx6dan9c2dsa55cs3jba3b5wx6w6p-opensbi-k230-riscv64-unknown-linux-gnu-1.4
stage1: /nix/store/p0p73sa482k9i60j4nq93vy71x31yish-k230-stage1
sdImage: /nix/store/vs5mll0vif0lnq36srm6qxgx75mgwf5m-k230-sd-image.img
$ cat result-stage1/SHA256SUMS
fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 fn_u-boot-spl.bin
805bd5434a48e79ba604f309c12d3273a78babc993eae67eff3065cc9e38a2b9 fn_ug_u-boot.bin
cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 env.env
74d08f8701dc72ea166d74be62fc3e5bff437d7c73ab68ad2f113cab289c38f9 fw_jump.bin
9627edbeea9b115d7040beea27cc61b23b6aca61cd502fd7760d7ee179f42c99 fw_jump_add_uboot_head.bin
Only the two OpenSBI files changed; the SPL, U-Boot and environment hashes are those of §4.
$ sha256 over each slot of the image, for the file's length:
0x100000 fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 = fn_u-boot-spl.bin
0x180000 fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 = fn_u-boot-spl.bin
0x200000 805bd5434a48e79ba604f309c12d3273a78babc993eae67eff3065cc9e38a2b9 = fn_ug_u-boot.bin
0x300000 cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 = env.env
0x320000 cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 = env.env
$ debugfs -R 'dump /fw_jump_add_uboot_head.bin' <partition 1>; sha256sum
9627edbeea9b115d7040beea27cc61b23b6aca61cd502fd7760d7ee179f42c99 = fw_jump_add_uboot_head.bin (the pass-through OpenSBI)
Not flashed, not booted. When it is, opensbi-fdt-lands-in-kernel-image.md's dd on /sys/kernel/btf/vmlinux
should return BTF bytes, not d0 0d fe ed.
7. tools/flash-latest.sh after task 5.3: the everyday flash builds the from-source image (dry runs, nothing written)
----------------------------------------------------------------------------------------------------------------------
Before 5.3 the script set K230_STAGE1_DIR unconditionally, so after this change landed a plain
'nix build .#sdImage' and './tools/flash-latest.sh' would have written two different stage 1s.
$ ./tools/flash-latest.sh --dry-run
image: /nix/store/vs5mll0vif0lnq36srm6qxgx75mgwf5m-k230-sd-image.img
stage 1: built from source by this flake (nix/stage1.nix)
dry run: not flashing /dev/disk/by-id/usb-Generic-_USB3.0_CRW_-SD_201506301013-0:1
(the image the board booted, docs/evidence/stage1-from-source.txt)
$ K230_STAGE1=vendor ./tools/flash-latest.sh --dry-run # with firmware/stage1/ empty
error: K230_STAGE1=vendor but .../firmware/stage1/fn_u-boot-spl.bin is missing; run ./tools/gen-stage1.sh rc=1
$ K230_STAGE1=bogus ./tools/flash-latest.sh --dry-run
error: K230_STAGE1 must be 'source' (default) or 'vendor', got 'bogus' rc=1
$ K230_STAGE1=vendor ./tools/flash-latest.sh --dry-run # with the gen-stage1.sh output copied into firmware/stage1/
image: /nix/store/k5bzs11m2xy05radxjajskcf2ig18qwy-k230-sd-image.img
stage 1: local directory (K230_STAGE1_DIR=.../firmware/stage1), vendor-compiled
dry run: not flashing /dev/disk/by-id/usb-Generic-_USB3.0_CRW_-SD_201506301013-0:1
$ dd if=<that image> bs=1 skip=$((0x200000)) count=353794 | sha256sum
c0fb8d95a983c33f3d0a1d7f18de721314878cb3322eaf62fbb1d2788d26b0c4 = the vendor-compiled fn_ug_u-boot.bin, as intended
$ git ls-files firmware/ | xargs file --mime-type -b
text/plain text/plain (PROVENANCE.txt, tdisplay.env; nothing binary is tracked)