bty via bty-usbboot-rpi (Raspberry Pi flasher)

A USB-bootable arm64 image that runs the bty flash TUI on a Pi itself. The headline use case is the Compute Module 5 mounted in a CM5 IO-board inside an enclosure: with bty-usbboot-rpi the operator no longer has to disassemble the case, set the eMMC boot jumper, run rpiboot from a host PC, flash via Etcher, and reassemble. Plug the USB stick into one of the IO-board’s USB ports, power on, run the wizard, done. The same image also works on standalone Pi5 and Pi4 boards for ad-hoc reflashing.

Targets (when flashing from inside the bty TUI):

  • eMMC (/dev/mmcblk0 on the CM5 IO-board): the non-removable storage the OS will boot from. Headline target.

  • NVMe (/dev/nvme0n1 on a Pi5 / CM5 with a PCIe NVMe HAT): also non-removable in practice; second tier-1 target.

  • SD card (/dev/mmcblk1): the wizard surfaces it too, but the operator can usually just pop the SD card out and flash it on another host, so it’s not the workflow this tutorial is optimised for.

Prerequisites

You need

Notes

A USB stick, 4 GiB or larger

The image is ~600 MiB compressed.

A Pi target: CM5 on IO-board, Pi5, or Pi4

Older Pi3 boards lack USB-boot support and aren’t covered here.

The Pi’s BOOT_ORDER set to try USB

CM5 default 0xf41 (NVMe, USB, SD, loop) USB-boots when NVMe is empty. Pi5 default tries SD first; set BOOT_ORDER=0xf14 via sudo rpi-eeprom-config --edit or pop the SD card. Pi4 needs an EEPROM update done from any working Pi OS once.

A target image in the catalog

The default safl/nosi catalog ships rpios variants for arm64: debian-13-headless, rpios-13-headless, rpios-13-desktop, plus the Pi-flavoured nosi builds.

Step 1: Download the image

Each tagged release publishes the arm64 Pi flasher as a release asset. releases/latest/download/ always redirects to the newest version:

https://github.com/safl/bty/releases/latest/download/bty-usbboot-rpi-arm64-vX.Y.Z.img.gz
https://github.com/safl/bty/releases/latest/download/bty-usbboot-rpi-arm64-vX.Y.Z.img.gz.sha256

Verify the sha256 before writing.

Step 2: Write the image to a USB stick

gunzip -c bty-usbboot-rpi-arm64-vX.Y.Z.img.gz | sudo dd of=/dev/sdX bs=4M conv=fsync
sudo sync

Replace /dev/sdX with the USB stick’s device (verify with lsblk first; the wrong target overwrites itself happily).

Step 3: Boot the Pi from the stick

  1. Power off the target Pi.

  2. Plug the USB stick into one of the Pi’s USB ports (USB 3 on Pi4/Pi5; the IO-board’s USB-A on a CM5 carrier).

  3. (Pi5 / Pi4 only) make sure the BOOT_ORDER actually reaches USB. CM5 default works out of the box for a blank eMMC.

  4. Power on.

You should see:

  • The Pi rainbow splash (firmware OK).

  • Kernel boot messages on HDMI + serial (UART on GPIO14/15 if you have a USB-TTL adapter; the cmdline carries both).

  • The bty TUI on tty1.

Step 4: Flash a target

In the TUI:

  1. Select an image from the catalog. The default catalog (safl/nosi) lists every arm64-suitable variant.

  2. Select a target disk. The wizard shows:

    • /dev/mmcblk0 (eMMC on a CM5 IO-board).

    • /dev/nvme0n1 (NVMe HAT on Pi5 / CM5).

    • /dev/mmcblk1 (the operator-removable SD card slot).

  3. Confirm and let bty stream the image through.

When the flash finishes, power off, remove the USB stick, and power back on. The Pi falls through the BOOT_ORDER to the target you just flashed (eMMC or NVMe), and the freshly-imaged OS comes up.

Reflashing: keep USB ahead of eMMC / NVMe

Once the first flash succeeds, internal storage is bootable, and the Pi’s default boot order prefers it – the next power-on boots the freshly-flashed eMMC (or NVMe) instead of re-entering the USB flasher. To reflash on demand, bias the bootloader to try USB first. Set it in the EEPROM from any working Pi OS (or the target you just flashed):

sudo rpi-eeprom-config --edit
# Read right-to-left: 4=USB-MSD, 1=SD/eMMC, 6=NVMe, f=restart (loop).
# So: try USB first, then eMMC, then NVMe, then loop.
BOOT_ORDER=0xf614
# NVMe-over-PCIe only: needed on a Pi5 with a non-HAT+ adapter or on a
# CM4/CM5 carrier board. Omit it on a Pi4 (no PCIe) or if you only
# target eMMC/SD/USB.
PCIE_PROBE=1

BOOT_ORDER is the same mechanism on Pi4, Pi5, CM4, and CM5 – one EEPROM bootloader, identical nibble meanings – so the USB-first value above works on all of them (the 6 NVMe nibble is simply inert on a Pi4, which has no PCIe). Only PCIE_PROBE is hardware-specific, as noted above.

With USB first, the flasher always wins while the stick is inserted, and the target still boots normally once you remove it – no jumper or SD-card dance between flashes.

Alternatives if you’d rather not change the EEPROM (or you keep eMMC ahead of USB, e.g. BOOT_ORDER=0xf421):

  • Pull the USB stick before each flash run; the BOOT_ORDER falls through to USB only when prior targets fail.

  • Hold the IO-board’s nRPIBOOT jumper (or the dedicated “boot select” switch on some carriers) on power-on; the Pi enters firmware-recovery mode and prefers USB.

Local verification with QEMU (developer-only)

Real Pi firmware bootflow is best verified on hardware; QEMU’s -M raspi4b model is incomplete and doesn’t match real Pi behaviour for USB / NVMe / PCIe enumeration. For a quick “does the squashfs even boot” sanity check on a developer host (extracts the lb kernel + initrd from the image’s FAT partition and boots a generic arm64 VM):

mkdir -p /tmp/rpi-extract && cd /tmp/rpi-extract
# Substitute the version you downloaded. Latest is
# https://github.com/safl/bty/releases/latest/download/bty-usbboot-rpi-arm64.img.gz
gunzip -c ~/Downloads/bty-usbboot-rpi-arm64-v*.img.gz > image.img
mcopy -i image.img@@1M ::/vmlinuz .
mcopy -i image.img@@1M ::/initrd.img .

# Stand in for an eMMC / NVMe target the wizard can flash to.
qemu-img create -f qcow2 fake-emmc.qcow2 8G

qemu-system-aarch64 \
    -M virt -cpu cortex-a72 -m 2G -smp 2 \
    -kernel vmlinuz -initrd initrd.img \
    -append "boot=live components console=ttyAMA0,115200 root=LABEL=BTY_LIVE rootwait" \
    -drive if=none,id=stick,format=raw,file=image.img \
    -device virtio-blk-device,drive=stick \
    -drive if=none,id=target,format=qcow2,file=fake-emmc.qcow2 \
    -device virtio-blk-device,drive=target \
    -nographic -serial mon:stdio

The TUI banner should reach the serial console within ~30 s; lsblk inside the VM should show both virtio-blk devices. Pi firmware bootflow + real GPU output are not exercised by this path; that’s hardware-test only.