@@ -92,6 +92,9 @@ def unstable(self) -> list[SectorResult]:
9292WRITE_CHUNK_SIZE = 512
9393# Max bytes per WRITE block.
9494WRITE_MAX_TRANSFER = 16 * 1024
95+ # Number of blocks sent before reading ACKs (windowed ACK).
96+ # Higher = less round-trip overhead, but more data at risk on failure.
97+ WRITE_WINDOW = 16 # 16 × 16KB = 256KB in flight
9598
9699# Optimal baud rate from hi3516ev300 + FT232R benchmarks.
97100# 921600 is the highest rate verified for both READ and WRITE with
@@ -268,11 +271,13 @@ async def write_memory(
268271 on_progress : Callable [[int , int ], None ] | None = None ,
269272 fast : bool = True ,
270273 ) -> bool :
271- """Write data to device RAM in chunked transfers .
274+ """Write data to device RAM using windowed ACK .
272275
273- If fast=True and data > 4KB, switches to DEFAULT_FAST_BAUD.
274- Splits into WRITE_MAX_TRANSFER-sized blocks to avoid PL011 FIFO
275- overflow on uncached DDR. Each block uses WRITE_CHUNK_SIZE packets.
276+ Sends W blocks (CMD_WRITE + DATA stream) back-to-back without
277+ waiting for ACKs, then reads all W ACKs. This pipelines the
278+ round-trips, approaching line speed.
279+
280+ Falls back to single-block mode on failure.
276281 """
277282 self ._clear_rx_buffers ()
278283
@@ -281,34 +286,91 @@ async def write_memory(
281286
282287 total = len (data )
283288 offset = 0
284- max_retries = 5
289+ window = min ( WRITE_WINDOW , ( total + WRITE_MAX_TRANSFER - 1 ) // WRITE_MAX_TRANSFER )
285290
286291 while offset < total :
287- block_size = min (WRITE_MAX_TRANSFER , total - offset )
288- block = data [offset :offset + block_size ]
292+ # Send a window of blocks without waiting for ACKs
293+ blocks_sent = 0
294+ window_start = offset
289295
290- ok = False
291- for attempt in range (max_retries ):
292- ok = await self ._write_block (addr + offset , block )
293- if ok :
296+ for _ in range (window ):
297+ if offset >= total :
298+ break
299+ block_size = min (WRITE_MAX_TRANSFER , total - offset )
300+ block = data [offset :offset + block_size ]
301+ crc = zlib .crc32 (block ) & 0xFFFFFFFF
302+
303+ # Send CMD_WRITE + all DATA packets (no wait)
304+ payload = struct .pack ("<III" , addr + offset , block_size , crc )
305+ await send_packet (self ._transport , CMD_WRITE , payload )
306+
307+ blk_off = 0
308+ seq = 0
309+ while blk_off < block_size :
310+ chunk = min (WRITE_CHUNK_SIZE , block_size - blk_off )
311+ pkt = struct .pack ("<H" , seq ) + block [blk_off :blk_off + chunk ]
312+ await send_packet (self ._transport , RSP_DATA , pkt )
313+ blk_off += chunk
314+ seq += 1
315+
316+ offset += block_size
317+ blocks_sent += 1
318+
319+ # Now read all ACKs: initial ACK + CRC ACK per block
320+ all_ok = True
321+ for i in range (blocks_sent ):
322+ try :
323+ # Initial ACK
324+ cmd , resp = await recv_response (self ._transport , timeout = 10.0 )
325+ if cmd != RSP_ACK or resp [0 ] != ACK_OK :
326+ all_ok = False
327+ break
328+
329+ # CRC ACK
330+ crc_timeout = max (60.0 , WRITE_MAX_TRANSFER / 50000 )
331+ cmd , resp = await recv_response (self ._transport , timeout = crc_timeout )
332+ if cmd != RSP_ACK or resp [0 ] != ACK_OK :
333+ all_ok = False
334+ break
335+ except Exception :
336+ all_ok = False
294337 break
295- logger .warning (
296- "WRITE retry %d at offset %d/%d" ,
297- attempt + 1 , offset , total ,
298- )
299- import asyncio
300- await asyncio .sleep (0.1 )
301338
302- if not ok :
303- logger .error (
304- "WRITE failed at offset %d/%d after %d retries" ,
305- offset , total , max_retries ,
306- )
307- return False
339+ if on_progress :
340+ on_progress (window_start + (i + 1 ) * WRITE_MAX_TRANSFER , total )
308341
309- offset += block_size
310- if on_progress :
311- on_progress (offset , total )
342+ if not all_ok :
343+ # Fall back to single-block retry for the failed window
344+ logger .warning ("Window failed at offset %d, retrying single-block" , window_start )
345+ offset = window_start
346+ window = 1 # Degrade to single-block mode
347+ self ._clear_rx_buffers ()
348+
349+ # Drain any stale ACKs from partially-completed window
350+ import asyncio
351+ await asyncio .sleep (0.5 )
352+ port = getattr (self ._transport , '_port' , None )
353+ if port is not None :
354+ port .reset_input_buffer ()
355+ from defib .agent .protocol import _port_buffers
356+ _port_buffers [id (port )] = bytearray ()
357+
358+ # Retry this window as single blocks
359+ for _ in range (blocks_sent ):
360+ if offset >= total :
361+ break
362+ block_size = min (WRITE_MAX_TRANSFER , total - offset )
363+ block = data [offset :offset + block_size ]
364+ ok = await self ._write_block (addr + offset , block )
365+ if not ok :
366+ logger .error ("Single-block retry failed at %d/%d" , offset , total )
367+ return False
368+ offset += block_size
369+ if on_progress :
370+ on_progress (offset , total )
371+
372+ # Restore window for next iteration
373+ window = min (WRITE_WINDOW , (total - offset + WRITE_MAX_TRANSFER - 1 ) // WRITE_MAX_TRANSFER )
312374
313375 return True
314376
0 commit comments