Skip to content

power: drive Tasmota plugs and HTTP relays; v500: continuous handshake - #144

Merged
widgetii merged 4 commits into
masterfrom
power/tasmota-http
Oct 2, 2026
Merged

widgetii merged 4 commits into
masterfrom
power/tasmota-http

Conversation

@widgetii

@widgetii widgetii commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Prerequisite for the low-level NAND ECC study of OpenIPC/firmware#2285 and #2519, which needs unattended power cycling on two bench DUTs.

power: drive Tasmota plugs and plain HTTP relays

  • New DEFIB_POWER_TYPE=tasmota (/cm?cmnd= API; off / wait / on, each switch confirmed from the plug's reply) and DEFIB_POWER_TYPE=http (on/off GET URLs, e.g. a local bridge for a cloud-only Sonoff; secrets in the URLs are redacted from logs and errors, user:pass@ is sent as Basic auth).
  • agent upload --power-cycle was ignored on the HiSiliconStandard path. It now powers off, starts the handshake while the camera is dark, powers on, and re-cycles once if the bootrom stays silent for 15 s. Takes --poe-port / comment discovery for RouterOS; pulse-only controllers (Vectis) are sequenced like RecoverySession.

protocol/v500: keep the handshake on the wire continuously

  • The handshake sent one 14-byte frame and then sat idle for up to 100 ms, so the bootrom's short listen window was missed on boards with healthy flash. It now sends back-to-back 8-frame bursts, scans the RX stream for the reply, and drains its TX queue before the HEAD stage.

transport/socket: bytes_waiting() sees unread data

  • It only counted bytes already pulled into _buf, so polling callers (drain_input, the CV6xx and V500 handshakes) were blind on socket://, tcp:// and rack://.

Review (Qodo) — all six findings addressed

  1. Socket handshake blind → fixed in SocketTransport.bytes_waiting() (root cause, also affected CV6xx/drain_input), with a socketpair test that fails on the old code.
  2. RouterOS empty port → --poe-port + comment discovery.
  3. Vectis pulse-only → supports_independent_power capability, pulse ordered by protocol style, RFC 2217 transport attached.
  4. Relay secrets in logs → redacted.
  5. Unverified Tasmota cycle → each step verified. Note: verifying by polling during a Backlog ... Delay is harmful on Tasmota 14.3 — commands sent during the Delay cut it short (~4.5 s → ~2 s off) and the mains-powered camera then never booted, so Backlog was dropped altogether.
  6. TX backlog after the reply → flush_output() before settle + flush_input().

Verification

  • pytest 944 passed, ruff, mypy, make -C agent test, JS tests.
  • hi3516ev300 + Sonoff Micro via HTTP bridge: agent upload --power-cycle → READY.
  • GK7205V510 (GD5F1GM7 NAND, vendor firmware) + Tasmota plug: burn --power-cycle caught the bootrom 3/3 consecutive runs, full 292 KB upload each. Before the V500 fix it never caught it in a 120 s run.

Two new DEFIB_POWER_TYPE backends, so lab benches built from cheap smart
plugs no longer need someone at a second shell to flip the power:

- tasmota: the /cm?cmnd= HTTP API (DEFIB_TASMOTA_HOST, optional _RELAY,
  _USER, _PASSWORD). On/off replies are checked against the requested
  state, and an auth WARNING reply is an error rather than a silent no-op.
  power_cycle runs on the plug as "Backlog Power OFF; Delay N; Power ON",
  so the off window does not depend on network latency and a dropped
  connection mid-cycle still leaves the camera powered; it then polls
  until the relay reports ON again.
- http: any relay with an "on" GET URL and an "off" GET URL
  (DEFIB_HTTP_POWER_ON_URL / _OFF_URL, optional _TIMEOUT) — e.g. a local
  bridge in front of a cloud-only Sonoff switch.

agent upload accepted --power-cycle but ignored it on the
HiSiliconStandard path; only CV6xx used the controller. It now powers off,
starts the handshake while the camera is still dark, then powers on. The
order matters for relays whose power_on returns seconds after the relay
actually closed: the blaster is already on the wire when the bootrom
window opens, and nothing a running OS prints can be mistaken for bootrom
markers. A handshake that does not complete within 15 s gets one fresh
cycle.

Verified on hardware:
- hi3516ev300 behind a cloud-backed Sonoff Micro USB relay, through a
  local HTTP bridge: `DEFIB_POWER_TYPE=http ... defib agent upload -c
  hi3516ev300 --power-cycle` caught the bootrom on the first cycle; DDR
  step, SPL and agent uploaded, agent READY, `agent info` answered.
- GK7205V500 behind a Tasmota 14.3 mains plug: the Backlog cycle reboots
  the board (vendor U-Boot -> Linux boot log captured read-only), and
  `defib burn -c gk7205v500 --power-cycle` completed the full upload once
  the V500 handshake fix in the next commit was in place.
The V500 handshake wrote one 14-byte frame (~1.2 ms at 115200) and then
blocked up to 100 ms waiting for a 14-byte reply that had to start
exactly at the read boundary. The line sat idle ~99% of the time, and the
bootrom only listens for a few tens of ms after reset before it falls
through to flash boot. On a board with healthy NAND that was a lottery,
lost most of the time — it is why catching a GK7205V510 on a known-good
flash "worked once, then failed three times in a row".

Now each iteration writes an 8-frame burst (~10 ms), drains whatever has
arrived without blocking, and searches the accumulated stream for the
BD 00 reply wherever it lands — after boot noise or split across reads.
Once it is found, the replies to the rest of the burst are allowed to
settle and are flushed, so they cannot be read as ACKs in the HEAD stage.

Verified on a GK7205V500 with GD5F1GM7 NAND and working vendor firmware,
power-cycled by a Tasmota plug: `defib burn -c gk7205v500 -f
u-boot-gk7205v500-nand.bin --power-cycle` caught the bootrom on the first
cycle and uploaded HEAD, AUX and the 292 KB boot image (34 s). Before the
change the same command never caught it in a 120 s run.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add HTTP power controllers and improve bootrom handshake capture

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

Grey Divider

AI Description

• Add Tasmota and generic HTTP power controllers for unattended bench power cycling.
• Make standard HiSilicon agent uploads power-cycle before handshaking, with one retry.
• Send continuous V500 handshake bursts to catch short bootrom listen windows.
Diagram

graph TD
  CLI["Recovery CLI"] --> Factory["Power factory"] --> Tasmota["Tasmota controller"] --> DUT["Bench device"]
  Factory --> HTTP["HTTP relay"] --> DUT
  CLI --> Protocol["Bootrom handshakes"] --> Serial["Serial transport"] --> DUT
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use only the generic HTTP controller for both relay types
  • ➕ Fewer backend implementations to maintain.
  • ➕ One configuration model for HTTP-controlled devices.
  • ➖ Cannot provide Tasmota-specific Backlog timing, state verification, or authentication-warning handling without rebuilding those features elsewhere.

Recommendation: Keep separate controllers behind the existing PowerController factory. Generic URLs suit simple relays, while Tasmota needs device-specific commands and response validation to make power cycling dependable.

Files changed (11) +920 / -26

Enhancement (3) +275 / -1
factory.pyRegister Tasmota and HTTP controllers +11/-1

Register Tasmota and HTTP controllers

• Dispatches the two new DEFIB_POWER_TYPE values and updates the factory's configuration guidance and unknown-type error.

src/defib/power/factory.py

http.pyAdd a URL-driven HTTP relay controller +86/-0

Add a URL-driven HTTP relay controller

• Implements on and off GET requests with configurable URLs and timeout. Converts HTTP and connection failures into power-controller errors.

src/defib/power/http.py

tasmota.pyAdd a Tasmota smart-plug controller +178/-0

Add a Tasmota smart-plug controller

• Supports relay selection, optional credentials, checked on/off commands, and plug-timed Backlog cycles that poll until power returns. Rejects authentication warnings and malformed or unexpected replies.

src/defib/power/tasmota.py

Bug fix (2) +149 / -22
app.pyPower-cycle standard agent uploads into the handshake +110/-7

Power-cycle standard agent uploads into the handshake

• Wires the configured controller into non-CV6xx agent uploads, starting the handshake while the device is off and retrying once after a 15-second timeout. Reports power errors, closes the controller, and broadens power-cycle option help.

src/defib/cli/app.py

hisilicon_v500.pyKeep V500 handshake frames flowing +39/-15

Keep V500 handshake frames flowing

• Replaces single-frame sends followed by long waits with repeated eight-frame bursts and scans buffered serial input for replies. Flushes residual responses before firmware transfer.

src/defib/protocol/hisilicon_v500.py

Tests (4) +451 / -0
test_agent_upload_power.pyTest power-to-handshake sequencing and retries +104/-0

Test power-to-handshake sequencing and retries

• Checks that handshaking starts before power-on, timeouts trigger a fresh cycle, failed attempts terminate, and power-on errors cancel the handshake task.

tests/test_agent_upload_power.py

test_power_http.pyTest HTTP relay configuration and requests +101/-0

Test HTTP relay configuration and requests

• Covers factory selection, required URLs, timeout parsing, on/off order, host-timed cycling, and HTTP or connection failures.

tests/test_power_http.py

test_power_tasmota.pyTest Tasmota commands, cycles, and errors +198/-0

Test Tasmota commands, cycles, and errors

• Covers configuration, relay addressing, credentials, response validation, Backlog timing and polling, and HTTP or connection errors.

tests/test_power_tasmota.py

test_protocol_v500.pyTest V500 burst handshakes and RX scanning +48/-0

Test V500 burst handshakes and RX scanning

• Adds scripted serial tests for boot noise, split replies, repeated bursts, and flushing leftover handshake responses.

tests/test_protocol_v500.py

Documentation (2) +45 / -3
CLAUDE.mdList the new power backends +2/-1

List the new power backends

• Adds Tasmota and generic HTTP relays to the repository's power-subsystem overview.

CLAUDE.md

README.mdDocument smart-plug and HTTP relay setup +43/-2

Document smart-plug and HTTP relay setup

• Adds environment configuration and CLI examples for both controllers. Explains plug-timed versus host-timed cycles and single-outlet limitations.

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. V500 handshake hangs on network serial links ✓ Resolved
Description
The V500 handshake gates read() on bytes_waiting(), but SocketTransport.bytes_waiting() counts
only _buf, which normal socket reads do not populate. On tcp:// and socket:// transports,
bootrom replies remain unread in the socket while the handshake keeps sending bursts and can
busy-spin the event loop.
Code

src/defib/protocol/hisilicon_v500.py[R89-91]

+                waiting = await transport.bytes_waiting()
+                if waiting > 0:
+                    buffer += await transport.read(waiting, timeout=0.01)
Evidence
SocketTransport.bytes_waiting() returns the length of its internal buffer rather than unread
socket data, and read() does not fill that buffer. The new guard therefore prevents the operation
that fetches incoming socket bytes on both supported socket URL schemes; the previous handshake
called read(14, timeout=0.1) directly.

src/defib/transport/socket.py[118-119]
src/defib/transport/socket.py[64-88]
src/defib/transport/serial_platform.py[134-157]
src/defib/protocol/hisilicon_v500.py[87-93]
src/defib/transport/socket.py[55-76]
src/defib/transport/socket.py[113-114]
src/defib/transport/serial_platform.py[128-155]

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 handshake gates reads on `bytes_waiting()`, which does not reflect unread socket data for `tcp://` and `socket://` transports. Bootrom replies consequently remain unread while the handshake continues sending bursts.
## Fix Focus Areas
- src/defib/protocol/hisilicon_v500.py[86-93]
- src/defib/transport/socket.py[55-76]
- src/defib/transport/socket.py[118-119]
## Recommended Fix
Replace the `bytes_waiting()` gate with a bounded, timed read inside the existing `TransportTimeout` handler, such as `buffer += await transport.read(256, timeout=0.01)`, so every Transport implementation can receive replies. Add a socket-transport handshake test. Optionally, make `SocketTransport.bytes_waiting()` perform a non-blocking receive into `_buf`.

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


2. Agent upload power cycling fails on PoE switches ✓ Resolved
Description
_power_cycle_into_handshake passes "" to both power operations, while agent upload neither
discovers the RouterOS PoE interface nor accepts a --poe-port option. When `agent upload
--power-cycle uses the default RouterOS controller, _set_poe('')` cannot match an interface and
raises PoE interface not found at the first power-off, before upload proceeds.
Code

src/defib/cli/app.py[R1311-1313]

+        log(f"Powering off via {power.name()}{suffix}...")
+        await power.power_off("")
+        await asyncio.sleep(off_duration)
Evidence
The helper supplies an empty port name to the controller, while RouterOS uses that name to query and
set PoE and raises if no interface matches. The burn path resolves the interface before cycling, but
agent upload does not.

src/defib/power/routeros.py[300-312]
src/defib/power/routeros.py[360-364]
src/defib/cli/app.py[151-180]
src/defib/cli/app.py[1079-1097]
src/defib/cli/app.py[1311-1320]
src/defib/power/routeros.py[298-315]
src/defib/cli/app.py[143-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
Agent upload passes an empty interface name to RouterOS power operations, causing the default backend to fail at the first power-off.
## Fix Focus Areas
- src/defib/cli/app.py[1079-1097]
- src/defib/cli/app.py[1174-1185]
- src/defib/cli/app.py[1203-1217]
- src/defib/cli/app.py[1283-1338]
## Recommended Fix
Expose a `--poe-port` option or discover the RouterOS interface as burn does by calling `find_port_by_comment` with the serial device label. Pass the selected port into `_power_cycle_into_handshake` and use it for both `power_off` and `power_on`, including retries.

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



Remediation recommended

3. Vectis users cannot cycle agent power ✓ Resolved
Description
The new standard agent-upload path calls power_off() and power_on() on every selected
controller, while VectisController implements neither operation and raises PowerControllerError
for both. With DEFIB_POWER_TYPE=vectis, agent upload --power-cycle fails at the first power
operation rather than using Vectis's shared-transport pulse.
Code

src/defib/cli/app.py[R1311-1312]

+        log(f"Powering off via {power.name()}{suffix}...")
+        await power.power_off("")
Evidence
The factory permits Vectis selection, but its off/on methods explicitly reject these calls and its
pulse method requires special handling for a live recovery connection.

src/defib/cli/app.py[1174-1179]
src/defib/cli/app.py[1311-1320]
src/defib/power/vectis.py[87-126]
src/defib/power/factory.py[27-34]

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 standard agent-upload sequence invokes unsupported off/on methods on Vectis.
## Fix Focus Areas
- src/defib/cli/app.py[1192-1227]
- src/defib/cli/app.py[1283-1338]
- src/defib/power/vectis.py[87-126]
## Recommended Fix
Handle pulse-only controllers explicitly, attach the live RFC2217 transport when appropriate, and sequence their `power_cycle()` with the handshake instead of calling off/on.

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


4. Relay credentials appear in logs ✓ Resolved
Description
HttpRelayController._get_sync() logs each configured URL verbatim and includes the same URL in
HTTP and connection-error messages. If an on/off GET URL contains a password or query token, using
the relay exposes that credential through logs or a displayed failure.
Code

src/defib/power/http.py[R75-76]

+    def _get_sync(self, url: str) -> None:
+        logger.info("http relay GET %s", url)
Evidence
The backend accepts arbitrary URLs from the environment and emits those exact strings before
requests and on failures.

src/defib/power/http.py[39-63]
src/defib/power/http.py[75-86]

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

## Issue description
Arbitrary configured relay URLs are emitted verbatim in logs and errors, including embedded credentials.
## Fix Focus Areas
- src/defib/power/http.py[75-86]
## Recommended Fix
Log a redacted endpoint or operation label, and apply the same redaction to error messages while retaining the HTTP status and useful response detail.

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


5. A rejected plug cycle looks successful ✓ Resolved
Description
TasmotaController.power_cycle() discards the Backlog command's reply and checks only whether a
later state query reports ON. If an API-compatible plug rejects or does not support Backlog
while its relay was already on, the method reports a completed cycle without ever turning the camera
off.
Code

src/defib/power/tasmota.py[R101-104]

+        await self._cmnd(f"Backlog {cmd} OFF; Delay {delay}; {cmd} ON")
+        # The backlog runs asynchronously on the plug.  Wait for the relay
+        # to come back so callers can rely on "power is on" afterwards.
+        deadline = time.monotonic() + delay / 10 + self._timeout
Evidence
The Backlog response is ignored, and the first ON state query returns success regardless of whether
the preceding OFF or Delay commands executed.

src/defib/power/tasmota.py[98-113]
src/defib/power/tasmota.py[127-140]
tests/test_power_tasmota.py[46-52]

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 Tasmota cycle treats an already-on relay as proof that an unchecked Backlog command executed.
## Fix Focus Areas
- src/defib/power/tasmota.py[98-113]
## Recommended Fix
Validate the Backlog response for command rejection before polling, and add a test where Backlog is rejected while the relay remains ON.

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


View medium (1)
6. Leftover handshake frames can break the upload ✓ Resolved
Description
Each loop pass writes a 112-byte burst without waiting for the bytes to leave the host, and after a
reply the code only sleeps 0.1 s and flushes input. Frames still queued in the OS or the RFC 2217
link keep reaching the bootrom, which delays the HEAD frame and lets late replies be read as ACK or
NAK bytes.
Code

src/defib/protocol/hisilicon_v500.py[R104-105]

+                await asyncio.sleep(0.1)
+                await transport.flush_input()
Evidence
SerialTransport.write returns once data is handed to the OS, and RFC 2217 writes are unpaced with
a no-op flush_output. The standard protocol paces its flood with a 50 ms read after each burst.

src/defib/transport/serial.py[87-133]
src/defib/transport/rfc2217.py[158-174]
src/defib/protocol/hisilicon_standard.py[136-146]

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

## Issue description
Unpaced handshake bursts pile up in the TX queue. After the reply, the remaining frames keep reaching the bootrom and their replies get mixed into the HEAD stage.
## Fix Focus Areas
- src/defib/protocol/hisilicon_v500.py[86-105]
## Recommended Fix
Pace the writes to about the burst's wire time, for example by awaiting a timed read each iteration. After a reply, call `await transport.flush_output()` before the settle sleep and `flush_input()`.

ⓘ 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/power/http.py Outdated
Comment thread src/defib/power/tasmota.py Outdated
Comment thread src/defib/protocol/hisilicon_v500.py
Comment thread src/defib/cli/app.py Outdated
Comment thread src/defib/protocol/hisilicon_v500.py
SocketTransport.read() goes straight to the socket, so _buf only ever
held bytes pushed back by unread(), and bytes_waiting() reported 0 while
data sat in the kernel. Anything that polls before reading was blind on
socket://, tcp:// and rack:// links — Transport.drain_input(), the CV6xx
handshake, and the V500 handshake from the previous commit, which would
blast forever without ever seeing the bootrom's reply. bytes_waiting()
now pulls whatever the non-blocking socket already has into _buf.

The V500 handshake also drains its TX queue once the reply arrives,
before the settle delay and input flush: bursts still queued on the host
would otherwise keep reaching the bootrom, and their replies could be
read as ACK/NAK bytes in the HEAD stage. (A timed read every pass instead
of the bytes_waiting() gate was tried; it reprograms the serial timeout
with a tcsetattr per pass and was dropped.)

Both found in review (Qodo). A socketpair-backed test fails on the old
bytes_waiting() and passes now. Re-verified on the GK7205V510: three
consecutive `defib burn --power-cycle` catches, 3/3.
Review (Qodo) of the new power backends:

- tasmota: power_cycle sent "Backlog Power OFF; Delay N; Power ON" and
  never checked it happened; Tasmota answers any Backlog with {}. Polling
  to verify made it worse: on Tasmota 14.3 a command sent during the
  Delay cuts the delay short (relay back ON at ~2 s instead of ~4.5 s),
  and the mains-powered GK7205V510 then never booted at all — measured by
  timing poll replies against the board's first UART byte. The plug now
  uses the base off / wait / on, each step confirmed by its reply.
- agent upload --power-cycle passed "" as the port, which RouterOS
  cannot resolve. It now takes --poe-port and falls back to comment
  discovery from the serial device name, like burn.
- Vectis can only pulse, and rejects power_off/power_on. Controllers now
  declare supports_independent_power; for pulse-only ones the handshake
  starts before the pulse on proactive protocols and after it on the
  reactive HiSilicon-standard one (whose 0x20 markers a still-running
  OS could fake), matching RecoverySession. A live RFC 2217 transport is
  attached so Vectis pulses over the UART connection defib holds.
- http: configured URLs were logged and echoed in errors verbatim. Logs
  and errors now show scheme://host/path with user info and query
  redacted (also inside echoed response bodies), and user:pass@ in a URL
  is sent as Basic auth — urllib would otherwise try to resolve a host
  literally named "user:pass@host".

Re-verified on hardware: GK7205V510 behind the Tasmota plug, `defib burn
--power-cycle` 3/3; hi3516ev300 behind the HTTP-bridged Sonoff, `agent
upload --power-cycle` to READY.
@widgetii
widgetii merged commit c2301dd into master Oct 2, 2026
13 checks passed
widgetii added a commit that referenced this pull request Oct 2, 2026
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 #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.
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