Projekte

NextThing C.H.I.P.: Mainline Linux on Hardware the Wiki Said Couldn't Run It

A NextThing C.H.I.P. — Allwinner R8 / sun5i, single-core Cortex-A8, 512 MB RAM, 8 GB Hynix MLC NAND, discontinued in 2018 — now boots current mainline Linux 7.1.5 and Debian 13 (trixie), standalone from its onboard NAND, with working SSH and WiFi. The linux-sunxi wiki states flatly that "the mainline kernel can not interact with the builtin NAND." That turned out to be fixable, and the reason it hung was not what it looked like.


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.


What Works, What Doesn't

Verified on real hardware, not just in theory.

Verified working

NAND rootfs (UBI/UBIFS) USB gadget ethernet + serial console SSH WiFi (RTL8723BS) Bluetooth — pairing, bonding, SDP DRM / lima cedrus video decode MMC NTP

Known gaps

  • Bluetooth LE scanning failshcitool lescan returns 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 apt fails until NTP syncs over WiFi.

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.


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.


Building and flashing a complete C.H.I.P. image

Mirrors docs/BUILD-AND-FLASH.md in the repository — if they ever disagree, the repository copy is the one that is kept current.

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:

  1. Kernel — drivers/clocksource/timer-sun4i.c
    if (evt <= TIMER_SYNC_TICKS)
            return -ETIME;
    at the top of sun4i_clkevt_next_event() (and the same in timer-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 registered min_delta — that makes the guard unreachable and reintroduces the bug.
  2. Devicetree — arch/arm/boot/dts/allwinner/sun5i-r8-chip.dts
    &nfc {
            status = "okay";
            nand@0 {
                    nand-ecc-mode = "hw";
                    nand-ecc-step-size = <1024>;
                    nand-ecc-strength = <56>;
            };
    };
    Both ECC properties are required (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 bare C.H.I.P. board with the SK hynix NAND chip in the center and the RTL8723BS WiFi/Bluetooth module beside it
    The chip in question: the SK hynix MLC NAND, center. The blue module to its right is the RTL8723BS WiFi/Bluetooth.
  3. Flashing — write u-boot-dtb.img, NOT u-boot-dtb.bin
    The SPL is built with CONFIG_SPL_LEGACY_IMAGE_SUPPORT=y and CONFIG_SPL_RAW_IMAGE_SUPPORT unset, so it requires the legacy mkimage header (0x27051956). A raw .bin is silently rejected and the board never boots even though U-Boot is byte-perfect in flash. flash-custom.sh still has this bug; flash-boot-only.sh is 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

NextThing C.H.I.P. powered over micro-USB, with a red jumper wire connecting the FEL pin to GND
The FEL↔GND jumper (red wire): with it fitted, the board enters FEL mode on the next replug and can be driven entirely over USB.

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.

resultmeaningfix
Debian boots, gadget appearsU-Boot, kernel and rootfs are all fine; only the on-NAND SPL stage is brokenflash-boot-only.sh — rootfs untouched
U-Boot runs, no kernelUBIFS or the kernel image is damagedrewrite the rootfs (3a)
nothing at allearlier 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_close each 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.