diff --git a/src/defib/profiles/data/hi3516ev300.json b/src/defib/profiles/data/hi3516ev300.json index 14b69ae..6220544 100644 --- a/src/defib/profiles/data/hi3516ev300.json +++ b/src/defib/profiles/data/hi3516ev300.json @@ -1 +1 @@ -{"name": "hi3516ev200", "DDRSTEP0": [4, 224, 45, 229, 36, 0, 159, 229, 36, 16, 159, 229, 0, 16, 128, 229, 32, 0, 159, 229, 32, 16, 159, 229, 4, 16, 128, 228, 0, 224, 128, 229, 4, 240, 157, 228, 239, 190, 173, 222, 239, 190, 173, 222, 239, 190, 173, 222, 60, 1, 2, 18, 120, 86, 52, 18, 64, 1, 2, 18, 117, 106, 105, 122], "ADDRESS": ["0x04013000", "0x04010500", "0x41000000"], "FILELEN": ["0x0040", "0x6000"], "STEPLEN": ["0x0040", "0x0080"]} \ No newline at end of file +hi3516ev200.json diff --git a/src/defib/protocol/hisilicon_standard.py b/src/defib/protocol/hisilicon_standard.py index d100358..e0a1d03 100644 --- a/src/defib/protocol/hisilicon_standard.py +++ b/src/defib/protocol/hisilicon_standard.py @@ -92,6 +92,15 @@ def set_continuous_ack(self, enabled: bool) -> None: """ self._continuous_ack = enabled + @property + def uses_frame_blast_handshake(self) -> bool: + """Whether this chip uses sendFrameForStart (HEAD blast) as handshake. + + When True, the caller should skip the separate handshake() call and + let send_firmware() handle it internally via _send_frame_for_start(). + """ + return self._profile is not None and self._profile.prestep_data is not None + async def handshake( self, transport: Transport, @@ -263,16 +272,53 @@ async def _send_tail(self, transport: Transport, seq: int) -> bool: transport, frame, FRAME_SEND_RETRIES_SHORT, timeout=0.15 ) + async def _send_frame_for_start( + self, transport: Transport, profile: SoCProfile + ) -> bool: + """HiTool-style handshake: blast 0xAA + HEAD(FILELEN0, ADDRESS0) until ACK. + + Continuously sends 0xAA (to trigger bootrom download mode) followed + by the 14-byte HEAD frame for up to 30 seconds. The bootrom enters + download mode on 0xAA, then ACKs the HEAD frame. + """ + import time + + addr = profile.ddr_step_address + head_frame = HeadFrame(length=64, address=addr).encode() + logger.debug( + "sendFrameForStart: blasting 0xAA+HEAD(64, 0x%08X) for 30s", + addr, + ) + + deadline = time.monotonic() + 30.0 + attempt = 0 + while time.monotonic() < deadline: + await transport.write(BOOTMODE_ACK + head_frame) + attempt += 1 + try: + ack = await transport.read(1, timeout=0.05) + if ack == ACK_BYTE: + logger.debug( + "sendFrameForStart: ACK after %d blasts", attempt + ) + await transport.flush_input() + return True + except TransportTimeout: + continue + logger.debug("sendFrameForStart: timeout after 30s (%d blasts)", attempt) + return False + async def _send_ddr_step( self, transport: Transport, profile: SoCProfile, on_progress: Callable[[ProgressEvent], None] | None = None, ) -> bool: - """Send DDR initialization step (64 bytes to SRAM). + """Send DDR initialization steps to SRAM. - If the profile has PRESTEP0 data, it is sent first. PRESTEP0 - prepares the DDR controller before the actual init trigger. + Matches HiTool's exact sequence: + 1. sendFrameForStart: blast HEAD(64, ADDRESS0) until ACK (handshake) + 2. For each step (PRESTEP0, DDRSTEP0, PRESTEP1): HEAD+DATA+TAIL """ _emit(on_progress, ProgressEvent( stage=Stage.DDR_INIT, bytes_sent=0, bytes_total=64, @@ -280,10 +326,14 @@ async def _send_ddr_step( )) addr = profile.ddr_step_address - - # PRESTEP0: pre-DDR init (e.g. write "NOWD" to DDR controller) prestep = profile.prestep_data + + # sendFrameForStart: blast HEAD as handshake (HiTool approach) if prestep is not None: + if not await self._send_frame_for_start(transport, profile): + return False + + # PRESTEP0: HEAD+DATA+TAIL (HEAD sent again per HiTool's loop) logger.debug( "=== PRESTEP0 === address=0x%08X data=%d bytes", addr, len(prestep), @@ -294,7 +344,6 @@ async def _send_ddr_step( return False if not await self._send_tail(transport, 2): logger.debug("PRESTEP0 TAIL not ACKed (non-fatal)") - await self._rehandshake(transport) # DDRSTEP0: actual DDR init trigger logger.debug( @@ -372,6 +421,13 @@ async def _send_spl( if not await self._send_tail(transport, len(chunks) + 1): return False + # DDR training delay: HiTool sleeps 300ms after SPL transfer + # for files matching specific sizes (20224, 21504, 29696). + import asyncio as _asyncio + if spl_size in (20224, 21504, 29696): + logger.debug("DDR training delay: 300ms (spl_size=%d)", spl_size) + await _asyncio.sleep(0.3) + _emit(on_progress, ProgressEvent( stage=Stage.SPL, bytes_sent=spl_size, bytes_total=spl_size, message="SPL complete", @@ -399,9 +455,14 @@ async def _send_uboot( message=f"Sending {label}", )) - # After SPL runs DDR init, some SoCs (e.g. hi3516av200) re-send - # 0x20 bootmode markers requiring a fresh 0xAA handshake. - await self._rehandshake(transport) + # After SPL runs DDR init, some SoCs re-send 0x20 bootmode markers. + # For chips with PRESTEP0 (frame-blast), use the full blast approach + # since simple rehandshake isn't sufficient on av200. + if profile.prestep_data is not None: + if not await self._send_frame_for_start(transport, profile): + return False + else: + await self._rehandshake(transport) head = HeadFrame(length=total, address=profile.uboot_address).encode() if not await self._send_frame_with_retry( transport, head, retries=64, timeout=0.15, diff --git a/src/defib/recovery/session.py b/src/defib/recovery/session.py index 8180d88..56adc64 100644 --- a/src/defib/recovery/session.py +++ b/src/defib/recovery/session.py @@ -15,6 +15,7 @@ from defib.protocol.hisilicon_standard import HiSiliconStandard from defib.protocol.registry import find_protocol from defib.recovery.events import ( + HandshakeResult, LogEvent, ProgressEvent, RecoveryResult, @@ -91,10 +92,12 @@ async def run( if on_log: on_log(LogEvent(level="info", message=f"Loaded profile: {profile.name}")) - # Power cycle + handshake - # For devices with very short bootrom windows (<100ms), the - # handshake must start BEFORE power-on so continuous ACK mode - # can flood 0xAA while the bootrom is active. + # Determine if this chip uses frame-blast handshake (built into send_firmware) + frame_blast = ( + isinstance(protocol, HiSiliconStandard) and protocol.uses_frame_blast_handshake + ) + + # Power cycle if self._power and self._poe_port: if on_log: on_log(LogEvent( @@ -120,28 +123,36 @@ async def run( ) # Wait for power to actually be cut, then flush any warm-reboot - # garbage from the serial buffer before starting the handshake. + # garbage from the serial buffer. 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_progress: + on_progress(ProgressEvent( + stage=Stage.POWER_CYCLE, bytes_sent=1, bytes_total=1, + message="Power cycle complete", + )) + + # Handshake — skip for frame-blast chips (handled inside send_firmware) + if frame_blast: + if on_log: + on_log(LogEvent( + level="info", + message=f"Using sendFrameForStart handshake for {self.chip}", + )) + handshake = HandshakeResult(success=True, message="Frame-blast (deferred)") + elif self._power and self._poe_port: + # Power-cycle mode with 0x20→0xAA handshake: flood 0xAA 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) ) - - if on_progress: - on_progress(ProgressEvent( - stage=Stage.POWER_CYCLE, bytes_sent=1, bytes_total=1, - message="Power cycle complete", - )) - handshake = await handshake_task else: # Manual power cycling — just start handshake and wait