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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,40 @@ jobs:
if: matrix.soc == 'nt98566' && steps.kmod.outputs.args != ''
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)
needs: build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
soc: [nt98562, nt98566]
steps:
- uses: actions/checkout@v4

- name: Install runtime dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libfdt1 libglib2.0-0 libpixman-1-0 libslirp0

- name: Download QEMU binary
uses: actions/download-artifact@v4
with:
name: qemu-arm
path: qemu-src/build/

- name: Make QEMU executable
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
- name: Install defib
run: pipx install git+https://github.com/OpenIPC/defib@a99ceee7b4ee3de5b59272bf4caf30346243e1e4

- name: Recover a blank flash over UART, through the straps and the fallback
run: bash qemu-boot/test-novatek-recovery.sh --soc ${{ matrix.soc }}

boot-fullhan:
name: Boot ${{ matrix.soc }}
needs: build
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ mask ROM (copies the loader to SRAM; stubs for its ROM routines hand their
arguments to a C service window; the ROM image in DRAM at 0 and read-only at
`0x80000000`), and the loader's closed DDR training runs against
`hw/misc/nvt-ddrc.c` (done / self-clearing bits, paged PHYs). `loader=off`
forces the stand-in. `qemu-boot/build-novatek-loader.sh` builds the SDKs'
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`,
`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` /
`nt98529` (NA51055 / NA51084), `nt98532` (NS02501), `nt98321` (NA51068)
Expand Down
53 changes: 50 additions & 3 deletions docs/novatek.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,9 +450,56 @@ way with `SOC=nt98566 FLASH=nand.bin`.
## Recovery boot

The silicon's ROM also takes its loader from SD, Ethernet (DHCP and TFTP),
USB or UART. That fetch is not modelled: `boot-file=` is what it delivered,
a host file copied to SRAM in place of the flash's first bytes, and
`boot-strap=` is the straps as TOP+0 reads them. From there the guest code
USB or UART. On NA51089 (NT98560/561/562/566) the ROM model runs that
fallback itself, as read from a dump of the ROM and the NT9856x ROM code
application note, when the flash has no valid loader and `rom-fallback=on`,
or whenever `boot-strap=` is not a flash strap:

- **Order:** flash, SD, Ethernet, USB, UART, then SD again. The straps pick
where it starts: 0xC UART, 4 Ethernet, 1 SD, 5 and 0xB USB.
- **Console letters on UART 0:**
- `NPIp>K9`: the flash loader fails the ROM's check.
- `S1K2`: no SD card.
- `HE00003500000037000000D1E1s100FX` then `S1` per DHCPDISCOVER, every
5 s, six of them, then `K:`.
- `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.
- **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`
CDBs, opset 0xC0):
- IS_NVT answers with the chip word.
- OPEN_DEVICE stops the stage's 30 s timeout.
- ADDR_READ and ADDR_WRITE; ADDR_WRITE acts even with a bad checksum.
- WRLOADER, then WAIT_DONE, which checks the loader and runs it or
prints `K9` and moves on.
- **The ROM's quirks are kept:** every 16 bytes are one command, nothing
resyncs, and the WRLOADER payload is decoded as commands too, so a
payload chunk that looks like one acts as one. Bytes that arrive outside
the UART stage are dropped, as a full FIFO drops them on silicon.
- **`rom-fast=on`** cuts the DHCP and USB waits tenfold. The 30 s UART
window is the host's, and stays.

OpenIPC's [defib](https://github.com/OpenIPC/defib) recovers a blank board
through it as it does a real one:

```sh
qemu-system-arm -M nt98562,flash-file=blank.bin,boot-strap=0xc \
-display none -nodefaults \
-chardev socket,id=s0,path=/tmp/nvt.sock,server=on,wait=off -serial chardev:s0 &
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.

`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
`boot-strap=` the straps as TOP+0 reads them. From there the guest code
is the real thing: an `Eth` loader runs Novatek's DHCP and TFTP client on
`nvt-eqos` against slirp, a `Uart` loader serves `openipc/nvt-uart-boot.py`
from OpenIPC/u-boot's `openipc-novatek` over `-serial tcp:...`.
Expand Down
95 changes: 95 additions & 0 deletions qemu-boot/test-novatek-recovery.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/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:
#
# 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
#
# 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.

set -euo pipefail

SOC=nt98562
DEFIB=${DEFIB:-defib}
QEMU=${QEMU:-qemu-src/build/qemu-system-arm}

while [ $# -gt 0 ]; do
case "$1" in
--soc) SOC=$2; shift 2 ;;
--defib) DEFIB=$2; shift 2 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done

WORK=$(mktemp -d)
QPID=
cleanup() {
[ -n "$QPID" ] && kill "$QPID" 2>/dev/null || true
if [ -n "${KEEP:-}" ]; then
echo "logs kept in $WORK"
else
rm -rf "$WORK"
fi
}
trap cleanup EXIT

python3 -c "open('$WORK/blank.bin', 'wb').write(b'\xff' * (8 << 20))"

# The prompt answers "version" with an OpenIPC U-Boot banner
prompt_answers() {
python3 - "$1" <<'EOF'
import socket, sys, time
s = socket.socket(socket.AF_UNIX)
s.connect(sys.argv[1])
s.settimeout(0.2)
s.sendall(b'\rversion\r')
out = b''
end = time.time() + 5
while time.time() < end and b'U-Boot 20' not in out:
try:
out += s.recv(4096)
except socket.timeout:
pass
sys.exit(0 if b'U-Boot 20' in out else 1)
EOF
}

fails=0
for way in ${WAYS:-strap fallback}; do
case $way in
strap) opts="boot-strap=0xc" ;;
fallback) opts="rom-fallback=on" ;;
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 &
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" &&
prompt_answers "$sock"; then
echo "PASS: $SOC $way: U-Boot from RAM answers at its prompt"
else
echo "FAIL: $SOC $way"
grep -v Downloading "$WORK/defib-$way.log" | tail -20
fails=$((fails + 1))
fi
kill "$QPID" 2>/dev/null || true
wait "$QPID" 2>/dev/null || true
QPID=
done
exit $fails
71 changes: 69 additions & 2 deletions qemu/hw/arm/novatek.c
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ struct NvtMachineState {

char *flash_file;
bool real_loader;
bool rom_fallback;
bool rom_fast;
char *boot_file;
uint32_t boot_strap;
uint32_t flash_jedec;
Expand Down Expand Up @@ -1499,6 +1501,30 @@ void nvt_rom_set_loader_file(const char *path)
nvt_rom_loader_file = path;
}

static bool nvt_rom_fallback, nvt_rom_fast;
static uint32_t nvt_rom_strap;

void nvt_rom_set_fallback(bool on, uint32_t strap, bool fast)
{
nvt_rom_fallback = on;
nvt_rom_strap = strap;
nvt_rom_fast = fast;
}

/*
* What the ROM model starts from flash (nvt-bootrom.c, rom_first_source):
* all but SD (1), Ethernet (4), USB (5, 0xB) and UART (0xC).
*/
bool nvt_rom_strap_is_flash(uint32_t strap)
{
switch (strap & 0xF) {
case 1: case 4: case 5: case 0xB: case 0xC:
return false;
default:
return true;
}
}

bool nvt_flash_has_loader(NvtFlashState *flash, uint64_t sram_base,
uint64_t sram_size)
{
Expand Down Expand Up @@ -1538,6 +1564,11 @@ void nvt_rom_boot(NvtFlashState *flash, const NvtRomConfig *rc)
if (nvt_rom_loader_file) {
qdev_prop_set_string(rom, "loader-file", nvt_rom_loader_file);
}
if (nvt_rom_fallback && rc->layout == 0) {
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);
}
sysbus_realize_and_unref(SYS_BUS_DEVICE(rom), &error_fatal);
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(rom), 0,
object_property_get_uint(OBJECT(rom), "svc-base",
Expand Down Expand Up @@ -1886,10 +1917,16 @@ static void novatek_common_init(MachineState *machine, const NvtSoCConfig *c)
arm_load_kernel(cpu[0], machine, &binfo);

if (!machine->kernel_filename && nms->flash_file) {
/* NA51089's ROM: the fallback, asked for or implied by the straps */
bool fallback = nms->real_loader && !c->na51055 &&
(nms->rom_fallback ||
!nvt_rom_strap_is_flash(nms->boot_strap));

nvt_rom_set_loader_file(nms->boot_file);
nvt_rom_set_fallback(fallback, nms->boot_strap, nms->rom_fast);
if (nms->real_loader &&
nvt_flash_has_loader(NVT_FLASH(flash), NVT_SRAM_BASE,
NVT_SRAM_SIZE)) {
(fallback || nvt_flash_has_loader(NVT_FLASH(flash), NVT_SRAM_BASE,
NVT_SRAM_SIZE))) {
/* NA51055's ROM has its routines at fixed addresses */
nvt_rom_boot(NVT_FLASH(flash), &nvt_rom_cfg[c->na51055 ? 1 : 0]);
} else {
Expand Down Expand Up @@ -1976,6 +2013,26 @@ static void nvt_machine_set_boot_strap(Object *obj, Visitor *v,
visit_type_uint32(v, name, &NVT_MACHINE(obj)->boot_strap, errp);
}

static bool nvt_machine_get_rom_fallback(Object *obj, Error **errp)
{
return NVT_MACHINE(obj)->rom_fallback;
}

static void nvt_machine_set_rom_fallback(Object *obj, bool value, Error **errp)
{
NVT_MACHINE(obj)->rom_fallback = value;
}

static bool nvt_machine_get_rom_fast(Object *obj, Error **errp)
{
return NVT_MACHINE(obj)->rom_fast;
}

static void nvt_machine_set_rom_fast(Object *obj, bool value, Error **errp)
{
NVT_MACHINE(obj)->rom_fast = value;
}

static void nvt_machine_instance_init(Object *obj)
{
NVT_MACHINE(obj)->real_loader = true;
Expand Down Expand Up @@ -2005,6 +2062,16 @@ static void nvt_machine_class_init(ObjectClass *oc, const void *data)
object_class_property_set_description(oc, "boot-strap",
"boot strap pins as TOP+0 reads them (BS[3:0]: NT9856x 4 Ethernet, "
"0xC UART)");
object_class_property_add_bool(oc, "rom-fallback",
nvt_machine_get_rom_fallback, nvt_machine_set_rom_fallback);
object_class_property_set_description(oc, "rom-fallback",
"NA51089: with no valid loader in flash, run the mask ROM's own "
"fallback (SD, Ethernet, USB, UART) as the silicon does, instead of "
"the stand-in; implied by boot-strap other than flash");
object_class_property_add_bool(oc, "rom-fast",
nvt_machine_get_rom_fast, nvt_machine_set_rom_fast);
object_class_property_set_description(oc, "rom-fast",
"the ROM fallback's waits before UART (DHCP 5 s, USB 30 s) / 10");
object_class_property_add_str(oc, "flash-file",
nvt_machine_get_flash_file, nvt_machine_set_flash_file);
object_class_property_set_description(oc, "flash-file",
Expand Down
Loading
Loading