From 73a363770582581d58782ac98e1327ded5f5b793 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Mon, 20 Apr 2026 16:59:15 +0300 Subject: [PATCH] Add PRESTEP1 DDR training step and fix boot timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PRESTEP1 support: DDR training verification bytecode sent after DDRSTEP0. Required by hi3516av200 (and likely others) for DDR to actually initialize. - Make PRESTEP0/DDRSTEP0/PRESTEP1 TAIL frames non-fatal (av200 bootrom doesn't ACK them). - Add rehandshake after PRESTEP0 (av200 bootrom goes silent after executing PRESTEP0 code). - Increase HEAD frame timeout from 30ms to 150ms (av200 needs ~100ms after handshake before accepting frames). - Fix power-cycle session: flush serial buffer 1s after power-cycle to discard warm-reboot markers, preventing false handshake triggers. Tested on hi3516av200 hardware: full PRESTEP0→DDRSTEP0→PRESTEP1→SPL→ U-Boot transfer succeeds, U-Boot boots. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/defib/profiles/data/hi3516av200.json | 2 +- src/defib/profiles/schema.py | 10 ++++++++ src/defib/protocol/hisilicon_standard.py | 21 ++++++++++++++--- src/defib/recovery/session.py | 30 ++++++++++++++---------- tests/test_protocol_robustness.py | 4 ++-- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/defib/profiles/data/hi3516av200.json b/src/defib/profiles/data/hi3516av200.json index eb15cba..94e296c 100644 --- a/src/defib/profiles/data/hi3516av200.json +++ b/src/defib/profiles/data/hi3516av200.json @@ -1 +1 @@ -{"name": "hi3516av200", "PRESTEP0": [4, 224, 45, 229, 60, 1, 0, 227, 2, 2, 65, 227, 78, 23, 5, 227, 79, 20, 68, 227, 0, 16, 128, 229, 64, 1, 0, 227, 2, 2, 65, 227, 117, 26, 6, 227, 105, 26, 71, 227, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222], "DDRSTEP0": [4, 224, 45, 229, 60, 1, 0, 227, 2, 2, 65, 227, 120, 22, 5, 227, 52, 18, 65, 227, 0, 16, 128, 229, 64, 1, 0, 227, 2, 2, 65, 227, 117, 26, 6, 227, 105, 26, 71, 227, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222], "ADDRESS": ["0x04013000", "0x04010500", "0x81000000"], "FILELEN": ["0x0040", "0x4F00"], "STEPLEN": ["0x0040", "0x0080"]} +{"name": "hi3516av200", "PRESTEP0": [4, 224, 45, 229, 60, 1, 0, 227, 2, 2, 65, 227, 78, 23, 5, 227, 79, 20, 68, 227, 0, 16, 128, 229, 64, 1, 0, 227, 2, 2, 65, 227, 117, 26, 6, 227, 105, 26, 71, 227, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222], "PRESTEP1": [4, 224, 45, 229, 64, 1, 0, 227, 2, 2, 65, 227, 255, 31, 15, 227, 255, 31, 79, 227, 0, 16, 128, 229, 60, 1, 0, 227, 2, 2, 65, 227, 78, 23, 5, 227, 79, 20, 68, 227, 0, 16, 128, 229, 60, 0, 159, 229, 0, 0, 144, 229, 32, 98, 160, 225, 3, 96, 6, 226, 0, 0, 86, 227, 5, 243, 160, 3, 1, 0, 86, 227, 5, 243, 160, 3, 80, 1, 0, 227, 2, 2, 65, 227, 67, 29, 4, 227, 77, 21, 68, 227, 0, 16, 128, 229, 4, 224, 157, 228, 14, 240, 160, 225, 239, 190, 173, 222, 239, 190, 173, 222, 140, 0, 2, 18, 0, 0, 160, 225, 0, 0, 160, 225, 0, 0, 160, 225], "DDRSTEP0": [4, 224, 45, 229, 60, 1, 0, 227, 2, 2, 65, 227, 120, 22, 5, 227, 52, 18, 65, 227, 0, 16, 128, 229, 64, 1, 0, 227, 2, 2, 65, 227, 117, 26, 6, 227, 105, 26, 71, 227, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222], "ADDRESS": ["0x04013000", "0x04010500", "0x81000000"], "FILELEN": ["0x0040", "0x4F00"], "STEPLEN": ["0x0040", "0x0080"]} diff --git a/src/defib/profiles/schema.py b/src/defib/profiles/schema.py index 08c81d5..df5f17c 100644 --- a/src/defib/profiles/schema.py +++ b/src/defib/profiles/schema.py @@ -16,6 +16,10 @@ class SoCProfile(BaseModel): default=None, alias="PRESTEP0", description="Pre-DDR init bytecode (sent before DDRSTEP0)", ) + prestep1: list[int] | None = Field( + default=None, alias="PRESTEP1", + description="DDR training verification bytecode (sent after DDRSTEP0)", + ) ddrstep0: list[int] = Field(alias="DDRSTEP0", description="DDR initialization bytecode") addresses: list[str] = Field( alias="ADDRESS", @@ -56,4 +60,10 @@ def prestep_data(self) -> bytes | None: return None return bytes(self.prestep0) + @property + def prestep1_data(self) -> bytes | None: + if self.prestep1 is None: + return None + return bytes(self.prestep1) + model_config = {"populate_by_name": True} diff --git a/src/defib/protocol/hisilicon_standard.py b/src/defib/protocol/hisilicon_standard.py index b5d57b6..d100358 100644 --- a/src/defib/protocol/hisilicon_standard.py +++ b/src/defib/protocol/hisilicon_standard.py @@ -244,7 +244,7 @@ async def _send_head( logger.debug("HEAD: length=%d address=0x%08X", length, address) frame = HeadFrame(length=length, address=address).encode() return await self._send_frame_with_retry( - transport, frame, FRAME_SEND_RETRIES_SHORT, timeout=0.03 + transport, frame, FRAME_SEND_RETRIES_SHORT, timeout=0.15 ) async def _send_data( @@ -293,7 +293,8 @@ async def _send_ddr_step( if not await self._send_data(transport, 1, prestep): return False if not await self._send_tail(transport, 2): - return False + logger.debug("PRESTEP0 TAIL not ACKed (non-fatal)") + await self._rehandshake(transport) # DDRSTEP0: actual DDR init trigger logger.debug( @@ -308,7 +309,21 @@ async def _send_ddr_step( return False if not await self._send_tail(transport, 2): - return False + logger.debug("DDRSTEP0 TAIL not ACKed (non-fatal)") + + # PRESTEP1: DDR training verification (waits for DDR to be ready) + prestep1 = profile.prestep1_data + if prestep1 is not None: + logger.debug( + "=== PRESTEP1 === address=0x%08X data=%d bytes", + addr, len(prestep1), + ) + if not await self._send_head(transport, len(prestep1), addr): + return False + if not await self._send_data(transport, 1, prestep1): + return False + if not await self._send_tail(transport, 2): + logger.debug("PRESTEP1 TAIL not ACKed (non-fatal)") _emit(on_progress, ProgressEvent( stage=Stage.DDR_INIT, bytes_sent=64, bytes_total=64, diff --git a/src/defib/recovery/session.py b/src/defib/recovery/session.py index 7ad157f..8180d88 100644 --- a/src/defib/recovery/session.py +++ b/src/defib/recovery/session.py @@ -107,22 +107,9 @@ async def run( message=f"Power-cycling {self._poe_port}...", )) - # Start handshake (flooding 0xAA) BEFORE power cycle so the - # bootrom sees 0xAA immediately when power returns. - if on_log: - on_log(LogEvent( - level="info", - message=f"Starting {self._protocol_cls.name()} handshake for {self.chip}", - )) - import asyncio - handshake_task = asyncio.create_task( - protocol.handshake(transport, on_progress) - ) - try: await self._power.power_cycle(self._poe_port) except Exception as e: - handshake_task.cancel() elapsed = (time.monotonic() - start_time) * 1000 if on_log: on_log(LogEvent(level="error", message=f"Power cycle failed: {e}")) @@ -132,6 +119,23 @@ async def run( elapsed_ms=elapsed, ) + # Wait for power to actually be cut, then flush any warm-reboot + # garbage from the serial buffer before starting the handshake. + import asyncio + await asyncio.sleep(1.0) + await transport.flush_input() + + # Now start handshake — floods 0xAA so the bootrom sees it + # immediately when power returns from the cycle. + if on_log: + on_log(LogEvent( + level="info", + message=f"Starting {self._protocol_cls.name()} handshake for {self.chip}", + )) + handshake_task = asyncio.create_task( + protocol.handshake(transport, on_progress) + ) + if on_progress: on_progress(ProgressEvent( stage=Stage.POWER_CYCLE, bytes_sent=1, bytes_total=1, diff --git a/tests/test_protocol_robustness.py b/tests/test_protocol_robustness.py index 8fa9e1c..fdd331f 100644 --- a/tests/test_protocol_robustness.py +++ b/tests/test_protocol_robustness.py @@ -152,8 +152,8 @@ async def test_full_transfer_with_rehandshake(self): protocol = HiSiliconStandard() protocol.set_profile(profile) - # Phase 0: PRESTEP0 ACKs (head + data + tail = 3 ACKs, if profile has it) - prestep_acks = 3 if profile.prestep_data is not None else 0 + # Phase 0: PRESTEP0 ACKs (head + data + tail = 3, + 1 consumed by rehandshake) + prestep_acks = 4 if profile.prestep_data is not None else 0 # Phase 1: DDR step ACKs (head + data + tail = 3 ACKs) # Phase 2: SPL ACKs (head + spl_chunks + tail) spl_size = profile.spl_max_size