Three Bugs
What Stood in the Way
Full write-up, including the theories that turned out to be wrong, is in the repository's root-cause analysis.
The clockevent core is told a lie
timer-sun4i.c (and timer-sun5i.c) write a forced-minimum-delta interval of zero and report success instead of -ETIME. The scheduler tick dies permanently while interrupts keep running — the board still answers pings, but no task is ever scheduled again.
Every NAND read looked corrupt
Mainline's NAND ID table gives this chip 40-bit ECC — correct as the datasheet minimum, but the factory image and U-Boot wrote the flash at 56-bit. Reading 56-bit data with a 40-bit layout misaligns every ECC byte, so every read fails -EBADMSG and UBI can't attach.
A byte-perfect bootloader, silently rejected
The SPL expects a legacy mkimage header, not a raw ARM binary. Flashing u-boot-dtb.bin writes a byte-perfect U-Boot that the SPL then silently ignores — the readback matches the file exactly, and the board still never boots. The fix: flash u-boot-dtb.img instead.
Status
What Works, What Doesn't
Verified on real hardware, not just in theory.
Verified working
Known gaps
- Bluetooth LE scanning fails —
hcitool lescanreturns an I/O error. Looks like a limitation of the staging r8723bs/btrtl path, not a config issue. Classic Bluetooth is unaffected — test this yourself before planning a BLE project around this board. - No audio server in the image. Bluetooth pairs and does service discovery, but A2DP needs PipeWire or PulseAudio plus libspa-0.2-bluetooth to actually stream — without it the speaker pairs, then drops the link.
- No RTC battery. The clock starts from the newest filesystem timestamp, so
aptfails until NTP syncs over WiFi.
First Login
Credentials You'll Need
The image is unusable without these — the password is expired on purpose, not just documented.
root / chip
Password expired — the first login must set a new one before a shell is granted.
Reachable at 192.168.7.2 over the USB gadget link, or on the gadget console (ttyGS0).
The board has no default route until WiFi is configured — and the moment it is, an unchanged password is one every reader of this page knows. That's why it ships expired rather than merely written down.
Downloads
Get the Image
Release v1.0
Decompress rootfs.ubi.xz with unxz before flashing — the flashing steps in the build & flash guide want the raw image. SHA256SUMS covers the image in both compressed and raw form.
The published image is built from the same kernel config, compiler and hash-pinned Debian packages as the image that ran for hours on real hardware — but this exact artifact has not itself been flashed and booted. Verify downloads against SHA256SUMS.
Released under GPL-2.0; some flashing logic follows NextThing Co's MIT-licensed CHIP-tools. Full attribution in NOTICE.
Guide
Building and flashing a complete C.H.I.P. image
All commands run on the host in your working directory. Export it once:
export CHIP_BUILD=/path/to/your/build # kernel tree, rootfs, u-boot, images
Every script in scripts/ reads CHIP_BUILD and fails loudly if it is unset.
0. What makes this work (three non-obvious fixes)
If you rebuild any of these from scratch, do not lose them:
-
Kernel —
drivers/clocksource/timer-sun4i.c
at the top ofif (evt <= TIMER_SYNC_TICKS) return -ETIME;sun4i_clkevt_next_event()(and the same intimer-sun5i.c). Without it the driver programs the clockevent with interval 0 and returns success on the core's forced min-delta path, the scheduler tick dies, and all userspace freezes a few tens of seconds into boot while interrupts keep running. Do not also raise the registeredmin_delta— that makes the guard unreachable and reintroduces the bug. -
Devicetree —
arch/arm/boot/dts/allwinner/sun5i-r8-chip.dts
Both ECC properties are required (&nfc { status = "okay"; nand@0 { nand-ecc-mode = "hw"; nand-ecc-step-size = <1024>; nand-ecc-strength = <56>; }; };sunxi_nand_attach_chip()tests!ecc->size, so strength alone is silently ignored). Mainline's ID table says 40 for this chip; U-Boot and the factory image wrote the flash with 56, so 40 makes every read fail. Sanity check: a correct boot finds six bad blocks (2025/2027/2033/2035/2041/2043). ECC 40 finds only three.
The chip in question: the SK hynix MLC NAND, center. The blue module to its right is the RTL8723BS WiFi/Bluetooth. -
Flashing — write
u-boot-dtb.img, NOTu-boot-dtb.bin
The SPL is built withCONFIG_SPL_LEGACY_IMAGE_SUPPORT=yandCONFIG_SPL_RAW_IMAGE_SUPPORTunset, so it requires the legacy mkimage header (0x27051956). A raw.binis silently rejected and the board never boots even though U-Boot is byte-perfect in flash.flash-custom.shstill has this bug;flash-boot-only.shis fixed.
Also: u-boot/.config must NOT set CONFIG_AXP_DISABLE_POWERON_VIN, or the board will not power up from bare USB and needs a power-button press.
1. Modify the rootfs
The tree is owned by your unprivileged build user, with group IDs in the 100000+ subgid range, because it was created inside a user namespace. Every operation on it must run inside the same mapping or ownership is destroyed:
scripts/rootfs-chroot.sh 'apt-get update && apt-get install -y <pkg>'
scripts/rootfs-chroot.sh # interactive shell
rootfs-chroot.sh uses unshare -r --map-auto (restores root:root / root:shadow) plus binfmt_misc + qemu-arm to run armhf binaries on x86. qemu-arm is registered with the F flag, so nothing needs copying into the tree.
Already installed for a working board: wpasupplicant, wireless-regdb (regulatory.db + .p7s — the kernel has CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y so the signature is required), systemd-timesyncd, firmware-realtek, NetworkManager, openssh-server.
Enabled units: usb0-static, systemd-timesyncd, wpa_supplicant, NetworkManager, ssh, serial-getty@ttyGS0, getty@tty1.
2. Build the kernel, DTB and image
Toolchain used for the published binaries:
arm-linux-gnueabihf-gcc-15 (Debian 15.3.0-2) 15.3.0
GNU ld (GNU Binutils for Debian) 2.47
gcc-15-arm-linux-gnueabihf is in Debian sid; trixie ships 14.2. Other versions should build fine — this is recorded so the CI release job and the download come out of the same compiler, not because 15 is required.
cd linux-7.1.5
make O=$PWD/debian/build/build_armhf_none_armmp ARCH=arm \
CROSS_COMPILE=arm-linux-gnueabihf- zImage allwinner/sun5i-r8-chip.dtb -j$(nproc)
cd ..
KV=7.1.5+deb14-armmp
K=linux-7.1.5/debian/build/build_armhf_none_armmp
cp $K/arch/arm/boot/zImage rootfs/boot/vmlinuz-$KV
cp $K/arch/arm/boot/dts/allwinner/sun5i-r8-chip.dtb rootfs/boot/dtbs/$KV/
mkimage -A arm -T script -C none -n "chip boot" -d xfer/boot.cmd.v6 rootfs/boot/boot.scr
# UBI geometry MUST match the flash: PEB 2 MiB, LEB 2064384, min-io 16384
unshare -r --map-auto /usr/sbin/mkfs.ubifs -m 16384 -e 2064384 -c 2048 \
-r rootfs -o rootfs.ubifs
/usr/sbin/ubinize -o rootfs.ubi -p 0x200000 -m 16384 -s 16384 ubinize.ini
Note mkfs.ubifs/ubinize live in /usr/sbin, which is not on the user PATH.
Or use the released image
The published rootfs.ubi is xz-compressed, because uncompressed it exceeds GitLab's job-artifact limit; it is served from the project's package registry rather than as a pipeline artifact. Decompress before flashing — the flashing steps below all want the raw image:
unxz rootfs.ubi.xz
sha256sum -c SHA256SUMS
SHA256SUMS lists both forms, so it verifies before or after decompression. Downloads are above and on the release page.
3. Flash
Do not use flash-custom.sh. It erases the whole chip, pushes 216 MB through FEL at ~220 kB/s (~16 min), does the on-device write with the board off USB and U-Boot's console on the invisible UART, and writes the wrong U-Boot image. It failed twice and reported success both times.
Instead, split it. Both halves are verifiable.
3a. Rootfs — written from Linux, over USB ethernet
Jumper FEL↔GND, replug, then boot a kernel from RAM:
sudo scripts/fel-usbconsole.sh nandonly \
"g_cdc.dev_addr=02:11:22:33:44:66 g_cdc.host_addr=02:11:22:33:44:55 modprobe.blacklist=sunxi_wdt break=premount"
sudo scripts/host-usbnet.sh # host end: 192.168.7.1
On the board (drive it with scripts/send.py 'cmd'):
ip link set usb0 up; ip addr add 192.168.7.2/24 dev usb0
ubiformat /dev/mtd5 -y # ~2 min, erases 2000 PEBs
ubiattach -m 5
ubimkvol /dev/ubi0 -N rootfs -m
nc 192.168.7.1 5555 | ubiupdatevol -s $(stat -c %s rootfs.ubifs) /dev/ubi0_0 -
Host side: nc -l -p 5555 < rootfs.ubifs (~18 min; NAND write is the bottleneck, not the link).
Verify before moving on — this is the step that saves hours:
mount -t ubifs ubi0:rootfs /mnt2
md5sum /mnt2/boot/vmlinuz-$KV /mnt2/boot/dtbs/$KV/sun5i-r8-chip.dtb
umount /mnt2; ubidetach -m 5
Both must match the host files.
3b. Boot chain — SPL + U-Boot only
Replug (jumper still fitted), then:
sudo scripts/flash-boot-only.sh
~5 MB of uploads, seconds on-device. Erase is limited to 0x0-0x1000000, so the rootfs at 0x3000000 survives. Wait for the board to reappear in FEL before touching anything — the script returning only means the upload finished.
Verify (boot from RAM again, then from the initramfs shell):
md5sum /dev/mtd2 # must equal md5 of u-boot-dtb.img padded to 4 MB
No network needed — mtd2 is exactly 4 MB.
Only U-Boot can be verified this way. mtd2/mtd3 are written through the normal ECC path, so they read back byte-identical to the file. The SPL is written raw with its own ECC padding (nand write.raw.noverify), so reading mtd0/mtd1 back — with dd, md5sum or nanddump --oob — runs data through an ECC layout it was not written in and returns garbage. That garbage differs between the two copies, because it depends on the physical block.
A correct SPL and a corrupt one are indistinguishable this way. Do not read a mismatch there as damage; it is the expected result even immediately after a successful write. The only test of the SPL is whether the board boots.
3c. Boot it
Remove the FEL jumper, replug. Expect ~60 s: SPL, U-Boot mounting UBIFS, kernel, UBI attach plus a full bad-block scan (~6.5 s, by design — nand-on-flash-bbt is deliberately not set).
3d. If a working board stops booting
Observed once on this board: it had been booting from NAND for weeks, was unplugged and replugged, and then did nothing at all — no console, no gadget, no sign of life. The rootfs turned out to be perfectly intact.
Find out which stage failed before writing anything. Fit the FEL jumper and:
sudo scripts/fel-uboot-nandboot.sh
This uploads a DRAM-init SPL and the production U-Boot into SRAM and lets U-Boot run its own bootcmd against NAND. Nothing is written to flash.
| result | meaning | fix |
|---|---|---|
| Debian boots, gadget appears | U-Boot, kernel and rootfs are all fine; only the on-NAND SPL stage is broken | flash-boot-only.sh — rootfs untouched |
| U-Boot runs, no kernel | UBIFS or the kernel image is damaged | rewrite the rootfs (3a) |
| nothing at all | earlier than U-Boot; check power and the FEL link itself | — |
In the observed case it was the first row, and flash-boot-only.sh fixed it in under a minute with the Debian install, WiFi config and packages all intact.
Two things that made this take longer than it should have:
- The board being reachable in FEL means very little. FEL lives in the BROM, so it works even when everything in NAND is unreadable. It proves the SoC and USB are alive, nothing more.
- The SPL readback trap above. Both copies "mismatching" looks like damning evidence and is worth nothing — see 3b.
The cause of the SPL failure was never established. Nothing writes to those partitions at runtime (they are ro, and outside any filesystem), so an apt upgrade or a userspace fault cannot reach them.
4. First contact
sudo scripts/host-usbnet.sh
ssh root@192.168.7.2 # password: chip
The password is expired on purpose — the first login has to set a new one before a shell is granted.
WiFi:
nmcli device wifi list
nmcli device wifi connect "SSID" password "PASSWORD"
The board has no RTC battery, so the clock starts from the newest filesystem timestamp. systemd-timesyncd fixes it once WiFi is up; until then apt will fail with "Not live until <date>" because repository signatures are not yet valid from the board's point of view.
5. Known cosmetic issues
WARNING: drivers/usb/gadget/function/u_serial.c:703 at gs_closeeach time agetty closes the gadget tty (60 s login timeout). Harmless, noisy on the console. Worth reporting upstream separately.- Kernel messages interleave with the getty on ttyGS0, so console logins are awkward. Use SSH.