From d1597826641bc5446243b90e7642c995e5c4da1d Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:57:30 +0300 Subject: [PATCH 1/2] install: the split NOR layout for hi3516cv300 and hi3516ev100 Their firmware now ships the split NOR package (OpenIPC/firmware#2645, on OpenIPC/linux#127) and their OpenIPC/u-boot NOR build scans for it (OpenIPC/u-boot#42), so both join NOR_SPLIT_SOCS: one firmware.bin. at 0x50000, the rest of the chip erased. Their U-Boot comes from OpenIPC/u-boot as u-boot--nor.bin (and -nand.bin for cv300), so they move from AVAILABLE_FIRMWARE to PER_FLASH_TYPE_UBOOT. The u-boot-*-universal.bin still on the release is the U-Boot 2010.06 build, which knows no split layout. hi3516ev100 publishes no NAND build; NOR_ONLY_UBOOT makes asking for one find none rather than a URL that 404s. Tests that used hi3516cv300 as their example of a universal image or of the legacy NAND layout use hi3516cv200 now. Verified end to end on a hi3516cv300 board (imx291, W25Q128, 128 MiB), after a full flash backup: defib install -c hi3516cv300 --firmware openipc.hi3516cv300-nor-lite-split.tgz \ --uboot u-boot-hi3516cv300-nor.bin -p /dev/ttyUSB0 --power-cycle \ --poe-port ether3 --nic enp12s0 --host-ip ... --device-ip ... U-Boot at 0 (flash CRC 9507112D), firmware.bin at 0x50000 with 0x50000+0xFB0000 erased (flash CRC BD92A6E5), default env with the factory ethaddr restored; the next boot: "Found Kernel at 0x00050000", "Found RootFS at 0x001f0000", "Verifying Checksum ... OK", "hi_sfc: split NOR layout, device tree partitions", root on 31:4, login prompt. --- CLAUDE.md | 5 +++-- src/defib/firmware.py | 16 ++++++++++++---- tests/test_firmware.py | 21 ++++++++++++++------- tests/test_install_nor_split.py | 3 ++- tests/test_nand_install.py | 6 +++--- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e753848..915f8b9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,12 +168,13 @@ Backends: `spi_flash.c` (fmc100), `spi_flash_hisfc350.c` (V1-era parts), `u-boot--universal.bin`; the SoCs in `PER_FLASH_TYPE_UBOOT` (the u-boot-xmedia ones: hi3516ev200/ev300, hi3518ev300, hi3516dv200, gk7205v500/v510/v530; and the OpenIPC/u-boot gk7202v300, gk7205v200/v300, - gk7605v100) publish `u-boot--nor.bin` and `-nand.bin` instead, so + gk7605v100, hi3516cv300, hi3516ev100 -- ev100 has no NAND build) publish `u-boot--nor.bin` and `-nand.bin` instead, so callers pass `flash_type` (NOR when unknown). On NAND those SoCs install the UBI-only layout (`NAND_UBI_LAYOUT` in `install/layout.py`: 768k boot, 256k env, rest ubi), whose mtdparts/bootcmd/bootargs come from the U-Boot default env; `NAND_LAYOUT` is the legacy split layout for other chips. The - `NOR_SPLIT_SOCS` (gk7205v200 family, hi3516cv6xx, hi3519dv500; keyed by + `NOR_SPLIT_SOCS` (gk7205v200 family, hi3516cv6xx, hi3519dv500, + hi3516cv300/ev100; keyed by package board, `FIRMWARE_PACKAGE_BOARD` maps cv608/cv610/cv613 to `hi3516cv6xx` and hi3516dv500 to `hi3519dv500`) install the split NOR package: one `firmware.bin.` where the boot area ends (0x50000; diff --git a/src/defib/firmware.py b/src/defib/firmware.py index eca0355..c2fe555 100644 --- a/src/defib/firmware.py +++ b/src/defib/firmware.py @@ -34,9 +34,8 @@ # Boot-image SoCs are deliberately absent — see BOOT_IMAGE_VARIANTS below. AVAILABLE_FIRMWARE: set[str] = { "hi3516av100", "hi3516av200", "hi3516av300", - "hi3516cv100", "hi3516cv200", "hi3516cv300", "hi3516cv500", + "hi3516cv100", "hi3516cv200", "hi3516cv500", "hi3516dv100", "hi3516dv300", - "hi3516ev100", "hi3518av100", "hi3518cv100", "hi3518ev100", "hi3518ev200", "hi3519v101", "hi3520dv200", "hi3536cv100", "hi3536dv100", "t40a", "t40n", "t40xp", @@ -44,18 +43,24 @@ # SoCs whose OpenIPC U-Boot is published once per flash type as # u-boot-{chip}-nor.bin / u-boot-{chip}-nand.bin: the u-boot-xmedia SoCs, and -# the gk7205v200 family built from OpenIPC/u-boot (openipc-hisilicon). The +# the gk7205v200 family and hi3516cv300/hi3516ev100 built from OpenIPC/u-boot +# (openipc-hisilicon). The # NAND build owns the UBI-only layout (768k boot, 256k env, rest ubi) in its # default environment, so a NOR image must never be installed on NAND or vice # versa. The old u-boot-{chip}-universal.bin of these SoCs came from retired # per-SoC repositories (u-boot-hi3516ev200, u-boot-gk7205v200); it is never -# downloaded for them again. +# downloaded for them again. hi3516cv300/hi3516ev100 still have a +# u-boot-*-universal.bin (U-Boot 2010.06), which knows no split layout. PER_FLASH_TYPE_UBOOT: frozenset[str] = frozenset({ "hi3516ev200", "hi3516ev300", "hi3518ev300", "hi3516dv200", "gk7205v500", "gk7205v510", "gk7205v530", "gk7202v300", "gk7205v200", "gk7205v300", "gk7605v100", + "hi3516cv300", "hi3516ev100", }) +# Of those, the SoCs with no NAND build published: asking for one finds none. +NOR_ONLY_UBOOT: frozenset[str] = frozenset({"hi3516ev100"}) + # The firmware package a chip installs, where it is not the chip's own: one # hi3516cv6xx build serves the whole CV6xx family, and hi3516dv500 is the # hi3519dv500 die. Packages are openipc.--[-split].tgz. @@ -75,6 +80,7 @@ NOR_SPLIT_SOCS: frozenset[str] = frozenset({ "gk7202v300", "gk7205v200", "gk7205v300", "gk7605v100", "hi3516cv6xx", "hi3519dv500", + "hi3516cv300", "hi3516ev100", }) # NOR boot area -- (boot partition size, env partition size) -- where it is @@ -210,6 +216,8 @@ def asset_name(chip: str, flash_type: str | None = None) -> str | None: name = CHIP_TO_FIRMWARE.get(base.lower(), base.lower()) if name in PER_FLASH_TYPE_UBOOT: + if kind == "nand" and name in NOR_ONLY_UBOOT: + return None # Board variants (e.g. ``:emmc``) do not change the per-flash build. return f"u-boot-{name}-{kind}.bin" diff --git a/tests/test_firmware.py b/tests/test_firmware.py index 3cc2523..14cf305 100644 --- a/tests/test_firmware.py +++ b/tests/test_firmware.py @@ -35,8 +35,8 @@ def test_aliased_chip(self): assert "hi3518ev200" in url def test_url_format(self): - url = firmware_url("hi3516cv300") - assert url == "https://github.com/OpenIPC/firmware/releases/download/latest/u-boot-hi3516cv300-universal.bin" + url = firmware_url("hi3516cv200") + assert url == "https://github.com/OpenIPC/firmware/releases/download/latest/u-boot-hi3516cv200-universal.bin" class TestHasFirmware: @@ -55,10 +55,10 @@ def test_aliased_chip(self): class TestAvailableFirmware: def test_at_least_20_chips(self): - assert len(AVAILABLE_FIRMWARE) >= 20 + assert len(AVAILABLE_FIRMWARE | PER_FLASH_TYPE_UBOOT) >= 20 def test_common_chips_included(self): - for chip in ["hi3516cv300", "hi3518ev200"]: + for chip in ["hi3516cv200", "hi3518ev200"]: assert chip in AVAILABLE_FIRMWARE def test_per_flash_type_socs_are_not_universal(self): @@ -185,10 +185,10 @@ def test_empty_cache_file_is_ignored(self, tmp_path, monkeypatch): class TestClassicChipsUnchanged: def test_universal_naming_preserved(self): - assert asset_name("hi3516cv300") == "u-boot-hi3516cv300-universal.bin" + assert asset_name("hi3516cv200") == "u-boot-hi3516cv200-universal.bin" def test_flash_type_does_not_rename_universal_images(self): - assert asset_name("hi3516cv300", "nand") == "u-boot-hi3516cv300-universal.bin" + assert asset_name("hi3516cv200", "nand") == "u-boot-hi3516cv200-universal.bin" def test_variant_still_ignored_for_classic_socs(self): assert asset_name("hi3516ev300:emmc") == asset_name("hi3516ev300") @@ -272,10 +272,17 @@ class TestPerFlashTypeUBoot: "hi3516ev200", "hi3516ev300", "hi3518ev300", "hi3516dv200", "gk7205v500", "gk7205v510", "gk7205v530", "gk7202v300", "gk7205v200", "gk7205v300", "gk7605v100", + "hi3516cv300", ) + NOR_ONLY = ("hi3516ev100",) def test_set_matches_the_published_socs(self): - assert set(self.SOCS) == PER_FLASH_TYPE_UBOOT + assert set(self.SOCS + self.NOR_ONLY) == PER_FLASH_TYPE_UBOOT + + @pytest.mark.parametrize("soc", NOR_ONLY) + def test_nor_only_has_no_nand_build(self, soc): + assert asset_name(soc) == f"u-boot-{soc}-nor.bin" + assert asset_name(soc, "nand") is None @pytest.mark.parametrize("soc", SOCS) def test_name_per_flash_type(self, soc): diff --git a/tests/test_install_nor_split.py b/tests/test_install_nor_split.py index aeb53e2..d0e0df6 100644 --- a/tests/test_install_nor_split.py +++ b/tests/test_install_nor_split.py @@ -353,8 +353,9 @@ def test_package_board_and_boot_area_are_data(): assert package_board("gk7205v210") == "gk7205v200" assert "hi3516cv6xx" in split_boards("hi3516cv608") for chip in ("hi3516cv608", "hi3516cv610", "hi3516cv613", "hi3519dv500", - "hi3516dv500", "gk7205v200"): + "hi3516dv500", "gk7205v200", "hi3516cv300", "hi3516ev100"): assert supports_nor_split(chip), chip + assert nor_boot_area("hi3516cv300") == (0x40000, 0x10000) assert not supports_nor_split("hi3516ev300") assert nor_boot_area("hi3516cv610") == (0x40000, 0x10000) diff --git a/tests/test_nand_install.py b/tests/test_nand_install.py index 8d97a2c..d68f31d 100644 --- a/tests/test_nand_install.py +++ b/tests/test_nand_install.py @@ -139,8 +139,8 @@ async def test_nand_install_without_crc32_keeps_legacy_compatibility( rootfs = b"R" * 2048 with tarfile.open(firmware_tar, "w:gz") as archive: for name, payload in ( - ("uImage.hi3516cv300", kernel), - ("rootfs.squashfs.hi3516cv300", rootfs), + ("uImage.hi3516cv200", kernel), + ("rootfs.squashfs.hi3516cv200", rootfs), ): info = tarfile.TarInfo(name) info.size = len(payload) @@ -260,7 +260,7 @@ async def fake_send_command( await run_install( InstallRequest( - chip="hi3516cv300", + chip="hi3516cv200", firmware_path=str(firmware_tar), uboot_path=str(uboot), port="COM15", From 1cc496065fe1f141fe7fd5be331fcaa19feabfa5 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:58:06 +0300 Subject: [PATCH 2/2] install, web: never the 2010.06 universal U-Boot for hi3516cv300/ev100 From review: - download_firmware() fell back offline to a cached u-boot-*-universal.bin for per-flash-type SoCs. For hi3516cv300 and hi3516ev100 that is U-Boot 2010.06, which cannot boot the split layout install now writes; NO_UNIVERSAL_FALLBACK makes the download fail instead. - The Web UI's own list of -nor SoCs (XMEDIA_SOCS) gains both, so the browser loads the same OpenIPC/u-boot build as defib. That list also turns on SPL boundary detection, which this image needs: defib burn found its SPL at 0x5400 on the board, past the profile's 0x4F00. --- src/defib/firmware.py | 7 +++++++ tests/test_firmware.py | 18 ++++++++++++++++++ web/index.html | 8 +++++--- web/protocol.js | 9 ++++++--- web/protocol.test.js | 14 ++++++++++++++ 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/defib/firmware.py b/src/defib/firmware.py index c2fe555..6ed066d 100644 --- a/src/defib/firmware.py +++ b/src/defib/firmware.py @@ -61,6 +61,12 @@ # Of those, the SoCs with no NAND build published: asking for one finds none. NOR_ONLY_UBOOT: frozenset[str] = frozenset({"hi3516ev100"}) +# Of those, the SoCs whose old u-boot-*-universal.bin must never stand in for +# the -nor build, even offline: it is U-Boot 2010.06, which knows no split +# layout, and install writes the split firmware and loads that U-Boot's +# default environment. +NO_UNIVERSAL_FALLBACK: frozenset[str] = frozenset({"hi3516cv300", "hi3516ev100"}) + # The firmware package a chip installs, where it is not the chip's own: one # hi3516cv6xx build serves the whole CV6xx family, and hi3516dv500 is the # hi3519dv500 die. Packages are openipc.--[-split].tgz. @@ -423,6 +429,7 @@ def download_firmware( if ( uses_per_flash_type_uboot(chip) and normalize_flash_type(flash_type) == "nor" + and package_board(chip) not in NO_UNIVERSAL_FALLBACK ): legacy = _cached_file(_legacy_cache_name(chip)) if legacy is not None: diff --git a/tests/test_firmware.py b/tests/test_firmware.py index 14cf305..0732140 100644 --- a/tests/test_firmware.py +++ b/tests/test_firmware.py @@ -358,6 +358,24 @@ def offline(url, dest, on_progress=None): with pytest.raises(ConnectionError): download_firmware("hi3516ev300", flash_type="nand") + @pytest.mark.parametrize("soc", ["hi3516cv300", "hi3516ev100"]) + def test_no_universal_fallback_for_split_2010_06_socs(self, soc, tmp_path, monkeypatch): + # Their universal image is U-Boot 2010.06, which cannot boot the + # split layout install writes: offline, fail rather than flash it. + from defib import firmware + + monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path)) + monkeypatch.setattr("sys.platform", "linux") + (get_cache_dir() / f"u-boot-{soc}-universal.bin").write_bytes(b"U" * 4096) + + def offline(url, dest, on_progress=None): + raise ConnectionError("offline") + + monkeypatch.setattr(firmware, "_download", offline) + assert get_cached_path(soc) is None + with pytest.raises(ConnectionError): + download_firmware(soc) + def test_gk7205v5xx_shares_the_v500_donor_cache_entry(self, tmp_path, monkeypatch): # download_v500_donor() caches u-boot-xmedia's NOR build under the # same name the OpenIPC/firmware release uses, so either source diff --git a/web/index.html b/web/index.html index 5e437d3..d5d4275 100644 --- a/web/index.html +++ b/web/index.html @@ -297,9 +297,11 @@

Serial Console

const ddrAddr = parseInt(profile.ADDRESS[0], 16); const splAddr = parseInt(profile.ADDRESS[1], 16); const ubootAddr = parseInt(profile.ADDRESS[2], 16); - // Only the u-boot-xmedia images are cut at their payload: the profiles here - // carry no SRAMLIMIT, and on other chips the profile length is what the - // Web UI has always sent safely. + // Only the -nor images (u-boot-xmedia, and hi3516cv300/ev100's + // OpenIPC/u-boot build, whose SPL runs to 0x5400 past the profile's + // 0x4F00) are cut at their payload: the profiles here carry no SRAMLIMIT, + // and on other chips the profile length is what the Web UI has always + // sent safely. const profileMax = parseInt(profile.FILELEN[1], 16); const splMax = XMEDIA_SOCS.has(fwNameForChip(chip)) ? detectSplSize(firmware, profileMax) : profileMax; diff --git a/web/protocol.js b/web/protocol.js index d0e9fce..57ea93c 100644 --- a/web/protocol.js +++ b/web/protocol.js @@ -191,13 +191,16 @@ const FW_DIRECT_BASE = 'https://github.com/OpenIPC/firmware/releases/download/latest'; // Asset name → chip, e.g. u-boot-gk7205v300-universal.bin → gk7205v300. -// The u-boot-xmedia SoCs publish per flash type instead (-nor.bin, -nand.bin); -// a recovery loads U-Boot into RAM and needs no flash layout, so it takes the -// -nor one, as `defib burn` does without --nand. +// The u-boot-xmedia SoCs, and hi3516cv300/hi3516ev100 from OpenIPC/u-boot, +// publish per flash type instead (-nor.bin, -nand.bin); a recovery loads +// U-Boot into RAM and needs no flash layout, so it takes the -nor one, as +// `defib burn` does without --nand. The -universal image still published for +// cv300/ev100 is U-Boot 2010.06, which cannot boot their split NOR layout. const FW_ASSET_RE = /^u-boot-(.+)-(universal|nor)\.bin$/; const XMEDIA_SOCS = new Set([ 'hi3516ev200', 'hi3516ev300', 'hi3518ev300', 'hi3516dv200', 'gk7205v500', 'gk7205v510', 'gk7205v530', + 'hi3516cv300', 'hi3516ev100', ]); /** The published U-Boot a firmware name resolves to. */ diff --git a/web/protocol.test.js b/web/protocol.test.js index e2506cb..a246592 100644 --- a/web/protocol.test.js +++ b/web/protocol.test.js @@ -626,6 +626,20 @@ describe('fwAssetName', () => { assert.equal(fwAssetName('hi3516ev300'), 'u-boot-hi3516ev300-nor.bin'); assert.equal(fwAssetName('gk7205v510'), 'u-boot-gk7205v510-nor.bin'); assert.equal(fwAssetName('gk7205v300'), 'u-boot-gk7205v300-universal.bin'); + assert.equal(fwAssetName('hi3516cv300'), 'u-boot-hi3516cv300-nor.bin'); + assert.equal(fwAssetName('hi3516ev100'), 'u-boot-hi3516ev100-nor.bin'); + }); + it('takes the -nor build of hi3516cv300/ev100 over their 2010.06 universal', () => { + const idx = parseReleaseAssets({ assets: [ + { name: 'u-boot-hi3516cv300-universal.bin', size: 1, digest: null }, + { name: 'u-boot-hi3516cv300-nor.bin', size: 2, digest: null }, + { name: 'u-boot-hi3516cv300-nand.bin', size: 3, digest: null }, + { name: 'u-boot-hi3516ev100-universal.bin', size: 4, digest: null }, + { name: 'u-boot-hi3516ev100-nor.bin', size: 5, digest: null }, + ] }); + assert.equal(idx.get('hi3516cv300').name, 'u-boot-hi3516cv300-nor.bin'); + assert.equal(idx.get('hi3516ev100').name, 'u-boot-hi3516ev100-nor.bin'); + assert.equal(idx.size, 2); }); it('takes each chip\'s own asset and ignores a stale -universal one', () => { const idx = parseReleaseAssets({ assets: [