Skip to content

agent: upload natively to gk7205v500/v510/v530 - #145

Merged
widgetii merged 2 commits into
masterfrom
agent/v500-upload
Oct 2, 2026
Merged

widgetii merged 2 commits into
masterfrom
agent/v500-upload

Conversation

@widgetii

@widgetii widgetii commented Oct 2, 2026

Copy link
Copy Markdown
Member

Phase 0 of the NAND ECC study for OpenIPC/firmware#2285 / #2519: get the flash agent onto the V500 family, which had no path at all (no SPL stage, no profile JSON by design).

How

  • agent upload -c gk7205v5x0 builds a V500 boot image around the agent. The key area, params and aux (DDR-init) code come from a donor OpenIPC u-boot-xmedia image, auto-downloaded u-boot-<chip>-nor.bin or -f. The agent goes in the boot-code slot, with the length fields patched (wrap_v500_payload).
  • After a UART download the bootrom runs the boot code in place at 0x41007000 (load address + 8 KiB key + 20 KiB aux). It does not use the header's entry field (0x40707000). A 144-byte probe in the slot printed pc=0x41007000 lr=0xad8. The new gk7205v500 agent stanza therefore links there, and the wrap checks the link address against the donor's code offset.
  • nand_identify() now uses a table: MX35LF, W25N01GV, and GD5F1GM7 (3.3 V / 1.8 V). An unknown NAND fell into the NOR path, which hung forever in flash_global_unlock() polling a NOR WIP bit that a NAND never clears. UART breadcrumbs placed through startup → main → flash_init pinned it down.
  • --power-cycle / --poe-port go through the shared helper from power: drive Tasmota plugs and HTTP relays; v500: continuous handshake #144 (proactive ordering).

Verified on hardware

  • GK7205V510 (chip ID 0x72050510, GD5F1GM7 NAND, working vendor firmware), Tasmota-cycled: defib agent upload -c gk7205v510 --power-cycle → READY. agent info reports JEDEC 00c891, 128 MiB, 128 KiB blocks. agent read -a 0x10000000 -s 0x100 dumps the FMC registers (FMC_CFG 0x1821, version 0x100).
  • hi3516ev300 + W25N01GV: previously misreported as 16 MiB NOR, now 128 MiB NAND.
  • The stock gk7205v510 donor RAM-boots this board; the gk7205v500 one does not, so -c must name the actual chip.

Checks: pytest 952 passed, ruff, mypy, make -C agent test, all-socs plus clean gk7205v500 / hi3516ev300 builds, JS tests.

The V500 bootrom has no SPL stage and these chips have no profile, so
`defib agent upload` had no way onto them. It now builds a V500 boot image
around the agent: key area, params and aux (DDR init) area from a donor
OpenIPC u-boot-xmedia image (downloaded and cached, or -f), the agent in
the boot-code slot, and the boot-code/total length fields patched.

The bootrom runs that slot *in place* after a UART download, at
0x41000000 + 0x2000 key area + 0x5000 aux area = 0x41007000. The image's
own entry field (0x40707000, where U-Boot's position-independent
decompressor stub expects to end up) is not used on this path: a 144-byte
probe in the slot printed pc=0x41007000, lr=0xad8 (bootrom). So the
gk7205v500 agent links at 0x41007000, and wrap_v500_payload checks the
link address against the donor's actual code offset.

Getting READY also needed the GD5F1GM7 recognised as a SPI NAND. Only
MX35LF was, so any other NAND fell through to the NOR path, and there
flash_global_unlock() polled a NOR status register the NAND does not
implement: on GD5F1GM7 (ID reads 00 C8 91) "WIP" never clears and the
agent hung before READY. UART breadcrumbs placed through startup.S,
main() and flash_init() pinned it there. nand_identify() now takes a
table: MX35LF1GE4AB, W25N01GV (EF AA, third ID byte lost to the dummy
byte), GD5F1GM7 3.3 V/1.8 V — all 1 Gbit / 2 KiB / 64-page blocks, the
geometry flash_init already assumes.

Verified on hardware:
- GK7205V510 (chip ID 0x72050510) with GD5F1GM7 NAND and working vendor
  firmware, Tasmota-cycled: `defib agent upload -c gk7205v510
  --power-cycle` -> READY; `agent info` JEDEC 00c891, 128 MiB, 128 KiB
  blocks; `agent read -a 0x10000000 -s 0x100` dumps the FMC registers
  (FMC_CFG 0x1821, version 0x100 at +0xBC).
- hi3516ev300 with W25N01GV, previously reported as 16 MiB NOR: now
  128 MiB NAND with 128 KiB blocks; `agent upload --power-cycle` + info.
- The stock u-boot-gk7205v510-nor.bin donor RAM-boots on this board
  (System startup / DRAM 128 MiB / download mode); the gk7205v500 donor
  does not, so -c must name the actual chip.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Enable native flash-agent upload on GK7205V500-family chips

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Upload agents to GK7205V500/V510/V530 without an SPL or SoC profile by using a donor boot image.
• Recognize additional SPI NAND chips so they initialize as NAND instead of hanging in the NOR path.
• Add donor-image and upload tests, plus documentation for V500 builds and boot behavior.
Diagram

sequenceDiagram
    actor User as Operator
    participant CLI as Upload CLI
    participant Donor as Donor cache
    participant Wrap as Image wrapper
    participant Proto as V500 protocol
    participant ROM as Bootrom
    participant Agent as Flash agent
    participant NAND as SPI NAND
    User->>CLI: Upload for chip
    CLI->>Donor: Fetch matching image
    Donor-->>CLI: Donor boot image
    CLI->>Wrap: Insert linked agent
    Wrap-->>CLI: Patched boot image
    CLI->>Proto: Handshake and upload
    Proto->>ROM: Send boot image
    ROM->>Agent: Initialize DDR and run code
    Agent->>NAND: Identify flash
    NAND-->>Agent: NAND identity
    Agent-->>CLI: READY and flash info
    CLI-->>User: Upload result
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Build a standalone V500 boot image
  • ➕ Removes the runtime dependency on a donor U-Boot image.
  • ➕ Could provide tighter control over the image header and DDR initialization.
  • ➖ Requires implementing and maintaining board-compatible DDR initialization and boot-image construction.
  • ➖ Increases hardware validation scope beyond getting the agent onto these chips.

Recommendation: Use the PR's donor-image approach for this initial upload path: it reuses working DDR initialization while replacing only the boot code, and the link-address check catches incompatible layouts. Keep the chip-specific donor selection and -f override because DDR initialization can vary by board.

Files changed (10) +375 / -17

Enhancement (5) +243 / -5
MakefileAdd the GK7205V500-family agent build +17/-1

Add the GK7205V500-family agent build

• Adds a gk7205v500 configuration shared by V500/V510/V530, linking the agent at the bootrom's in-place execution address of 0x41007000. Lists the new build target in the unsupported-SoC error.

agent/Makefile

client.pyMap V500-family chips to the shared agent binary +3/-0

Map V500-family chips to the shared agent binary

• Routes gk7205v500, gk7205v510, and gk7205v530 binary lookups to the gk7205v500 build.

src/defib/agent/client.py

app.pyAdd a profile-free V500 agent upload path +140/-3

Add a profile-free V500 agent upload path

• Dispatches V500 chips to a dedicated flow that wraps the agent in a donor boot image, handshakes and uploads it, then waits for READY and flash information. Supports a supplied donor via '-f' or an automatic download, and uses the shared proactive power-cycle helper.

src/defib/cli/app.py

firmware.pyDownload and cache chip-specific V500 donor images +32/-1

Download and cache chip-specific V500 donor images

• Extracts the existing download logic into a reusable helper. Adds cached downloads of matching NOR U-Boot images from OpenIPC/u-boot-xmedia for use as V500 donors.

src/defib/firmware.py

hisilicon_v500.pyWrap agents in V500 boot images +51/-0

Wrap agents in V500 boot images

• Adds image-layout constants and a wrapper that retains the donor's key area and DDR-init code, replaces its boot code, and patches length fields. Rejects implausible aux lengths and donors whose boot-code offset does not match the agent's link address.

src/defib/protocol/hisilicon_v500.py

Bug fix (1) +25 / -11
spi_flash.cIdentify additional 1-Gbit SPI NAND devices +25/-11

Identify additional 1-Gbit SPI NAND devices

• Replaces the single-device NAND check with a table covering MX35LF1GE4AB, W25N01GV, and both GD5F1GM7 voltage variants. Accepts direct or dummy-byte-shifted IDs so these chips avoid the incorrect, potentially hanging NOR initialization path.

agent/spi_flash.c

Tests (2) +100 / -0
test_firmware.pyTest V500 donor download caching +32/-0

Test V500 donor download caching

• Verifies that a V510 donor is fetched from the expected OpenIPC asset URL and reused from cache on a second request.

tests/test_firmware.py

test_protocol_v500.pyTest V500 image wrapping and transfer +68/-0

Test V500 image wrapping and transfer

• Checks retained donor data, agent padding, patched lengths, and rejection of invalid donors or link addresses. Also verifies that the wrapped image is sent with its patched size in the boot transfer.

tests/test_protocol_v500.py

Documentation (2) +7 / -1
CLAUDE.mdCorrect the documented SoC count +1/-1

Correct the documented SoC count

• Changes the flash-agent overview from eleven to twelve supported SoC build configurations.

CLAUDE.md

README.mdDocument V500 agent addresses and upload requirements +6/-0

Document V500 agent addresses and upload requirements

• Adds the V500 memory-map row and explains that upload uses a donor image's DDR initialization before executing the agent in its boot-code slot.

agent/README.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Manual uploads can wait forever ✓ Resolved
Description
_agent_upload_v500 directly awaits HiSiliconV500.handshake() without a timeout when
--power-cycle is absent. That handshake loops until it receives a bootrom reply, so an unplugged
or mistimed device never reaches the CLI's handshake-failure path.
Code

src/defib/cli/app.py[1505]

+            hs = await protocol.handshake(transport, on_progress)
Evidence
The newly added manual-upload branch awaits the V500 handshake directly. The handshake declares a
timeout constant but its loop has no elapsed-time check; only the power-cycle helper supplies an
external timeout.

src/defib/cli/app.py[1495-1507]
src/defib/protocol/hisilicon_v500.py[36-37]
src/defib/protocol/hisilicon_v500.py[116-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Without `--power-cycle`, V500 upload awaits a handshake that never times out if the bootrom does not reply.
## Fix Focus Areas
- src/defib/cli/app.py[1504-1507]
- src/defib/protocol/hisilicon_v500.py[116-170]
## Recommended Fix
Apply a finite timeout to the manual handshake and route expiry through the existing handshake-failure response. Add a test for a device that never replies.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Failed uploads leave power sessions open ✓ Resolved
Description
_agent_upload_v500 creates the serial transport after resolving the power controller but before
entering its transport cleanup block. If transport creation raises after RouterOS port discovery
opened a connection, neither the power-controller cleanup nor the later transport cleanup runs.
Code

src/defib/cli/app.py[1483]

+    transport = await create_transport(normalize_port_name(port))
Evidence
Port discovery connects and retains a RouterOS session. The new transport creation call is outside
both the power-setup exception handler and the subsequent transport try/finally, so an exception
there bypasses power.close().

src/defib/cli/app.py[1457-1467]
src/defib/cli/app.py[1483-1503]
src/defib/power/routeros.py[235-249]
src/defib/power/routeros.py[273-289]
src/defib/power/routeros.py[350-357]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A serial transport creation error can leave the resolved RouterOS power-controller connection open.
## Fix Focus Areas
- src/defib/cli/app.py[1457-1467]
- src/defib/cli/app.py[1483-1486]
## Recommended Fix
Put transport creation inside a cleanup scope that closes the power controller if creation fails. Preserve transport closure after successful creation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Wrong chip name hangs board with misleading error ✓ Resolved
Description
_agent_upload_v500 picks the donor via download_v500_donor(chip) from the user's -c value and
only checks hs.success, ignoring the hs.chip_id the V500 handshake returns. When -c names a
different V500 member than the board (the PR notes a gk7205v500 donor does not RAM-boot a V510), the
image is uploaded anyway, client.connect times out, and the message steers the user toward -f
rather than the chip mismatch.
Code

src/defib/cli/app.py[R1506-1509]

+        if not hs.success:
+            fail("Handshake failed")
+
+        result = await protocol.send_firmware(transport, wrapped, on_progress)
Evidence
The handshake returns the real chip ID, but the code only checks .success before sending the
image. The donor file name is built from the user's -c value. All three V500 chips map to the same
agent build, so the agent lookup cannot catch a wrong -c either.

src/defib/protocol/hisilicon_v500.py[155-181]
src/defib/cli/app.py[1449-1456]
src/defib/firmware.py[315-320]
src/defib/agent/client.py[166-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The V500 upload builds the boot image from a donor chosen by the user's `-c` value. It never compares the chip ID returned by the handshake with `-c`. A mismatched donor silently fails to boot, and the error then blames the donor's DDR init.
## Fix Focus Areas
- src/defib/cli/app.py[1506-1522]
- src/defib/firmware.py[309-320]
## Recommended Fix
- After a successful handshake, map `hs.chip_id` to its V500 chip name: 0x72050500 → gk7205v500, 0x72050510 → gk7205v510, 0x72050530 → gk7205v530.
- If that name differs from `-c` and no `-f` donor was given, call `fail()` with a message naming the detected chip.
- Alternatively, choose the donor from the detected ID instead of from `-c`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Uppercase chip names fail the donor download ✓ Resolved
Description
download_v500_donor builds the asset name with _strip_variant(chip) and does not lowercase it.
HiSiliconV500.matches and agent_binary_for both lowercase the chip name, so -c GK7205V510
passes those lookups and only fails here, requesting a nonexistent release asset.
Code

src/defib/firmware.py[R315-316]

+    name = f"u-boot-{_strip_variant(chip)}-nor.bin"
+    dest = get_cache_dir() / name
Evidence
Both the protocol match and the agent lookup lowercase the chip name before using it. The donor name
is built from _strip_variant(chip), which only removes the :variant suffix and keeps the
original case.

src/defib/protocol/hisilicon_v500.py[112-114]
src/defib/agent/client.py[188-195]
src/defib/firmware.py[107-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The V500 donor URL and cache file name use the chip string with its original case, so an uppercase chip name requests an asset that does not exist.
## Fix Focus Areas
- src/defib/firmware.py[315-316]
## Recommended Fix
Build the asset name with `_strip_variant(chip).lower()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/defib/cli/app.py Outdated
Comment thread src/defib/cli/app.py Outdated
Comment thread src/defib/cli/app.py
Comment thread src/defib/firmware.py Outdated
Review (Qodo) of the V500 upload path:

- Without --power-cycle the V500 handshake has no deadline of its own,
  so an unplugged board hung the CLI forever. The manual wait is now
  bounded (60 s) and reported as a handshake failure.
- The serial transport was opened outside any cleanup scope, after
  RouterOS port discovery may already hold a session open; a port that
  failed to open leaked it. Opening is now covered and closes the power
  controller on failure.
- The handshake reports the chip ID, but only .success was checked. A
  -c that names a different family member than the board picks a donor
  whose DDR init cannot bring the board up, and the failure then blamed
  the donor. The reply's chip ID is now compared with -c (unless -f
  gives an explicit donor) and a mismatch names the detected chip before
  anything is uploaded. 0x72050510 is from a real GK7205V510; the v500
  and v530 IDs follow the pattern and are not yet seen on hardware —
  an unknown ID skips the check rather than block the upload.
- -c GK7205V510 passed every lookup but built an uppercase donor asset
  name that does not exist; the name is now lowercased.

Re-verified on the GK7205V510: `defib agent upload -c GK7205V510
--power-cycle` -> READY, 128 MiB NAND.
@widgetii
widgetii merged commit 3c30d0f into master Oct 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant