Skip to content

Commit d18b798

Browse files
authored
Merge pull request #37 from OpenIPC/feature/flash-stream
Add CMD_FLASH_STREAM with double-buffer pipeline and fix FMC read bug
2 parents eab126c + fc4bb17 commit d18b798

File tree

6 files changed

+263
-46
lines changed

6 files changed

+263
-46
lines changed

agent/main.c

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ static void handle_read(const uint8_t *data, uint32_t len) {
153153
for (uint32_t j = 0; j < 4 && (i + j) < chunk; j++)
154154
pkt[2 + i + j] = (val >> ((byte_off + j) * 8)) & 0xFF;
155155
}
156+
} else if (flash_readable && addr >= FLASH_MEM &&
157+
(addr + size) <= (FLASH_MEM + flash_info.size)) {
158+
/* Register-based flash read — boot mode window wraps at 1MB */
159+
uint8_t tmp[MAX_PAYLOAD];
160+
flash_read(addr - FLASH_MEM + offset, tmp, chunk);
161+
for (uint32_t i = 0; i < chunk; i++)
162+
pkt[2 + i] = tmp[i];
156163
} else {
157164
const uint8_t *ptr = (const uint8_t *)addr;
158165
for (uint32_t i = 0; i < chunk; i++)
@@ -177,8 +184,16 @@ static void handle_crc32_cmd(const uint8_t *data, uint32_t len) {
177184
return;
178185
}
179186

180-
const uint8_t *ptr = (const uint8_t *)addr;
181-
uint32_t c = crc32(0, ptr, size);
187+
uint32_t c;
188+
/* Route flash reads through register-based path — boot mode memory
189+
* window wraps at 1MB on some SoCs. */
190+
if (flash_readable && addr >= FLASH_MEM &&
191+
(addr + size) <= (FLASH_MEM + flash_info.size)) {
192+
c = flash_crc32(addr - FLASH_MEM, size);
193+
} else {
194+
const uint8_t *ptr = (const uint8_t *)addr;
195+
c = crc32(0, ptr, size);
196+
}
182197
uint8_t resp[4];
183198
write_le32(resp, c);
184199
proto_send(RSP_CRC32, resp, 4);
@@ -474,6 +489,129 @@ static void handle_flash_program(const uint8_t *data, uint32_t len) {
474489
proto_send_ack(ACK_OK);
475490
}
476491

492+
/*
493+
* CMD_FLASH_STREAM: stream data from UART directly to flash.
494+
*
495+
* Processes one sector at a time: erase → receive → program.
496+
* No separate RAM upload phase — data flows UART → RAM buffer → flash.
497+
* Host streams DATA packets continuously; agent sends per-sector progress.
498+
*
499+
* Host sends: CMD_FLASH_STREAM [flash_addr:4LE] [size:4LE] [crc:4LE]
500+
* Agent ACKs, then for each sector: erase, receive 64KB, program pages.
501+
* Progress: RSP_DATA [sector_done:2LE] [total:2LE] after each sector.
502+
* Final: ACK_OK or ACK_CRC_ERROR.
503+
*/
504+
static void handle_flash_stream(const uint8_t *data, uint32_t len) {
505+
if (len < 12) { proto_send_ack(ACK_CRC_ERROR); return; }
506+
if (!flash_readable) { proto_send_ack(ACK_FLASH_ERROR); return; }
507+
508+
uint32_t flash_addr = read_le32(&data[0]);
509+
uint32_t size = read_le32(&data[4]);
510+
uint32_t expected_crc = read_le32(&data[8]);
511+
512+
if (size == 0 || flash_addr + size > flash_info.size) {
513+
proto_send_ack(ACK_FLASH_ERROR);
514+
return;
515+
}
516+
517+
uint32_t sector_sz = flash_info.sector_size;
518+
uint32_t page_sz = flash_info.page_size;
519+
uint32_t num_sectors = (size + sector_sz - 1) / sector_sz;
520+
521+
/* Double buffer: receive into one while erasing+programming the other.
522+
* Only 128KB total — works on all SoCs. */
523+
uint8_t *buf[2] = {
524+
(uint8_t *)(RAM_BASE + 0x200000),
525+
(uint8_t *)(RAM_BASE + 0x210000),
526+
};
527+
int rx_buf = 0; /* Buffer currently being filled */
528+
529+
proto_send_ack(ACK_OK);
530+
531+
uint32_t total_received = 0;
532+
/* Pending sector to erase+program (from previous iteration) */
533+
int pending_buf = -1;
534+
uint32_t pending_addr = 0;
535+
uint32_t pending_bytes = 0;
536+
537+
for (uint32_t s = 0; s < num_sectors; s++) {
538+
uint32_t sector_offset = s * sector_sz;
539+
uint32_t sector_bytes = size - sector_offset;
540+
if (sector_bytes > sector_sz) sector_bytes = sector_sz;
541+
542+
/* Receive sector data into rx_buf.
543+
* No flash operations during receive — UART stays responsive. */
544+
uint32_t buf_received = 0;
545+
uint8_t pkt[MAX_PAYLOAD + 16];
546+
while (buf_received < sector_bytes) {
547+
uint32_t pkt_len = 0;
548+
uint8_t cmd = proto_recv(pkt, &pkt_len, 10000);
549+
if (cmd == RSP_DATA && pkt_len > 2) {
550+
uint32_t chunk = pkt_len - 2;
551+
for (uint32_t i = 0; i < chunk && buf_received < sector_bytes; i++)
552+
buf[rx_buf][buf_received++] = pkt[2 + i];
553+
} else if (cmd == 0) {
554+
uint8_t err[5];
555+
err[0] = ACK_FLASH_ERROR;
556+
write_le32(&err[1], total_received + buf_received);
557+
proto_send(RSP_ACK, err, 5);
558+
return;
559+
}
560+
}
561+
562+
total_received += sector_bytes;
563+
564+
/* Tell host: "sector received, send next now!"
565+
* Host starts streaming next sector immediately. */
566+
{
567+
uint8_t progress[4];
568+
progress[0] = ((s + 1) >> 0) & 0xFF;
569+
progress[1] = ((s + 1) >> 8) & 0xFF;
570+
progress[2] = (num_sectors >> 0) & 0xFF;
571+
progress[3] = (num_sectors >> 8) & 0xFF;
572+
proto_send(RSP_DATA, progress, 4);
573+
}
574+
575+
/* Process previous sector if pending (erase + program).
576+
* Host is streaming next sector into the OTHER buffer right now. */
577+
if (pending_buf >= 0) {
578+
flash_erase_sector(pending_addr);
579+
uint32_t offset = 0;
580+
while (offset < pending_bytes) {
581+
uint32_t chunk = pending_bytes - offset;
582+
if (chunk > page_sz) chunk = page_sz;
583+
flash_write_page(pending_addr + offset,
584+
&buf[pending_buf][offset], chunk);
585+
offset += chunk;
586+
proto_drain_fifo();
587+
}
588+
}
589+
590+
/* This sector's buffer becomes the pending one */
591+
pending_buf = rx_buf;
592+
pending_addr = flash_addr + sector_offset;
593+
pending_bytes = sector_bytes;
594+
595+
/* Swap to the other buffer for next receive */
596+
rx_buf ^= 1;
597+
}
598+
599+
/* Process the last sector (no more data to receive) */
600+
if (pending_buf >= 0) {
601+
flash_erase_sector(pending_addr);
602+
uint32_t offset = 0;
603+
while (offset < pending_bytes) {
604+
uint32_t chunk = pending_bytes - offset;
605+
if (chunk > page_sz) chunk = page_sz;
606+
flash_write_page(pending_addr + offset,
607+
&buf[pending_buf][offset], chunk);
608+
offset += chunk;
609+
}
610+
}
611+
612+
proto_send_ack(ACK_OK);
613+
}
614+
477615
/*
478616
* ARM32 position-independent trampoline (machine code).
479617
* Copies r2 bytes from r1 to r0, then branches to r0-r2 (original dst).
@@ -790,6 +928,9 @@ int main(void) {
790928
case CMD_FLASH_PROGRAM:
791929
handle_flash_program(cmd_buf, data_len);
792930
break;
931+
case CMD_FLASH_STREAM:
932+
handle_flash_stream(cmd_buf, data_len);
933+
break;
793934
case CMD_SET_BAUD:
794935
handle_set_baud(cmd_buf, data_len);
795936
break;

agent/protocol.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ static uint8_t rx_raw[MAX_PAYLOAD + 16];
6161

6262
/* Software RX FIFO — drains PL011 hardware FIFO (16 bytes) to prevent
6363
* overflow during COBS decode + CRC32 processing on uncached DDR. */
64-
static uint8_t soft_rx[4096];
64+
static uint8_t soft_rx[72 * 1024]; /* Must hold ~1 sector during flash ops */
6565
static uint32_t soft_rx_head = 0;
6666
static uint32_t soft_rx_tail = 0;
6767

agent/protocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define CMD_SET_BAUD 0x08
1919
#define CMD_SCAN 0x09
2020
#define CMD_FLASH_PROGRAM 0x0A
21+
#define CMD_FLASH_STREAM 0x0B
2122

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

agent/spi_flash.c

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ static void fmc_enter_boot(void) {
110110
for (volatile int i = 0; i < 1000; i++) {}
111111
REG_FMC_CRG = crg & ~FMC_SOFT_RESET;
112112
for (volatile int i = 0; i < 1000; i++) {}
113+
114+
/* Soft reset clears FMC registers — reconfigure for proper boot mode.
115+
* Without this, the memory window at FLASH_MEM wraps at 1MB. */
116+
fmc_reg(FMC_SPI_TIMING_CFG) = SPI_TIMING_VAL;
117+
fmc_reg(FMC_INT_CLR) = 0xFF;
113118
}
114119

115120
static void fmc_wait_ready(void) {
@@ -122,6 +127,9 @@ static void spi_wait_wip(void) {
122127
for (int i = 0; i < 10000000; i++) {
123128
uint8_t sr = flash_read_status();
124129
if (!(sr & SPI_STATUS_WIP)) return;
130+
/* Drain UART FIFO while waiting — prevents overflow during
131+
* long flash operations (erase ~150ms, page program ~1-3ms) */
132+
proto_drain_fifo();
125133
}
126134
}
127135

@@ -226,9 +234,29 @@ int flash_init(flash_info_t *info) {
226234
}
227235

228236
void flash_read(uint32_t addr, uint8_t *buf, uint32_t len) {
229-
const uint8_t *flash = (const uint8_t *)FLASH_MEM;
230-
for (uint32_t i = 0; i < len; i++)
231-
buf[i] = flash[addr + i];
237+
/* Use register-based reads (normal mode) instead of memory window.
238+
* The boot mode memory window wraps at 1MB on some SoCs. */
239+
fmc_enter_normal();
240+
volatile uint8_t *iobuf = (volatile uint8_t *)(FLASH_MEM);
241+
242+
while (len > 0) {
243+
uint32_t chunk = len > 256 ? 256 : len;
244+
fmc_reg(FMC_CMD) = 0x03; /* SPI READ */
245+
fmc_reg(FMC_ADDRL) = addr;
246+
fmc_reg(FMC_DATA_NUM) = chunk;
247+
fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(3);
248+
fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_READ_DATA | FMC_OP_REG_OP_START;
249+
fmc_wait_ready();
250+
251+
for (uint32_t i = 0; i < chunk; i++)
252+
buf[i] = iobuf[i];
253+
254+
buf += chunk;
255+
addr += chunk;
256+
len -= chunk;
257+
}
258+
259+
fmc_enter_boot();
232260
}
233261

234262
uint8_t flash_unlock_debug[3];
@@ -297,6 +325,31 @@ int flash_write_page(uint32_t addr, const uint8_t *data, uint32_t len) {
297325
}
298326

299327
uint32_t flash_crc32(uint32_t addr, uint32_t len) {
300-
const uint8_t *flash = (const uint8_t *)FLASH_MEM;
301-
return crc32(0, &flash[addr], len);
328+
/* Use register-based reads to compute CRC32 over flash region.
329+
* Boot mode memory window wraps at 1MB on some SoCs. */
330+
fmc_enter_normal();
331+
volatile uint8_t *iobuf = (volatile uint8_t *)(FLASH_MEM);
332+
uint32_t c = 0;
333+
334+
while (len > 0) {
335+
uint32_t chunk = len > 256 ? 256 : len;
336+
fmc_reg(FMC_CMD) = 0x03; /* SPI READ */
337+
fmc_reg(FMC_ADDRL) = addr;
338+
fmc_reg(FMC_DATA_NUM) = chunk;
339+
fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(3);
340+
fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_READ_DATA | FMC_OP_REG_OP_START;
341+
fmc_wait_ready();
342+
343+
/* CRC the I/O buffer contents in place */
344+
uint8_t tmp[256];
345+
for (uint32_t i = 0; i < chunk; i++)
346+
tmp[i] = iobuf[i];
347+
c = crc32(c, tmp, chunk);
348+
349+
addr += chunk;
350+
len -= chunk;
351+
}
352+
353+
fmc_enter_boot();
354+
return c;
302355
}

0 commit comments

Comments
 (0)