diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1121c8..0d06e3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index e9d0e54..9f40b94 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/docs/novatek.md b/docs/novatek.md index 5cd3b49..b20fc7d 100644 --- a/docs/novatek.md +++ b/docs/novatek.md @@ -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:...`. diff --git a/qemu-boot/test-novatek-recovery.sh b/qemu-boot/test-novatek-recovery.sh new file mode 100755 index 0000000..e0fbb89 --- /dev/null +++ b/qemu-boot/test-novatek-recovery.sh @@ -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 diff --git a/qemu/hw/arm/novatek.c b/qemu/hw/arm/novatek.c index 6aa4460..2ddbca7 100644 --- a/qemu/hw/arm/novatek.c +++ b/qemu/hw/arm/novatek.c @@ -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; @@ -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) { @@ -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", @@ -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 { @@ -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; @@ -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", diff --git a/qemu/hw/arm/nvt-bootrom.c b/qemu/hw/arm/nvt-bootrom.c index 6f31946..2fe0784 100644 --- a/qemu/hw/arm/nvt-bootrom.c +++ b/qemu/hw/arm/nvt-bootrom.c @@ -35,10 +35,11 @@ * the jump. * * The silicon's ROM also takes the loader from SD, Ethernet (DHCP and - * TFTP), USB or UART. None of those is modelled: "loader-file" is what - * one of them delivered, a host file copied to SRAM in place of the - * flash's first bytes, and the machines' "boot-strap" sets the strap the - * loader reads back. + * TFTP), USB or UART. "loader-file" stands for what one of them + * delivered, a host file copied to SRAM in place of the flash's first + * bytes. With "fallback" (NA51089), the ROM's own fallback runs instead + * when the flash has no valid loader or the straps are not at flash: see + * "the ROM's fallback" below. * * The ROM's own code is not available, so this one is QEMU's: a reset * vector branching to the loader's entry, and for each routine a stub that @@ -71,6 +72,8 @@ #include "migration/vmstate.h" #include "system/address-spaces.h" #include "system/dma.h" +#include "hw/core/cpu.h" +#include "qemu/timer.h" #define ROM_SIZE 0x8000 #define ROM_SVC_SIZE 0x100 @@ -507,11 +510,405 @@ static void rom_build(NvtBootRomState *s, uint32_t entry) } } +/* + * The ROM's fallback, as NA51089's mask ROM does it (read from a dump of + * it, and the NT9856x ROM code application note): with no valid loader in + * flash it tries SD, Ethernet, USB and UART in turn, and again from SD, + * printing a letter or two for each on UART 0. The straps pick where it + * starts: 0xC is UART, 4 Ethernet, 1 SD, 5 and 0xB USB, the rest flash. + * + * flash "PIp>", then "K9" for a loader that fails the check below + * SD "S1K2": no card is modelled + * Ethernet "HE", the PHY's registers, "s100FX", then a DHCPDISCOVER + * ("S1") every 5 s; six unanswered and it gives up ("K:") + * USB "U"; 30 s and it gives up ("K:") + * UART nothing; 30 s and it gives up ("K:"), unless OPEN_DEVICE + * + * The UART stage takes 16-byte commands (the CDB format of the SDK's + * scsi_op.h): byte 0 0xC0, byte 1 the op, bytes 2..5 an address, 6..9 a + * size or a value, 14..15 a checksum that brings the eight halfwords plus + * 0..7 to 0. As in the ROM, every 16 bytes received are one command and + * nothing ever resyncs, and once a WRLOADER has started, everything that + * follows is stored at dest + count -- and still decoded as commands, so a + * payload chunk that looks like one acts as one: + * + * IS_NVT (word 0x000081C0) echo, then the chip word (zeros on a + * bad checksum) + * OPEN_DEVICE (0x000001C0) echo, and the 30 s timeout stops (16 zeros + * on a bad checksum) + * ADDR_READ (0x83C0) echo, the address and the word there + * ADDR_WRITE (0x03C0) the word at the address, bad checksum or not; echo + * WRLOADER (0x02C0) the store's address and size; the echo comes once + * size bytes are in + * WAIT_DONE (word 0x000082C0) echo, then the loader is checked and run + * + * Every echo carries a fresh checksum. The check before the jump is the + * ROM's: the header's word 1 is the loader's address, word 9 its size, + * halfword 0x30 is 0xAA55, and its halfwords plus their indices sum to 0. + * Then "T" and the jump to word 0, or "K9" and the next source. + * + * The CPU waits in WFI in a parked ROM while this runs on timers, and the + * loader's arrival rewrites the ROM at 0 and wakes it. The Ethernet + * stage sends nothing yet: it behaves as a network with no DHCP server. + */ +enum { + NVT_SRC_FLASH, NVT_SRC_SD, NVT_SRC_ETH, NVT_SRC_USB, NVT_SRC_UART, + NVT_SRC_DONE, +}; + +#define UART_RBR 0x00 +#define UART_FCR 0x08 +#define UART_LCR 0x0C +#define UART_LSR 0x14 +#define UART_POLL_NS (200 * SCALE_US) + +#define DHCP_INTERVAL 5000 +#define DHCP_TRIES 6 +#define USB_TIMEOUT 30000 +#define UART_TIMEOUT 30000 + +static void rom_stage(NvtBootRomState *s); +static void rom_fallback_start(void *opaque); + +/* + * "fast" shortens the waits on the way to UART (DHCP, USB), not the UART + * window: that is the host's time to find the 16-byte alignment. + */ +static void rom_arm(NvtBootRomState *s, int64_t ms) +{ + timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + (s->fast && s->source != NVT_SRC_UART ? ms / 10 : ms)); +} + +static bool rom_loader_ok(const uint8_t *img, uint32_t len) +{ + uint32_t size, sum = 0, i; + + if (len < 0x34) { + return false; + } + size = ldl_le_p(img + 0x24); + if (size < 0x34 || size > len || size & 1 || + lduw_le_p(img + 0x30) != 0xAA55) { + return false; + } + for (i = 0; i < size / 2; i++) { + sum += lduw_le_p(img + 2 * i) + i; + } + return !(sum & 0xFFFF); +} + +/* The parked ROM: wfi, b 0 */ +static void rom_park(NvtBootRomState *s) +{ + uint32_t *w = (uint32_t *)s->image; + + memset(s->image, 0, ROM_SIZE); + w[0] = cpu_to_le32(0xE320F003); + w[1] = cpu_to_le32(0xEAFFFFFD); + address_space_write(&s->as, 0, MEMTXATTRS_UNSPECIFIED, s->image, ROM_SIZE); + memory_region_set_dirty(&s->rom, 0, ROM_SIZE); +} + +/* "T": the loader in SRAM is run, as after a flash boot */ +static void rom_run(NvtBootRomState *s, uint32_t entry) +{ + CPUState *cs = first_cpu; + + rom_puts(s, "T"); + s->source = NVT_SRC_DONE; + timer_del(s->timer); + timer_del(s->poll); + rom_build(s, entry); + address_space_write(&s->as, 0, MEMTXATTRS_UNSPECIFIED, s->image, ROM_SIZE); + memory_region_set_dirty(&s->rom, 0, ROM_SIZE); + if (cs) { + cs->halted = 0; + qemu_cpu_kick(cs); + } +} + +/* The loader at addr, checked; run, or "K9" and on to the next source */ +static void rom_try_loader(NvtBootRomState *s, uint32_t addr, uint32_t len) +{ + g_autofree uint8_t *img = g_malloc0(len); + + if (len >= 0x34 && !dma_memory_read(&s->as, addr, img, len, + MEMTXATTRS_UNSPECIFIED) && + rom_loader_ok(img, len) && ldl_le_p(img + 4) == addr) { + rom_run(s, ldl_le_p(img)); + return; + } + rom_puts(s, "K9"); + s->source = s->source == NVT_SRC_UART ? NVT_SRC_SD : s->source + 1; + rom_stage(s); +} + +static void rom_uart_send(NvtBootRomState *s, const uint8_t *buf, int n) +{ + while (n--) { + rom_putc(s, *buf++); + } +} + +static bool cdb_sum_ok(const uint8_t *c) +{ + uint32_t sum = 0; + int i; + + for (i = 0; i < 8; i++) { + sum += lduw_le_p(c + 2 * i) + i; + } + return !(sum & 0xFFFF); +} + +static void cdb_echo(NvtBootRomState *s, const uint8_t *c) +{ + uint8_t e[16]; + uint32_t sum = 7; + int i; + + memcpy(e, c, 16); + for (i = 0; i < 7; i++) { + sum += lduw_le_p(e + 2 * i) + i; + } + stw_le_p(e + 14, -sum); + rom_uart_send(s, e, 16); +} + +static void rom_cdb(NvtBootRomState *s, const uint8_t *c) +{ + static const uint8_t zeros[16]; + uint32_t w0 = ldl_le_p(c), addr = ldl_le_p(c + 2), val = ldl_le_p(c + 6); + uint16_t h0 = lduw_le_p(c); + bool ok = cdb_sum_ok(c); + uint8_t out[8]; + + if (w0 == 0x000081C0) { /* IS_NVT */ + cdb_echo(s, c); + stl_le_p(out, ok ? address_space_ldl_le(&s->as, s->top + 0xF0, + MEMTXATTRS_UNSPECIFIED, + NULL) : 0); + rom_uart_send(s, out, 4); + } else if (w0 == 0x000001C0) { /* OPEN_DEVICE */ + if (ok) { + cdb_echo(s, c); + timer_del(s->timer); + } else { + rom_uart_send(s, zeros, 16); + } + } else if (w0 == 0x000082C0) { /* WAIT_DONE */ + cdb_echo(s, c); + if (s->storing) { + uint32_t size = address_space_ldl_le(&s->as, s->dest + 0x24, + MEMTXATTRS_UNSPECIFIED, NULL); + rom_try_loader(s, s->dest, MIN(size, s->sram_size)); + } else { + rom_try_loader(s, s->sram_base, 0); + } + } else if (h0 == 0x83C0) { /* ADDR_READ */ + cdb_echo(s, c); + stl_le_p(out, addr); + stl_le_p(out + 4, address_space_ldl_le(&s->as, addr, + MEMTXATTRS_UNSPECIFIED, NULL)); + rom_uart_send(s, out, 8); + } else if (h0 == 0x03C0) { /* ADDR_WRITE */ + address_space_stl_le(&s->as, addr, val, MEMTXATTRS_UNSPECIFIED, NULL); + cdb_echo(s, c); + } else if (h0 == 0x02C0) { /* WRLOADER */ + s->storing = true; + s->echo_due = true; + s->dest = addr; + s->size = val; + s->count = 0; + memcpy(s->wr_cdb, c, 16); + } +} + +static void rom_uart_byte(NvtBootRomState *s, uint8_t b) +{ + s->chunk[s->nchunk++] = b; + if (s->nchunk < 16) { + return; + } + s->nchunk = 0; + if (s->storing) { + dma_memory_write(&s->as, s->dest + s->count, s->chunk, 16, + MEMTXATTRS_UNSPECIFIED); + s->count += 16; + } + rom_cdb(s, s->chunk); + if (s->source == NVT_SRC_UART && s->echo_due && s->count >= s->size) { + s->echo_due = false; + cdb_echo(s, s->wr_cdb); + } +} + +/* + * The UART stage takes what comes in. The other stages read nothing, and + * on silicon what arrives then overruns the FIFO and is lost; QEMU's 16550 + * would hold the backend back instead, and hand the UART stage a backlog + * of whatever a host sent meanwhile, so it is drained here. + */ +static void rom_uart_poll(void *opaque) +{ + NvtBootRomState *s = opaque; + int n; + uint8_t b; + + for (n = 0; n < 64 && s->source != NVT_SRC_DONE; n++) { + if (!(address_space_ldub(&s->as, s->uart + UART_LSR, + MEMTXATTRS_UNSPECIFIED, NULL) & 1)) { + break; + } + b = address_space_ldub(&s->as, s->uart + UART_RBR, + MEMTXATTRS_UNSPECIFIED, NULL); + if (s->source == NVT_SRC_UART) { + rom_uart_byte(s, b); + } + } + if (s->source != NVT_SRC_DONE) { + timer_mod(s->poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + UART_POLL_NS); + } +} + +/* A stage's timeout ran out, or the next DHCPDISCOVER is due */ +static void rom_timeout(void *opaque) +{ + NvtBootRomState *s = opaque; + + switch (s->source) { + case -1: /* just out of reset */ + rom_fallback_start(s); + return; + case NVT_SRC_ETH: + if (++s->tries < DHCP_TRIES) { + rom_puts(s, "S1"); + rom_arm(s, DHCP_INTERVAL); + return; + } + break; + case NVT_SRC_USB: + break; + case NVT_SRC_UART: + break; + default: + return; + } + rom_puts(s, "K:"); + s->source = s->source == NVT_SRC_UART ? NVT_SRC_SD : s->source + 1; + rom_stage(s); +} + +static void rom_stage(NvtBootRomState *s) +{ + uint32_t size; + + switch (s->source) { + case NVT_SRC_FLASH: + rom_puts(s, "PIp>"); + nvt_flash_read(s->flash, 0x24, &size, 4); + size = MIN(le32_to_cpu(size), s->sram_size); + s->loader = g_realloc(s->loader, MAX(size, 4)); + nvt_flash_read(s->flash, 0, s->loader, size); + address_space_write(&s->as, s->sram_base, MEMTXATTRS_UNSPECIFIED, + s->loader, size); + rom_try_loader(s, s->sram_base, size); + break; + case NVT_SRC_SD: + rom_puts(s, "S1K2"); + s->source = NVT_SRC_ETH; + rom_stage(s); + break; + case NVT_SRC_ETH: + rom_puts(s, "HE00003500000037000000D1E1s100FXS1"); + s->tries = 0; + rom_arm(s, DHCP_INTERVAL); + break; + case NVT_SRC_USB: + rom_puts(s, "U"); + rom_arm(s, USB_TIMEOUT); + break; + case NVT_SRC_UART: + /* + * 8N1, FIFOs on and reset. The receive trigger at 14 bytes: QEMU's + * 16550 offers its backend only up to the trigger level, so at 1 a + * 32 KiB loader would take half a minute. + */ + address_space_stb(&s->as, s->uart + UART_LCR, 0x03, + MEMTXATTRS_UNSPECIFIED, NULL); + address_space_stb(&s->as, s->uart + UART_FCR, 0xC7, + MEMTXATTRS_UNSPECIFIED, NULL); + s->nchunk = 0; + s->storing = s->echo_due = false; + rom_arm(s, UART_TIMEOUT); + break; + } +} + +/* Where the straps send the ROM first */ +static int rom_first_source(uint8_t strap) +{ + switch (strap & 0xF) { + case 0xC: + return NVT_SRC_UART; + case 4: + return NVT_SRC_ETH; + case 1: + return NVT_SRC_SD; + case 5: case 0xB: + return NVT_SRC_USB; + default: + return NVT_SRC_FLASH; + } +} + +static void rom_fallback_start(void *opaque) +{ + NvtBootRomState *s = opaque; + + s->source = rom_first_source(s->strap); + if (s->source != NVT_SRC_UART) { /* UART boot prints nothing */ + rom_puts(s, "N"); + } + timer_mod(s->poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + UART_POLL_NS); + rom_stage(s); +} + +static bool rom_flash_loader_ok(NvtBootRomState *s) +{ + uint32_t size; + g_autofree uint8_t *img = NULL; + + nvt_flash_read(s->flash, 0x24, &size, 4); + size = le32_to_cpu(size); + if (size < 0x34 || size > s->sram_size) { + return false; + } + img = g_malloc(size); + nvt_flash_read(s->flash, 0, img, size); + return rom_loader_ok(img, size) && ldl_le_p(img + 4) == s->sram_base; +} + static void nvt_bootrom_reset(DeviceState *dev) { NvtBootRomState *s = NVT_BOOTROM(dev); uint32_t hdr[10], size; + timer_del(s->timer); + timer_del(s->poll); + s->source = NVT_SRC_DONE; + if (s->fallback && !s->loader_file && + (rom_first_source(s->strap) != NVT_SRC_FLASH || + !rom_flash_loader_ok(s))) { + rom_park(s); + /* from the main loop, once every device is out of reset */ + timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL)); + s->source = -1; + return; + } + /* the loader into SRAM, as the ROM copies it */ if (s->loader_file) { g_autoptr(GError) err = NULL; @@ -562,6 +959,8 @@ static void nvt_bootrom_realize(DeviceState *dev, Error **errp) ROM_SIZE, s->image); memory_region_set_readonly(&s->rom, true); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->rom); + s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, rom_timeout, s); + s->poll = timer_new_ns(QEMU_CLOCK_VIRTUAL, rom_uart_poll, s); } static void nvt_bootrom_init(Object *obj) @@ -596,6 +995,10 @@ static const Property nvt_bootrom_properties[] = { DEFINE_PROP_UINT64("rom-base", NvtBootRomState, rom_base, 0x80000000), DEFINE_PROP_UINT8("layout", NvtBootRomState, layout, 0), DEFINE_PROP_STRING("loader-file", NvtBootRomState, loader_file), + DEFINE_PROP_BOOL("fallback", NvtBootRomState, fallback, false), + DEFINE_PROP_BOOL("fast", NvtBootRomState, fast, false), + DEFINE_PROP_UINT8("strap", NvtBootRomState, strap, 0), + DEFINE_PROP_UINT64("top", NvtBootRomState, top, 0xF0010000), }; static void nvt_bootrom_class_init(ObjectClass *klass, const void *data) diff --git a/qemu/include/hw/arm/novatek.h b/qemu/include/hw/arm/novatek.h index 70aa815..e34ebd2 100644 --- a/qemu/include/hw/arm/novatek.h +++ b/qemu/include/hw/arm/novatek.h @@ -65,5 +65,11 @@ bool nvt_flash_has_loader(NvtFlashState *flash, uint64_t sram_base, void nvt_rom_boot(NvtFlashState *flash, const NvtRomConfig *rc); /* The ROM's loader from a host file, not the flash (nvt-bootrom.c) */ void nvt_rom_set_loader_file(const char *path); +/* + * The ROM's own fallback (NA51089, nvt-bootrom.c): with no loader in + * flash, or the straps not at flash, it looks on SD, Ethernet, USB and UART. + */ +void nvt_rom_set_fallback(bool on, uint32_t strap, bool fast); +bool nvt_rom_strap_is_flash(uint32_t strap); #endif /* HW_ARM_NOVATEK_H */ diff --git a/qemu/include/hw/arm/nvt-bootrom.h b/qemu/include/hw/arm/nvt-bootrom.h index 341f2e4..2517013 100644 --- a/qemu/include/hw/arm/nvt-bootrom.h +++ b/qemu/include/hw/arm/nvt-bootrom.h @@ -11,6 +11,7 @@ #include "system/memory.h" #include "hw/ssi/nvt-flash.h" #include "qom/object.h" +#include "qemu/timer.h" #define TYPE_NVT_BOOTROM "nvt-bootrom" OBJECT_DECLARE_SIMPLE_TYPE(NvtBootRomState, NVT_BOOTROM) @@ -35,6 +36,27 @@ struct NvtBootRomState { uint8_t layout; /* 0 NA51089's table, 1 NA51055's, 2 NS02501 (none) */ uint32_t args[6]; uint32_t ret; + + /* + * Recovery (nvt-bootrom.c, "the ROM's fallback"): with no loader in + * flash, or straps not at flash, the CPU parks while the ROM looks for + * a loader on its other sources. + */ + bool fallback; /* run it when the flash has no loader */ + bool fast; /* timeouts / 10, for tests */ + uint8_t strap; /* TOP+0 BS[3:0] */ + uint64_t top; /* TOP, for the chip word at +0xF0 */ + QEMUTimer *timer; /* the current stage's timeout */ + QEMUTimer *poll; /* the UART receive poll */ + int source; /* the stage running, NVT_SRC_* */ + int tries; + /* UART download (the ROM's CDB engine) */ + uint8_t chunk[16]; + int nchunk; + bool storing; /* a WRLOADER has started the store */ + bool echo_due; /* the WRLOADER's echo, once size is in */ + uint8_t wr_cdb[16]; + uint32_t dest, size, count; }; #endif /* HW_ARM_NVT_BOOTROM_H */ diff --git a/qemu/tests/test-novatek.py b/qemu/tests/test-novatek.py index 0f0d0e9..87ca8e2 100644 --- a/qemu/tests/test-novatek.py +++ b/qemu/tests/test-novatek.py @@ -3717,6 +3717,140 @@ def rdes3(i): # ldr r0,=0xF0290000; FCR = 7; 1: wait for LSR.DR; RBR -> THR; b 1b +def nvt_cdb(op, addr=0, size=0, bad=False): + """A ROM command: the eight halfwords plus 0..7 sum to 0 (scsi_op.h).""" + c = bytearray(struct.pack('K9S1K2HE00003500000037000000D1E1s100FXS1' + got = rs.recv(timeout=8.0) + check('NOR fails, no SD card, Ethernet starts', + got[:len(start)], start) + check('six DHCP tries, then USB, then UART', + got[len(start):], b'S1' * 5 + b'K:UK:') + rs.send(nvt_cdb(0x81)) + check('the UART stage answers', rs.recv(20)[16:], b'\0\0\x21\x70') + finally: + rs.close() + + UART_ECHO = [0xE59F001C, 0xE3A01007, 0xE5801008, 0xE5901014, 0xE3110001, 0x0AFFFFFC, 0xE5902000, 0xE5802000, 0xEAFFFFF9, 0xF0290000] @@ -4960,6 +5094,8 @@ def run(args, tmp): finally: qt.close() test_boot_rom(args.qemu, tmp) + test_rom_uart(args.qemu, tmp) + test_rom_fallback(args.qemu, tmp) test_loader_models(args.qemu, tmp) for with_oob in (False, True):