Serial lines
Four commands work over a serial line rather than over USB. recover writes a bootloader to a
StarFive board’s flash, and uartboot boots a StarFive board into U-Boot without writing
anything. console watches what a board prints on its serial console, and uboot drives a
bootloader prompt.
Recovering a StarFive board
StarFive’s JH7110 boards, such as the VisionFive 2 and the Milk-V Mars CM, recover over a serial
line. Their BootROM has no USB, and acts as an XMODEM receiver. The board therefore does not
appear in list, and takes no --device. Strap the board into UART recovery, and name the
serial port with --port. recover then waits for you to power the board on.
recover sends StarFive’s recovery agent to the BootROM. It then writes the board’s QSPI NOR
flash through the agent’s menu:
pyrographer recover --port /dev/ttyUSB0 --agent jh7110-recovery-20230322.bin \
--spl u-boot-spl.bin.normal.out --uboot u-boot.itb
This will write the boot flash of a StarFive JH7110 board over /dev/ttyUSB0.
agent 161.41 KiB, sent to the BootROM first
SPL 137.72 KiB at 0x0, agent menu entry 0, its header checked
U-Boot 1.24 MiB at 0x100000, agent menu entry 2
The agent writes a backup copy of the SPL at 0x200000, inside the U-Boot region. U-Boot is written after it and overwrites that copy, as StarFive's own procedure does.
Warning: this write is not read back. The recovery protocol cannot read
flash. Each block's acknowledgment confirms that the board received it,
and the agent's verdict that its write finished, not that the flash
holds it.
Nothing here can be undone.
Proceed? [y/N]
The whole recovery is [UNVERIFIED] against hardware.
recover takes three files:
--agenttakes the recovery agent, ajh7110-recovery-*.binfrom StarFive’s Tools repository.recoverchecks its header, and that it carries the agent’s menu.--spltakes the SPL, as a rawu-boot-spl.binor as au-boot-spl.bin.normal.out.recoveradds the 1024-byte StarFive header to a raw file, and checks the header a.normal.outalready carries.--uboottakes the U-Boot payload, a FIT image such asu-boot.itb, and sends it unchanged.
At least one of --spl and --uboot is required, and --spl requires --uboot as well. The
agent writes a second copy of the SPL at 2 MiB, inside the U-Boot region. An SPL written alone
therefore breaks the U-Boot already on the board. With both files, the SPL is written first and
U-Boot second, as StarFive’s own procedure does.
recover refuses a file larger than the agent writes: 1 MiB for the SPL with its header, and
15 MiB for U-Boot. --dry-run prints the plan and stops without opening the port. --yes skips
the confirmation, as it does for flash.
recover types at the agent only when the agent asks. Whenever the agent is not receiving a
file, it reads a line of input, and it reads stray bytes as typing. recover waits for the
agent’s prompt before it types a menu choice, and for the agent to ask for a file before it sends
one. If the agent answers a block with text, recover stops at once.
OTP fuses are never touched. The agent’s menu also offers to burn the SoC’s OTP fuses, which
cannot be undone. recover never chooses that entry. If the fuse menu ever appears, recover
stops, sends nothing more, and tells you to power the board off.
The write is not read back. The recovery protocol cannot read flash. After each file the agent
reports updata success or updata fail, and recover reports a failure as an error. Neither
verdict proves that the flash holds the file. Every other write in pyrographer reads each window
back. The plan states this before you confirm, and recover repeats it at the end:
Recovery complete. The agent reported every write as done. This board's write cannot be read back.
Power off, return the boot strap to normal, and power on.
recover writes the QSPI NOR flash and not the eMMC. The agent’s eMMC entries write the SPL over
the start of the disk, where its partition table is. To write a board’s eMMC, boot U-Boot in RAM
and write the eMMC as a disk, as the next section describes.
On Linux, opening a serial port requires your user to be in the dialout group, or in uucp on
some distributions. If the open is refused, pyrographer says so. The window has the same recovery
flow, as Recovering a StarFive board describes.
Booting a StarFive board into U-Boot
uartboot brings a StarFive board up into U-Boot in RAM, and writes nothing. It sends an SPL to
the BootROM, and the SPL loads U-Boot over the same serial line by YMODEM. uartboot then stops
U-Boot at its prompt. It is the serial counterpart of db, which loads U-Boot into a Rockchip
board over USB.
pyrographer uartboot --port /dev/ttyUSB0 \
--spl u-boot-spl.bin.normal.out --uboot u-boot.itb
Booting U-Boot in RAM on a StarFive JH7110 board over /dev/ttyUSB0. Nothing is written to the board.
SPL 137.72 KiB, sent to the BootROM, its header checked
U-Boot 1.24 MiB, sent to the SPL by YMODEM
U-Boot is running in RAM, stopped at its prompt. Nothing was written to the board.
Run `pyrographer uboot --port /dev/ttyUSB0 --gadget ums` to hand its eMMC to this machine as a disk.
The SPL must be a mainline U-Boot SPL built with CONFIG_SPL_YMODEM_SUPPORT, which mainline’s
VisionFive 2 configuration sets. With the board still strapped for UART recovery, that SPL loads
U-Boot from the serial line. StarFive’s own SPL loads U-Boot from SPI flash instead, and
uartboot reports the device the SPL names. --prompt names a U-Boot prompt other than => .
uartboot is [UNVERIFIED] against hardware.
Writing a StarFive eMMC as a disk
A RAM-booted U-Boot built with CONFIG_CMD_USB_MASS_STORAGE hands the board’s eMMC to this
machine as a disk. Mainline’s VisionFive 2 configuration does not enable it, so U-Boot has to be
built with it. The block-device commands then write the disk, and read back
every window they write:
- Strap the board into UART recovery. Connect the serial adapter, and a USB cable from this machine to the board’s USB device port.
- Run
uartboot, and power the board on. - Run
uboot --gadget umson the same port. If the eMMC is notmmc:0, name it with--gadget-dev.uboot --cmd 'mmc list'lists the board’s MMC devices. - Run
list --blocksto find the disk, and write it withflash --device.
The route is [UNVERIFIED] against hardware.
Watching a serial console
console watches a board’s serial console for text you name, such as the verdict of a self-test
the board runs at boot. Give the port, the text that means the board worked (--expect), and the
text that means it did not (--or-fail):
pyrographer console --port /dev/ttyUSB0 --expect 'selftest: PASS' --or-fail 'selftest: FAIL'
[ 0.000000] Booting Linux ...
selftest: PASS
Saw "selftest: PASS" on /dev/ttyUSB0.
The console watch is [UNVERIFIED] against hardware.
console writes the transcript to standard error as it arrives, and the verdict to standard
output. A script can read the verdict and log the transcript. If an --or-fail pattern
appears, console exits non-zero. Its message is then a plain sentence rather than an error:,
because the board reported the failure and pyrographer ran correctly.
Both options can repeat. console reports whichever pattern the board printed first, in
whatever order the options were given. If a board prints FAIL at stage one and PASS at stage
two, console reports the FAIL.
Patterns match as bytes, with no line splitting and no normalization. A trailing space is part of the pattern. To write a byte that your shell removes or interprets, use one of these escapes:
\n,\rand\t\0\\\xNN, a byte in hex
console refuses an unknown escape as it reads the option, so a typo cannot become a pattern
that never matches.
--reads <n> sets how long console waits, counted in reads rather than seconds. On a quiet
line, each read waits about a second. It returns as soon as bytes arrive. --baud <rate> sets
the line’s baud rate, which defaults to 115200.
If no pattern matches within the read budget, console reports that the pattern did not appear.
It does not claim the board failed. The cause can be a failure, a slow boot, a wrong baud rate,
or a console on another UART:
pyrographer console --port /dev/ttyUSB0 --expect PASS --reads 5
error: protocol error: nothing matching "PASS" arrived on the console within 5 reads. Recent
console output:
U-Boot SPL 2026.04
console reads only what a board prints by itself. It does not drive a login prompt, because a
login needs credentials and pyrographer handles none.
Driving a U-Boot prompt
uboot drives a U-Boot prompt, the last stage before an operating system, over the same serial
port. Every form first sends a bare newline to interrupt autoboot. That stops a countdown, and on
a board already at a prompt it only produces another prompt.
The uboot driver is [UNVERIFIED] against hardware.
Starting a gadget
--gadget starts a USB gadget at the U-Boot prompt, which exposes the board’s flash over USB.
It completes the RAM-boot that db starts, once db has put a full mainline U-Boot into DRAM
over USB. With --gadget rockusb, the board returns to list as a loader:
pyrographer uboot --port /dev/ttyUSB0 --gadget rockusb
Hit any key to stop autoboot: 2
=>
At the prompt.
rockusb 0 mmc 0
Ran `rockusb 0 mmc 0`. The gadget is running, so the prompt does not return.
Run `pyrographer list` to find the board on USB.
A running gadget holds the console and never returns to the prompt. uboot therefore treats a
prompt that does not come back as success. If the prompt does come back, the command has
returned, for example on an unknown command or a missing device. uboot then reports what
U-Boot printed as the failure.
--gadget ums starts U-Boot’s USB mass-storage gadget instead. The board’s flash then appears in
list --blocks as a disk, and the block-device verbs read and write it. On an
RK3576, dd read the whole eMMC through this gadget, past the 32 MiB point where rkbin’s loader
returns constant fill.
Mainline U-Boot’s rockusb gadget does not answer K_FW_READ_FLASH_INFO, so info, partitions
and --partition fail against it. A dump by LBA requests no flash information, so this
failure does not affect it. Whether that gadget reads past the 32 MiB point is [UNVERIFIED].
--gadget-dev names the block device the gadget exposes, and defaults to mmc:0. If the gadget
is not on USB controller 0, use the form <controller>:<interface>:<index>.
Overriding the boot order
--boot-from sets what the board boots from, for one boot:
pyrographer uboot --port /dev/ttyUSB0 --boot-from mmc0
This will change what the board on /dev/ttyUSB0 boots from, for one boot.
boots from now mmc1 mmc0 usb0 pxe dhcp
would boot from mmc0
Note: the change is not saved. U-Boot keeps its environment in RAM until
`saveenv` writes it to storage, and the override never sends `saveenv`. The
board's own boot order is untouched, and the next reset restores it. The
board boots immediately after the order is set.
Proceed? [y/N]
The override is never saved. pyrographer has no saveenv operation at all. The board’s own
boot order is untouched in every case, and the next reset restores it.
To build the plan, uboot asks the board: it runs printenv boot_targets and shows the answer
beside the new order. --dry-run asks that question and stops.
The confirmation is a plain yes, not a typed coordinate, because an override has no second board and destroys nothing.
Running one command
--cmd types one command at the prompt and prints U-Boot’s response:
pyrographer uboot --port /dev/ttyUSB0 --cmd 'mmc info'
Device: mmc@2a330000
Manufacturer ID: 15
Capacity: 58.2 GiB
--cmd is ungated. U-Boot runs exactly what you type, saveenv included, with no plan and
no confirmation. A saveenv writes U-Boot’s environment to the board’s storage. --cmd is the
one command in pyrographer that works this way, and its help text says so.
--prompt names the prompt of a board whose prompt is not => , such as U-Boot> , or whatever
CONFIG_SYS_PROMPT was built with. The trailing space is part of the prompt. pyrographer keeps
no catalog of board prompts, so give the one the transcript shows.
The window has the same console flow, as The serial console describes.