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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
21 changes: 20 additions & 1 deletion agent/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion agent/spi_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
1 change: 1 addition & 0 deletions src/defib/agent/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion src/defib/profiles/data/hi3536dv100.json
Original file line number Diff line number Diff line change
@@ -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"]}
{"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"]}}}
6 changes: 6 additions & 0 deletions src/defib/protocol/hisilicon_standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 14 additions & 4 deletions src/defib/recovery/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
)
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
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
Expand Down
3 changes: 3 additions & 0 deletions tests/test_agent_binary_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

Expand Down
13 changes: 13 additions & 0 deletions tests/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions tests/test_recovery_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <chip>:<variant>` 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
Loading