Skip to content

Commit 49226c4

Browse files
widgetiiclaude
andauthored
Add CMD_FLASH_PROGRAM + sysctrl reboot: complete flash write pipeline (#34)
CMD_FLASH_PROGRAM (0x0A): erase + program flash from RAM in one operation. Host uploads firmware to RAM via write_memory (fast, reliable at 921600), then sends one command. Agent erases sectors, programs pages, sends per-sector/page progress. Host verifies after. Sysctrl reboot: write 0xdeadbeef to 0x12020004 (from Linux hisi-reboot driver). Clean reset that boots from flash — confirmed OpenIPC U-Boot + Linux kernel boot on hi3516ev300. End-to-end verified: - RAM upload: 16MB in 9min at 921600 - Flash program: 256 sectors + 65536 pages in 76s - Reboot: U-Boot → Linux 4.9.37-hi3516ev300 Co-authored-by: Dmitry Ilyin <widgetii@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e37d2e6 commit 49226c4

File tree

4 files changed

+163
-37
lines changed

4 files changed

+163
-37
lines changed

agent/main.c

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,102 @@ static void handle_flash_write(const uint8_t *data, uint32_t len) {
380380
proto_send_ack(ACK_OK);
381381
}
382382

383+
/*
384+
* CMD_FLASH_PROGRAM: erase + program flash from RAM.
385+
*
386+
* The U-Boot approach: host writes data to RAM first (via CMD_WRITE),
387+
* then sends this command to erase + program flash from that RAM buffer.
388+
* Agent does the entire flash operation locally, sending per-sector
389+
* progress so the host knows it's alive.
390+
*
391+
* Host sends: CMD_FLASH_PROGRAM [ram_addr:4LE] [flash_addr:4LE]
392+
* [size:4LE] [expected_crc:4LE]
393+
* Agent: verifies RAM CRC → erases sectors → programs pages
394+
* Progress: RSP_DATA [sectors_done:2LE] [total_sectors:2LE] per sector
395+
* Final: ACK_OK or ACK_CRC_ERROR/ACK_FLASH_ERROR
396+
*/
397+
static void handle_flash_program(const uint8_t *data, uint32_t len) {
398+
if (len < 16) { proto_send_ack(ACK_CRC_ERROR); return; }
399+
if (!flash_readable) { proto_send_ack(ACK_FLASH_ERROR); return; }
400+
401+
uint32_t ram_addr = read_le32(&data[0]);
402+
uint32_t flash_addr = read_le32(&data[4]);
403+
uint32_t size = read_le32(&data[8]);
404+
uint32_t expected_crc = read_le32(&data[12]);
405+
406+
/* Validate */
407+
if (size == 0 || flash_addr + size > flash_info.size) {
408+
proto_send_ack(ACK_FLASH_ERROR);
409+
return;
410+
}
411+
if (!addr_readable(ram_addr, size)) {
412+
proto_send_ack(ACK_FLASH_ERROR);
413+
return;
414+
}
415+
416+
const uint8_t *src = (const uint8_t *)ram_addr;
417+
418+
/* Verify RAM data CRC before touching flash */
419+
uint32_t actual_crc = crc32(0, src, size);
420+
if (actual_crc != expected_crc) {
421+
uint8_t err[9];
422+
err[0] = ACK_CRC_ERROR;
423+
write_le32(&err[1], actual_crc);
424+
write_le32(&err[5], size);
425+
proto_send(RSP_ACK, err, 9);
426+
return;
427+
}
428+
429+
proto_send_ack(ACK_OK); /* CRC verified, starting flash operation */
430+
431+
uint32_t sector_sz = flash_info.sector_size;
432+
uint32_t page_sz = flash_info.page_size;
433+
434+
/* Round up to sector boundary for erase */
435+
uint32_t erase_start = flash_addr & ~(sector_sz - 1);
436+
uint32_t erase_end = (flash_addr + size + sector_sz - 1) & ~(sector_sz - 1);
437+
uint32_t num_sectors = (erase_end - erase_start) / sector_sz;
438+
uint32_t total_sectors = num_sectors;
439+
440+
/* Phase 1: Erase sectors */
441+
for (uint32_t s = 0; s < num_sectors; s++) {
442+
flash_erase_sector(erase_start + s * sector_sz);
443+
444+
/* Progress: [sectors_done:2LE] [total:2LE] */
445+
uint8_t progress[4];
446+
progress[0] = ((s + 1) >> 0) & 0xFF;
447+
progress[1] = ((s + 1) >> 8) & 0xFF;
448+
progress[2] = (total_sectors >> 0) & 0xFF;
449+
progress[3] = (total_sectors >> 8) & 0xFF;
450+
proto_send(RSP_DATA, progress, 4);
451+
}
452+
453+
/* Phase 2: Program pages */
454+
uint32_t offset = 0;
455+
while (offset < size) {
456+
uint32_t chunk = size - offset;
457+
if (chunk > page_sz) chunk = page_sz;
458+
flash_write_page(flash_addr + offset, &src[offset], chunk);
459+
offset += chunk;
460+
461+
/* Progress every 64 pages (16KB) to keep host alive */
462+
if ((offset % (page_sz * 64)) == 0 || offset >= size) {
463+
uint8_t progress[4];
464+
uint16_t done = (uint16_t)(total_sectors + offset / (page_sz * 64));
465+
uint16_t total = (uint16_t)(total_sectors + size / (page_sz * 64));
466+
progress[0] = (done >> 0) & 0xFF;
467+
progress[1] = (done >> 8) & 0xFF;
468+
progress[2] = (total >> 0) & 0xFF;
469+
progress[3] = (total >> 8) & 0xFF;
470+
proto_send(RSP_DATA, progress, 4);
471+
}
472+
}
473+
474+
/* Skip in-agent verify — the FMC memory window may have stale data
475+
* after bulk programming (65536 soft resets). Host verifies separately. */
476+
proto_send_ack(ACK_OK);
477+
}
478+
383479
/*
384480
* ARM32 position-independent trampoline (machine code).
385481
* Copies r2 bytes from r1 to r0, then branches to r0-r2 (original dst).
@@ -694,14 +790,20 @@ int main(void) {
694790
case CMD_SCAN:
695791
handle_scan(cmd_buf, data_len);
696792
break;
793+
case CMD_FLASH_PROGRAM:
794+
handle_flash_program(cmd_buf, data_len);
795+
break;
697796
case CMD_SET_BAUD:
698797
handle_set_baud(cmd_buf, data_len);
699798
break;
700799
case CMD_REBOOT:
701-
/* Rejected — watchdog reset re-enters bootrom on serial
702-
* boot pin, killing the agent with no recovery. Use
703-
* SELFUPDATE to reload, or physical power cycle. */
704-
proto_send_ack(ACK_FLASH_ERROR);
800+
/* ACK first, then system reset via sysctrl register.
801+
* This is the standard HiSilicon reset (same as Linux
802+
* hisi-reboot driver): write 0xdeadbeef to 0x12020004. */
803+
proto_send_ack(ACK_OK);
804+
for (volatile int i = 0; i < 100000; i++) {}
805+
*(volatile uint32_t *)0x12020004 = 0xdeadbeef;
806+
while (1) {}
705807
break;
706808
default:
707809
proto_send_ack(ACK_CRC_ERROR);

agent/protocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define CMD_SELFUPDATE 0x07
1818
#define CMD_SET_BAUD 0x08
1919
#define CMD_SCAN 0x09
20+
#define CMD_FLASH_PROGRAM 0x0A
2021

2122
/* Responses (device → host) */
2223
#define RSP_INFO 0x81

src/defib/agent/client.py

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ACK_OK,
1919
CMD_CRC32,
2020
CMD_ERASE,
21+
CMD_FLASH_PROGRAM,
2122
CMD_INFO,
2223
CMD_READ,
2324
CMD_SCAN,
@@ -381,41 +382,59 @@ async def write_flash(
381382
data: bytes,
382383
on_progress: Callable[[int, int], None] | None = None,
383384
) -> bool:
384-
"""Write data to flash (assumes sectors already erased).
385+
"""Write data to flash: upload to RAM, then erase + program.
385386
386-
Uses _write_block with per-packet backpressure for reliable
387-
transfer. Agent receives to RAM staging, verifies CRC, programs
388-
flash page-by-page, verifies readback.
387+
Two-phase approach (like U-Boot sf write):
388+
1. Upload data to RAM via write_memory (fast, reliable)
389+
2. CMD_FLASH_PROGRAM: agent erases + programs from RAM locally
389390
"""
390391
total = len(data)
391-
offset = 0
392-
max_retries = 3
392+
ram_addr = self._ram_base + 0x200000 # Staging area in DDR
393+
394+
# Phase 1: Upload to RAM
395+
logger.info("Flash write: uploading %d bytes to RAM", total)
396+
ok = await self.write_memory(
397+
ram_addr, data,
398+
on_progress=lambda d, t: (
399+
on_progress(d // 2, total) if on_progress else None
400+
),
401+
)
402+
if not ok:
403+
logger.error("Flash write: RAM upload failed")
404+
return False
393405

394-
while offset < total:
395-
block_size = min(WRITE_MAX_TRANSFER, total - offset)
396-
block = data[offset:offset + block_size]
406+
# Phase 2: Tell agent to erase + program flash from RAM
407+
logger.info("Flash write: programming flash from RAM")
408+
self._clear_rx_buffers()
409+
expected_crc = zlib.crc32(data) & 0xFFFFFFFF
410+
payload = struct.pack("<IIII", ram_addr, addr, total, expected_crc)
411+
await send_packet(self._transport, CMD_FLASH_PROGRAM, payload)
397412

398-
ok = False
399-
for attempt in range(max_retries):
400-
ok = await self._write_block(addr + offset, block)
401-
if ok:
402-
break
403-
logger.warning(
404-
"Flash write retry %d at offset %d/%d",
405-
attempt + 1, offset, total,
406-
)
407-
import asyncio
408-
await asyncio.sleep(0.1)
413+
# Read initial ACK (CRC verified)
414+
cmd, resp = await recv_response(self._transport, timeout=30.0)
415+
if cmd != RSP_ACK or resp[0] != ACK_OK:
416+
logger.error("Flash program rejected: 0x%02x", resp[0] if resp else -1)
417+
return False
409418

410-
if not ok:
411-
logger.error("Flash write failed at offset %d/%d", offset, total)
419+
# Read progress packets until final ACK
420+
while True:
421+
cmd, data_pkt = await recv_packet(self._transport, timeout=60.0)
422+
if cmd == RSP_READY:
423+
continue
424+
elif cmd == RSP_DATA and len(data_pkt) >= 4:
425+
done = data_pkt[0] | (data_pkt[1] << 8)
426+
total_steps = data_pkt[2] | (data_pkt[3] << 8)
427+
if on_progress and total_steps > 0:
428+
on_progress(total // 2 + (total // 2) * done // total_steps, total)
429+
elif cmd == RSP_ACK:
430+
if data_pkt[0] == ACK_OK:
431+
return True
432+
logger.error("Flash program failed: 0x%02x", data_pkt[0])
412433
return False
434+
else:
435+
break
413436

414-
offset += block_size
415-
if on_progress:
416-
on_progress(offset, total)
417-
418-
return True
437+
return False
419438

420439
async def crc32(self, addr: int, size: int) -> int:
421440
"""Get CRC32 of a memory region from the device."""
@@ -654,9 +673,12 @@ async def set_baud(self, baud: int) -> bool:
654673
return False
655674

656675
async def reboot(self) -> None:
657-
"""Reboot is disabled — watchdog reset kills the agent on serial
658-
boot pin with no recovery. Use selfupdate() to reload code."""
659-
raise RuntimeError(
660-
"Reboot disabled: watchdog reset re-enters bootrom on serial "
661-
"boot mode, requiring physical power cycle. Use selfupdate() instead."
662-
)
676+
"""Reset the device via watchdog. Bootrom boots from flash if
677+
valid firmware is present, otherwise enters serial download."""
678+
self._clear_rx_buffers()
679+
from defib.agent.protocol import CMD_REBOOT
680+
await send_packet(self._transport, CMD_REBOOT)
681+
try:
682+
cmd, resp = await recv_response(self._transport, timeout=5.0)
683+
except Exception:
684+
pass # Agent may reset before ACK arrives

src/defib/agent/protocol.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
CMD_SELFUPDATE = 0x07
4545
CMD_SET_BAUD = 0x08
4646
CMD_SCAN = 0x09
47+
CMD_FLASH_PROGRAM = 0x0A
4748

4849
# Responses
4950
RSP_INFO = 0x81

0 commit comments

Comments
 (0)