Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,7 @@ jobs:
run: bash qemu-boot/test-novatek-hdal.sh --soc ${{ matrix.soc }} ${{ steps.kmod.outputs.args }} --panel nt35510

boot-novatek-recovery:
name: Novatek recovery ${{ matrix.soc }} (defib, mask ROM fallback)
name: Novatek recovery ${{ matrix.soc }} (defib, mask ROM UART and Ethernet)
needs: build
runs-on: ubuntu-24.04
strategy:
Expand All @@ -1328,11 +1328,13 @@ jobs:
run: chmod +x qemu-src/build/qemu-system-arm

# A reviewed defib commit (Novatek UART and Ethernet recovery,
# OpenIPC/defib#169 and #170), not whatever master holds
# OpenIPC/defib#169, #170 and #171), not whatever master holds
- name: Install defib
run: pipx install git+https://github.com/OpenIPC/defib@a99ceee7b4ee3de5b59272bf4caf30346243e1e4
run: pipx install git+https://github.com/OpenIPC/defib@74e1bccf574b6f2e055f9ae93ad5ca88c30206d8

- name: Recover a blank flash over UART, through the straps and the fallback
# UART through the straps and through the fallback, then Ethernet
# over a tap device (sudo: the tap, DHCP's port 67, TFTP's 69)
- name: Recover a blank flash over UART and over Ethernet
run: bash qemu-boot/test-novatek-recovery.sh --soc ${{ matrix.soc }}

boot-fullhan:
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,8 @@ arguments to a C service window; the ROM image in DRAM at 0 and read-only at
`hw/misc/nvt-ddrc.c` (done / self-clearing bits, paged PHYs). `loader=off`
forces the stand-in. On NA51089, `rom-fallback=on` (or a `boot-strap=` that
is not a flash strap) runs the ROM's own fallback when the flash has no
valid loader: SD, Ethernet (no DHCP yet), USB and its UART download engine,
which `defib burn` drives as on silicon (`qemu-boot/test-novatek-recovery.sh`,
valid loader: SD, Ethernet (its DHCP/TFTP client on `nvt-eqos`), USB and its
UART download engine, which `defib burn` (and `burn --nic`) drive as on silicon (`qemu-boot/test-novatek-recovery.sh`,
`docs/novatek.md#recovery-boot`). `qemu-boot/build-novatek-loader.sh` builds the SDKs'
loaders; `docs/novatek.md#the-real-first-stage-loader` has what each loader
waits for. Part-number machines `nt98560` / `nt98561` (NA51089), `nt98523` /
Expand Down
33 changes: 28 additions & 5 deletions docs/novatek.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,20 @@ or whenever `boot-strap=` is not a flash strap:
- `U`, then `K:` after 30 s.
- Then UART, which prints nothing.
- `T` on the jump.
- **Ethernet** sends nothing yet: the stage behaves like a network with no
DHCP server.
- **Ethernet** is the ROM's DHCP and TFTP client, driving `nvt-eqos`
before any loader does:
- A MAC of `00:00:00` and three random bytes, new every boot.
- It takes the first *broadcast* offer and drops a unicast one, as
silicon did against dnsmasq.
- DHCPREQUEST, the ACK, an ARP request for the next-server, then one
TFTP read request for `loader.bin` from port 2962, sent to the MAC the
offer came from.
- It then waits for DATA from that server with no timeout and no
retransmission. A network DHCP server that wins the race holds it
there for good, as on a real LAN.
- The letters match silicon's: `aOfIP <ip> SRV <srv>`, `MSK`, `bS2`,
`cAkS3S4`, `CD` for ARP, `t<block>` per block, `Dn`, then `T`.
- With no network, nobody answers: six DHCPDISCOVERs, then `K:`.
- **The loader check:** header word 1 is the address, word 9 the size,
halfword 0x30 is 0xAA55, and the halfwords plus their indices sum to 0.
- **The UART stage** is the ROM's command engine (the SDK's `scsi_op.h`
Expand All @@ -493,9 +505,20 @@ qemu-system-arm -M nt98562,flash-file=blank.bin,boot-strap=0xc \
defib burn -c nt98562 -p socket:///tmp/nvt.sock -b
```

`qemu-boot/test-novatek-recovery.sh` does that both ways in, through the
straps and through the fallback from a blank NOR, and checks that U-Boot
answers at its prompt.
Over Ethernet, `defib burn --nic` answers the ROM's DHCP and serves
`loader.bin` and the U-Boot pack. The real Ethernet loader then does its own
DHCP and TFTP and starts U-Boot. Use a tap device (root for ports 67 and 69):

```sh
qemu-system-arm -M nt98562,flash-file=blank.bin,boot-strap=4 -display none -nodefaults \
-chardev socket,id=s0,path=/tmp/nvt.sock,server=on,wait=off -serial chardev:s0 \
-nic tap,ifname=tap0,script=no,downscript=no &
sudo defib burn -c nt98562 -p socket:///tmp/nvt.sock --nic tap0
```

`qemu-boot/test-novatek-recovery.sh` runs all three ways in: through the
straps, through the fallback from a blank NOR, and over Ethernet. Each time
it checks that U-Boot answers at its prompt.

`boot-file=` still stands for a loader one of those sources delivered: a
host file copied to SRAM in place of the flash's first bytes, with
Expand Down
61 changes: 47 additions & 14 deletions qemu-boot/test-novatek-recovery.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
#!/bin/bash
# Recover a Novatek NT9856x with a blank flash over UART, the way a real
# board is recovered: OpenIPC's defib talks to the mask ROM model
# (nvt-bootrom's fallback), which takes the write loader, which takes the
# U-Boot pack and starts it; defib then stops U-Boot's autoboot and the
# prompt must answer. Two ways in:
# Recover a Novatek NT9856x with a blank flash, the way a real board is
# recovered: OpenIPC's defib talks to the mask ROM model (nvt-bootrom's
# fallback), and U-Boot ends up running from RAM; defib then stops its
# autoboot and the prompt must answer. Three ways in:
#
# strap boot-strap=0xc: the ROM goes straight to UART
# fallback rom-fallback=on, straps at NOR: the blank flash fails, the ROM
# tries SD, Ethernet (no DHCP server here) and USB, then UART
# strap boot-strap=0xc: the ROM goes straight to UART, takes the write
# loader, which takes the U-Boot pack
# fallback rom-fallback=on, straps at NOR, no network: the blank flash
# fails, the ROM tries SD, Ethernet (nobody answers) and USB,
# then UART, as "strap"
# eth boot-strap=4 on a tap device: defib burn --nic answers the
# ROM's DHCP and serves loader.bin and the pack over TFTP.
# Needs sudo (the tap, ports 67 and 69).
#
# rom-fast=on cuts the waits before the UART stage tenfold. The loader and
# the pack come from novatek-recovery.tar.gz on OpenIPC/u-boot's "novatek"
# release, which defib downloads itself.
#
# Usage: test-novatek-recovery.sh [--soc nt98562] [--defib "defib"]
# WAYS="strap" runs one way only; KEEP=1 keeps the logs.
# WAYS="strap eth" picks the ways (default all three); KEEP=1 keeps the
# logs; TAP names the tap device (default nvt-rec0, 192.168.77.1/24).

set -euo pipefail

Expand All @@ -32,8 +37,17 @@ done

WORK=$(mktemp -d)
QPID=
TAP=${TAP:-nvt-rec0}
TAP_IP=192.168.77.1/24
MADE_TAP=
ADDED_IP=
cleanup() {
[ -n "$QPID" ] && kill "$QPID" 2>/dev/null || true
if [ -n "$MADE_TAP" ]; then
sudo ip link del "$TAP" 2>/dev/null || true
elif [ -n "$ADDED_IP" ]; then
sudo ip addr del "$TAP_IP" dev "$TAP" 2>/dev/null || true
fi
if [ -n "${KEEP:-}" ]; then
echo "logs kept in $WORK"
else
Expand Down Expand Up @@ -64,23 +78,42 @@ EOF
}

fails=0
for way in ${WAYS:-strap fallback}; do
for way in ${WAYS:-strap fallback eth}; do
net=()
burn=(timeout 300 $DEFIB burn -c "$SOC" -b)
ready="U-Boot console ready"
case $way in
strap) opts="boot-strap=0xc" ;;
fallback) opts="rom-fallback=on" ;;
eth)
opts="boot-strap=4"
# A tap of the caller's keeps its state; one made here goes
if ! ip link show "$TAP" > /dev/null 2>&1; then
sudo ip tuntap add "$TAP" mode tap user "$(id -un)"
MADE_TAP=1
sudo ip link set "$TAP" up
fi
if ! ip -4 addr show dev "$TAP" | grep -q "inet ${TAP_IP%/*}/"; then
sudo ip addr add "$TAP_IP" dev "$TAP"
ADDED_IP=1
fi
net=(-nic "tap,ifname=$TAP,script=no,downscript=no")
burn=(sudo timeout 300 "$(command -v $DEFIB)" burn -c "$SOC"
--nic "$TAP")
ready="U-Boot is up from RAM"
;;
esac
sock="$WORK/$way.sock"
"$QEMU" -M "$SOC,flash-file=$WORK/blank.bin,rom-fast=on,$opts" \
-display none -nodefaults \
-chardev "socket,id=s0,path=$sock,server=on,wait=off" \
-serial chardev:s0 > "$WORK/qemu-$way.log" 2>&1 &
-serial chardev:s0 "${net[@]}" > "$WORK/qemu-$way.log" 2>&1 &
QPID=$!
for _ in $(seq 100); do [ -S "$sock" ] && break; sleep 0.05; done

echo "=== $SOC, $way: defib burn"
if timeout 300 $DEFIB burn -c "$SOC" -p "socket://$sock" -b \
> "$WORK/defib-$way.log" 2>&1 &&
grep -q "U-Boot console ready" "$WORK/defib-$way.log" &&
if "${burn[@]}" -p "socket://$sock" > "$WORK/defib-$way.log" 2>&1 &&
grep -q "$ready" "$WORK/defib-$way.log" &&
prompt_answers "$sock"; then
echo "PASS: $SOC $way: U-Boot from RAM answers at its prompt"
else
Expand Down
6 changes: 6 additions & 0 deletions qemu/hw/arm/novatek.c
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,7 @@ void nvt_rom_set_loader_file(const char *path)

static bool nvt_rom_fallback, nvt_rom_fast;
static uint32_t nvt_rom_strap;
static DeviceState *nvt_rom_eth; /* the MAC its Ethernet boot drives */

void nvt_rom_set_fallback(bool on, uint32_t strap, bool fast)
{
Expand Down Expand Up @@ -1568,6 +1569,10 @@ void nvt_rom_boot(NvtFlashState *flash, const NvtRomConfig *rc)
qdev_prop_set_bit(rom, "fallback", true);
qdev_prop_set_bit(rom, "fast", nvt_rom_fast);
qdev_prop_set_uint8(rom, "strap", nvt_rom_strap & 0xF);
if (nvt_rom_eth) {
object_property_set_link(OBJECT(rom), "eth", OBJECT(nvt_rom_eth),
&error_fatal);
}
}
sysbus_realize_and_unref(SYS_BUS_DEVICE(rom), &error_fatal);
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(rom), 0,
Expand Down Expand Up @@ -1843,6 +1848,7 @@ static void novatek_common_init(MachineState *machine, const NvtSoCConfig *c)
qdev_prop_set_uint32(dev, "dsl-unit", c->chip_id == 0x4821 ? 4 : 8);
qemu_configure_nic_device(dev, true, NULL);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
nvt_rom_eth = dev;
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(dev), 0, NVT_ETH_BASE, 1);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[NVT_IRQ_ETH]);

Expand Down
Loading
Loading