diff --git a/CLAUDE.md b/CLAUDE.md index 3e4563e..fc076d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,7 +128,7 @@ through agent RAM buffers, see `agent/nand_layout.h` and `defib.agent.nand`). `a optional features through a capability bitmask (`client.py`). Backends: `spi_flash.c` (fmc100), `spi_flash_hisfc350.c` (V1-era parts), -`emmc_himci.c`. Twelve SoCs are supported; each has its own `ifeq` stanza in +`emmc_himci.c`. Thirteen SoCs are supported; each has its own `ifeq` stanza in `agent/Makefile` carrying `LOAD_ADDR` (and `SPI_DRIVER` where it differs). `link.ld` itself is generic — it just places `. = LOAD_ADDR`. diff --git a/agent/Makefile b/agent/Makefile index 5e9a915..d19de55 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -203,8 +203,26 @@ else ifeq ($(SOC),hi3520dv200) CRG_BASE = 0x20030000 SYSCTRL_REBOOT = 0x20050004 SPI_DRIVER = hisfc350 +else ifeq ($(SOC),hi3536dv100) + # DVR/NVR generation, Cortex-A7, V4-style map per vendor + # arch-hi3536dv100/platform.h. Sold by Xiongmai as "Hi3536E" + # (NBD8010S-KL-V2 reads SCSYSID 0x3536D100). UART is PL011 on a fixed + # 24 MHz clock (CONFIG_PL011_CLOCK), so SET_BAUD needs no CRG work. + # FMC clock gate is CRG48 (+0xC0); the vendor driver never touches + # the SPI pads, and there is no pad block at 0x100C0000, so skip them. + UART_BASE = 0x12080000 + UART_CLOCK = 24000000 + LOAD_ADDR = 0x81000000 + FLASH_MEM = 0x14000000 + FMC_BASE = 0x10000000 + RAM_BASE = 0x80000000 + WDT_BASE = 0x12070000 + CRG_BASE = 0x12040000 + SYSCTRL_REBOOT = 0x12050004 + FMC_CRG_OFFSET = 0x00C0 + SPI_PINS_SKIP = 1 else - $(error Unknown SOC: $(SOC). Supported: hi3516ev300 hi3516ev200 gk7205v200 gk7205v300 gk7605v100 gk7205v500 hi3516cv300 hi3516cv500 hi3518ev200 hi3516cv610 hi3519v101 hi3520dv200) + $(error Unknown SOC: $(SOC). Supported: hi3516ev300 hi3516ev200 gk7205v200 gk7205v300 gk7605v100 gk7205v500 hi3516cv300 hi3516cv500 hi3518ev200 hi3516cv610 hi3519v101 hi3520dv200 hi3536dv100) endif # Per-SoC CPU. V3 chips (cv300) are ARM926EJ-S (ARMv5TEJ); @@ -230,6 +248,7 @@ CFLAGS = -mcpu=$(CPU_TYPE) -marm -O2 -ffreestanding -nostdlib \ $(if $(FMC_CRG_CLK_BIT),-DFMC_CRG_CLK_BIT=$(FMC_CRG_CLK_BIT)) \ $(if $(SPI_PIN_BASE),-DSPI_PIN_BASE=$(SPI_PIN_BASE)) \ $(if $(SPI_PINS_CV6XX),-DSPI_PINS_CV6XX=1) \ + $(if $(SPI_PINS_SKIP),-DSPI_PINS_SKIP=1) \ -mno-unaligned-access -Wall -Wextra LDFLAGS = -nostdlib -T link.ld -Ttext=$(LOAD_ADDR) --defsym=LOAD_ADDR=$(LOAD_ADDR) diff --git a/agent/README.md b/agent/README.md index ab2453c..c5b1b22 100644 --- a/agent/README.md +++ b/agent/README.md @@ -82,12 +82,19 @@ Requires `arm-none-eabi-gcc` (Arch: `pacman -S arm-none-eabi-gcc arm-none-eabi-n | hi3516cv610 | 0x11040000 | 0x14000000 | 0x40000000 | 0x41000000 | | hi3519v101 | 0x12100000 | 0x14000000 | 0x80000000 | 0x81000000 | | hi3520dv200 | 0x20080000 | 0x58000000 | 0x80000000 | 0x81000000 | +| hi3536dv100 | 0x12080000 | 0x14000000 | 0x80000000 | 0x81000000 | gk7205v500 also serves gk7205v510/v530. The V500 bootrom has no SPL stage: `defib agent upload` puts the agent in the boot-code slot of an OpenIPC u-boot-xmedia image (downloaded, or `-f` for your own), whose DDR-init code runs first; the bootrom then executes the slot in place at `0x41007000`. +hi3536dv100 is the DVR/NVR part Xiongmai sells as "Hi3536E". Its UART runs +off a fixed 24 MHz clock, so `SET_BAUD` reaches 921600 with no clock +switching. The OpenIPC SPL for it doesn't bring up DDR on the Xiongmai +NBD8010S-KL-V2 board; use `-c hi3536dv100:nbd8010s` there, which uploads the +board's own SPL instead. + Addresses from [qemu-hisilicon](https://github.com/OpenIPC/LoTool) hardware definitions. ### Where defib looks for a built binary diff --git a/agent/spi_flash.c b/agent/spi_flash.c index 773b027..7a5ad0d 100644 --- a/agent/spi_flash.c +++ b/agent/spi_flash.c @@ -168,7 +168,10 @@ static void fmc_enter_normal(void) { * 0x10260000 and uses different register offsets + values; the * dimerr/u-boot-hi3516cv610 driver fmc_hi3516cv610.c is the source * of truth for the CV6xx values. */ -#ifdef SPI_PINS_CV6XX +#if defined(SPI_PINS_SKIP) + /* The bootrom's pad setup is final on this SoC (hi3536dv100): + * vendor U-Boot leaves the SPI pads alone too. */ +#elif defined(SPI_PINS_CV6XX) /* CV6xx layout (per drivers/mtd/fmc_hi3516cv610.c, 3.3V single-CS): * 0x0C MOSI_IO0, 0x10 CLK, 0x14 HOLD_IO3, 0x18 CS0, * 0x1C MISO_IO1, 0x20 WP_IO2. */ diff --git a/src/defib/agent/client.py b/src/defib/agent/client.py index c084be8..1f945cb 100644 --- a/src/defib/agent/client.py +++ b/src/defib/agent/client.py @@ -163,6 +163,7 @@ def bad_block(self) -> list[SectorResult]: "hi3516cv608": "hi3516cv610", # cv6xx-family, same memory map "hi3518ev200": "hi3518ev200", "hi3520dv200": "hi3520dv200", # V1-era, HISFC350 SPI controller + "hi3536dv100": "hi3536dv100", "gk7205v500": "gk7205v500", "gk7205v510": "gk7205v500", # V500 family, same memory map "gk7205v530": "gk7205v500", diff --git a/src/defib/profiles/data/hi3536dv100-nbd8010s-spl.bin b/src/defib/profiles/data/hi3536dv100-nbd8010s-spl.bin new file mode 100644 index 0000000..e3bb63e Binary files /dev/null and b/src/defib/profiles/data/hi3536dv100-nbd8010s-spl.bin differ diff --git a/src/defib/profiles/data/hi3536dv100.json b/src/defib/profiles/data/hi3536dv100.json index 85caadd..94c3119 100644 --- a/src/defib/profiles/data/hi3536dv100.json +++ b/src/defib/profiles/data/hi3536dv100.json @@ -1 +1 @@ -{"name": "hi3536dv100", "PRESTEP0": [4, 224, 45, 229, 36, 0, 159, 229, 36, 16, 159, 229, 0, 16, 128, 229, 32, 0, 159, 229, 32, 16, 159, 229, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222, 60, 1, 5, 18, 78, 87, 79, 68, 64, 1, 5, 18, 117, 106, 105, 122], "DDRSTEP0": [4, 224, 45, 229, 36, 0, 159, 229, 36, 16, 159, 229, 0, 16, 128, 229, 32, 0, 159, 229, 32, 16, 159, 229, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222, 60, 1, 5, 18, 120, 86, 52, 18, 64, 1, 5, 18, 117, 106, 105, 122], "ADDRESS": ["0x04012000", "0x04010500", "0x81000000"], "FILELEN": ["0x0040", "0x5000"], "STEPLEN": ["0x0040", "0x0050"]} \ No newline at end of file +{"name": "hi3536dv100", "PRESTEP0": [4, 224, 45, 229, 36, 0, 159, 229, 36, 16, 159, 229, 0, 16, 128, 229, 32, 0, 159, 229, 32, 16, 159, 229, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222, 60, 1, 5, 18, 78, 87, 79, 68, 64, 1, 5, 18, 117, 106, 105, 122], "DDRSTEP0": [4, 224, 45, 229, 36, 0, 159, 229, 36, 16, 159, 229, 0, 16, 128, 229, 32, 0, 159, 229, 32, 16, 159, 229, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222, 60, 1, 5, 18, 120, 86, 52, 18, 64, 1, 5, 18, 117, 106, 105, 122], "ADDRESS": ["0x04012000", "0x04010500", "0x81000000"], "FILELEN": ["0x0040", "0x5000"], "STEPLEN": ["0x0040", "0x0050"], "variants": {"nbd8010s": {"SPL_BLOB": "hi3536dv100-nbd8010s-spl.bin", "FILELEN": ["0x0040", "0x5840"]}}} \ No newline at end of file diff --git a/src/defib/protocol/hisilicon_standard.py b/src/defib/protocol/hisilicon_standard.py index 31955cb..c1817c6 100644 --- a/src/defib/protocol/hisilicon_standard.py +++ b/src/defib/protocol/hisilicon_standard.py @@ -512,6 +512,12 @@ async def _send_spl( spl_size = self._detect_spl_size( scan_buf, profile.spl_max_size, sram_limit=profile.spl_sram_limit, ) + # A board SPL_BLOB has no compressed payload to mark its end, so the + # scan falls back to profile_max — on hi3516av300:emmc that is 0x6000 + # against a 0x5000 blob. Padding it out writes zeros past the SPL into + # SRAM the bootrom still uses; send only the bytes the blob holds. + if spl_override is not None and len(spl_override) < spl_size: + spl_size = len(spl_override) logger.debug( "=== SPL === address=0x%08X size=%d chunks=%d", profile.spl_address, spl_size, diff --git a/src/defib/recovery/session.py b/src/defib/recovery/session.py index 6d04aab..1a8dbd0 100644 --- a/src/defib/recovery/session.py +++ b/src/defib/recovery/session.py @@ -87,10 +87,15 @@ async def run( protocol = self._protocol_cls() - # For standard protocol, load and attach the SoC profile + # For standard protocol, load and attach the SoC profile. A board + # variant's SPL_BLOB replaces the SPL carved from the U-Boot image: + # that SPL carries one board's DDR table, and on a board with other + # DDR the bootrom jump into U-Boot lands in dead memory. + spl_override: bytes | None = None if isinstance(protocol, HiSiliconStandard): profile = load_profile(self.chip) protocol.set_profile(profile) + spl_override = profile.spl_data if self._power: protocol.set_continuous_ack(True) if on_log: @@ -247,9 +252,14 @@ async def run( level="info", message=f"Sending {len(firmware)} bytes of firmware...", )) - send_result = await protocol.send_firmware( - transport, firmware, on_progress, - ) + if spl_override is not None and isinstance(protocol, HiSiliconStandard): + send_result = await protocol.send_firmware( + transport, firmware, on_progress, spl_override=spl_override, + ) + else: + send_result = await protocol.send_firmware( + transport, firmware, on_progress, + ) if send_result.success: # Mutate handshake variable so post-loop code sees success. handshake_succeeded_result = send_result diff --git a/tests/test_agent_binary_lookup.py b/tests/test_agent_binary_lookup.py index 55ad1da..52cd2e2 100644 --- a/tests/test_agent_binary_lookup.py +++ b/tests/test_agent_binary_lookup.py @@ -43,6 +43,9 @@ def test_a_variant_suffix_still_resolves(self, monkeypatch, tmp_path): (tmp_path / "agent-hi3516cv500.bin").write_bytes(b"\x00" * 16) assert get_agent_binary("hi3516av300:emmc") is not None + def test_a_board_variant_uses_the_chip_build(self): + assert agent_binary_for("hi3536dv100:nbd8010s") == "hi3536dv100" + def test_every_path_is_absolute(self): assert all(Path(p).is_absolute() for p in _agent_search_path("hi3516cv300")) diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 77cddc9..2e8f330 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -220,6 +220,19 @@ def test_real_av300_emmc_variant_loads_spl_blob(self): # SPL extracted at 0x5000 (above the gzip boundary at ~0x5220) assert len(p.spl_data) == 0x5000 + def test_real_hi3536dv100_nbd8010s_variant_loads_spl_blob(self): + """The Xiongmai NBD8010S-KL-V2 NVR's DDR differs from the board the + OpenIPC hi3536dv100 SPL was built for, so its variant ships the SPL + from the board's own U-Boot. The blob runs right up to the LZMA + payload at 0x5840, and FILELEN grows to match so none of it is cut.""" + p = load_profile("hi3536dv100:nbd8010s", PROFILES_DIR) + assert p.spl_blob == "hi3536dv100-nbd8010s-spl.bin" + assert p.spl_data is not None + assert p.spl_data[:4] == bytes.fromhex("170800ea") + assert len(p.spl_data) == 0x5840 + assert p.spl_max_size == 0x5840 + assert load_profile("hi3536dv100", PROFILES_DIR).spl_data is None + class TestSplBlob: """SPL_BLOB lets a profile (typically a board variant) ship pre-built diff --git a/tests/test_recovery_session.py b/tests/test_recovery_session.py index adcaaba..fc4387c 100644 --- a/tests/test_recovery_session.py +++ b/tests/test_recovery_session.py @@ -54,3 +54,37 @@ async def test_full_standard_session(self): assert result.elapsed_ms > 0 assert len(log_events) > 0 assert len(progress_events) > 0 + + @pytest.mark.asyncio + @pytest.mark.parametrize("chip, spl_len", [ + ("hi3536dv100:nbd8010s", 0x5840), + # 0x5000 blob under an inherited FILELEN of 0x6000: must not be + # padded out to the profile maximum. + ("hi3516av300:emmc", 0x5000), + ]) + async def test_variant_spl_blob_replaces_the_firmware_spl(self, chip, spl_len): + """`burn -c :` must send the board's SPL_BLOB, exactly + as long as the blob, as the SPL stage.""" + import struct + + from defib.profiles.loader import load_profile + + profile = load_profile(chip, PROFILES_DIR) + blob = profile.spl_data + assert blob is not None and len(blob) == spl_len + + transport = MockTransport(flush_clears_buffer=False) + transport.enqueue_rx(b"\x20" * 5) + transport.enqueue_rx(ACK_BYTE * 1000) + firmware = b"\x5a" * 0x8000 + + session = RecoverySession(chip=chip, firmware_data=firmware) + result = await session.run(transport) + + assert result.success + sent = b"".join(transport.tx_log) + # SPL DATA frames carry the blob in 1024-byte chunks after a 3-byte + # frame header; the first chunk is enough to tell whose SPL went out. + assert blob[:1024] in sent + head = b"\xfe\x00\xff\x01" + struct.pack(">II", spl_len, profile.spl_address) + assert head in sent