From 04ab5f335fb6461299f18f093f8075f6ceca3533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 30 Oct 2023 15:37:12 +0100 Subject: [PATCH 01/24] [gui] Automatically calculate write length, check write offsets --- ltchiptool/gui/ltchiptool.wxui | 7 ++--- ltchiptool/gui/ltchiptool.xrc | 7 ++--- ltchiptool/gui/panels/flash.py | 52 +++++++++++++++++++--------------- ltchiptool/gui/work/flash.py | 12 ++++---- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/ltchiptool/gui/ltchiptool.wxui b/ltchiptool/gui/ltchiptool.wxui index a56643a..df9569e 100644 --- a/ltchiptool/gui/ltchiptool.wxui +++ b/ltchiptool/gui/ltchiptool.wxui @@ -329,7 +329,6 @@ class="wxComboBox" style="wxCB_READONLY" var_name="combo_family" - disabled="1" borders="wxBOTTOM|wxRIGHT|wxLEFT" colspan="3" flags="wxEXPAND" @@ -374,16 +373,16 @@ row="2" /> 5 - 0 @@ -426,21 +425,21 @@ wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND 5 - 0 + 0x0 wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND 5 - 0 + 0x0 wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND 5 - 0 + All data diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index d2d4690..4d2ce53 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -112,7 +112,7 @@ def SetSettings( file: str = None, offset: int = None, skip: int = None, - length: int = None, + length: int | None = None, prev_file: str = None, auto_file: str = None, **_, @@ -190,7 +190,7 @@ def OnUpdate(self, target: wx.Window = None): if not need_offset: self.offset = self.detection.offset self.skip = self.detection.skip - self.length = self.detection.length + self.length = None if is_uf2 else self.detection.length match self.detection.type: case Detection.Type.UNRECOGNIZED if auto: @@ -211,6 +211,20 @@ def OnUpdate(self, target: wx.Window = None): if manual: warnings.append("Warning: using custom options") + if self.skip >= self.detection.length: + errors.append( + f"Skip offset (0x{self.skip:X}) " + f"not within input file bounds " + f"(0x{self.detection.length:X})" + ) + elif self.skip + (self.length or 0) > self.detection.length: + errors.append( + f"Writing length (0x{self.skip:X} + 0x{self.length:X}) " + f"not within input file bounds " + f"(0x{self.detection.length:X})" + ) + errors.append("") + else: self.FileText.SetLabel("Output file") self.LengthText.SetLabel("Reading length") @@ -225,20 +239,15 @@ def OnUpdate(self, target: wx.Window = None): warnings.append("Using manual parameters") verbose( - f"Update: read_full={self.read_full}, " + f"Update: " f"target={type(target).__name__}, " f"port={self.port}, " f"family={self.family}", ) - if self.prev_read_full is not self.read_full: - # switching "entire chip" reading - update input text - self.length = self.length or 0x200000 - self.prev_read_full = self.read_full - if not self.family: errors.append("Choose the chip family") - if not self.length and not self.read_full: + if self.length == 0: errors.append("Enter a correct length") if not self.port: errors.append("Choose a serial port") @@ -369,44 +378,41 @@ def file(self, value: str | None): self.DoUpdate(self.File) @property - def offset(self): + def offset(self) -> int: text: str = self.Offset.GetValue().strip() or "0" value = int_or_zero(text) return value @offset.setter - def offset(self, value: int): + def offset(self, value: int) -> None: value = value or 0 self.Offset.SetValue(f"0x{value:X}") @property - def skip(self): + def skip(self) -> int: text: str = self.Skip.GetValue().strip() or "0" value = int_or_zero(text) return value @skip.setter - def skip(self, value: int): + def skip(self, value: int) -> None: value = value or 0 self.Skip.SetValue(f"0x{value:X}") @property - def length(self): - text: str = self.Length.GetValue().strip() or "0" + def length(self) -> int | None: + text: str = self.Length.GetValue().strip() + if not text: + return None value = int_or_zero(text) return value @length.setter def length(self, value: int | None): - value = value or 0 - if self.read_full: - self.Length.SetValue("Entire chip") - else: + if value: self.Length.SetValue(f"0x{value:X}") - - @property - def read_full(self): - return self.length == 0 and self.auto_detect and self.operation != FlashOp.WRITE + else: + self.Length.SetValue("") def make_dump_filename(self): if not self.file: diff --git a/ltchiptool/gui/work/flash.py b/ltchiptool/gui/work/flash.py index a766959..5b28f2b 100644 --- a/ltchiptool/gui/work/flash.py +++ b/ltchiptool/gui/work/flash.py @@ -62,9 +62,8 @@ def run_impl(self): def stop(self): super().stop() - if self.ctx: - # try to break UF2 flashing - self.soc.flash_disconnect() + # try to break flashing & cleanup + self.soc.flash_disconnect() def _link(self): self.soc.flash_set_connection(FlashConnection(self.port, self.baudrate)) @@ -114,7 +113,7 @@ def _do_write(self): return file = open(self.file, "rb") - size = stat(self.file).st_size + file_size = stat(self.file).st_size _read = file.read def read(n: int = -1) -> bytes | None: @@ -124,7 +123,8 @@ def read(n: int = -1) -> bytes | None: file.read = read - if self.skip + self.length > size: + self.length = self.length or max(file_size - self.skip, 0) + if self.skip + self.length > file_size: raise ValueError(f"File is too small (requested to write too much data)") max_length = self.soc.flash_get_size() @@ -156,7 +156,7 @@ def _do_read(self): else: max_length = self.soc.flash_get_size() - self.length = self.length or (max_length - self.offset) + self.length = self.length or max(max_length - self.offset, 0) if self.offset + self.length > max_length: raise ValueError( From d0d8e933e62cca4c05985c41ae64702781bc4067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 30 Oct 2023 15:39:54 +0100 Subject: [PATCH 02/24] [ambz2] Fix serial timeout counting, fix dumping received ASCII data --- ltchiptool/soc/ambz2/flash.py | 18 +++++++++------ ltchiptool/soc/ambz2/util/ambz2tool.py | 31 +++++++++++++++----------- ltchiptool/soc/interface.py | 2 +- ltchiptool/util/streams.py | 7 ++++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index dee27df..b0c0e54 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -107,13 +107,17 @@ def flash_write_raw( self.flash_connect() assert self.amb callback.attach(data, limit=length) - self.amb.memory_write( - offset=offset, - stream=data, - use_flash=True, - hash_check=verify, - ) - callback.detach(data) + try: + self.amb.memory_write( + offset=offset, + stream=data, + use_flash=True, + hash_check=verify, + ) + callback.detach(data) + except Exception as e: + callback.detach(data) + raise e def flash_write_uf2( self, diff --git a/ltchiptool/soc/ambz2/util/ambz2tool.py b/ltchiptool/soc/ambz2/util/ambz2tool.py index 386a8b2..2c91bc5 100644 --- a/ltchiptool/soc/ambz2/util/ambz2tool.py +++ b/ltchiptool/soc/ambz2/util/ambz2tool.py @@ -13,7 +13,7 @@ from xmodem import XMODEM from ltchiptool.util.intbin import align_down -from ltchiptool.util.logging import LoggingHandler, verbose +from ltchiptool.util.logging import LoggingHandler, stream, verbose from ltchiptool.util.misc import retry_catching, retry_generator _T_XmodemCB = Optional[Callable[[int, int, int], None]] @@ -34,7 +34,7 @@ class AmbZ2FlashSpeed(IntEnum): class AmbZ2Tool: - crc_speed_bps: int = 2000000 + crc_speed_bps: int = 1500000 prev_timeout_list: List[float] flash_mode: AmbZ2FlashMode = None flash_speed: AmbZ2FlashSpeed = AmbZ2FlashSpeed.SINGLE @@ -83,16 +83,16 @@ def command(self, cmd: str) -> None: def read(self, count: int = None) -> bytes: response = b"" end = time() + self.read_timeout - end_nb = time() + 0.01 # not before while time() < end: - read = self.s.read_all() + if count: + read = self.s.read(count - len(response)) + else: + read = self.s.read_all() response += read if count and len(response) >= count: break - if not response or time() <= end_nb: - continue - if not read: - break + if read: + end = time() + self.read_timeout if not response: raise TimeoutError(f"Timeout in read({count}) - no data received") @@ -100,7 +100,9 @@ def read(self, count: int = None) -> bytes: return response response = response[:count] if len(response) != count: - raise TimeoutError(f"Timeout in read({count}) - not enough data received") + raise TimeoutError( + f"Timeout in read({count}) - not enough data received ({len(response)})" + ) return response def readlines(self) -> Generator[str, None, None]: @@ -154,7 +156,7 @@ def ping(self) -> None: self.command("ping") resp = self.read(4) if resp != b"ping": - raise RuntimeError(f"incorrect ping response: {resp!r}") + raise RuntimeError(f"Incorrect ping response: {resp!r}") def disconnect(self) -> None: self.command("disc") @@ -175,13 +177,16 @@ def change_baudrate(self, baudrate: int) -> None: self.ping() self.command(f"ucfg {baudrate} 0 0") # change Serial port baudrate - debug("-- UART: Changing port baudrate") + stream("-- UART: Changing port baudrate") self.s.baudrate = baudrate # wait up to 1 second for OK response self.push_timeout(1.0) - resp = self.read() + try: + resp = self.read() + except TimeoutError: + raise RuntimeError("Timed out while changing baud rate") if resp != b"OK": - raise RuntimeError(f"baud rate change not OK: {resp!r}") + raise RuntimeError(f"Baud rate change not OK: {resp!r}") self.pop_timeout() # link again to make sure it still works diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 56b00ce..14a0ec1 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -141,7 +141,7 @@ def flash_get_chip_info_string(self) -> str: def flash_get_guide(self) -> List[Union[str, list]]: """Get a short textual guide for putting the chip in download mode.""" - raise NotImplementedError() + return [] # Optional; do not fail here def flash_get_size(self) -> int: """Retrieve the flash size, in bytes.""" diff --git a/ltchiptool/util/streams.py b/ltchiptool/util/streams.py index 0772bb9..328d9c2 100644 --- a/ltchiptool/util/streams.py +++ b/ltchiptool/util/streams.py @@ -110,7 +110,7 @@ def __init__(self): self.buf = {"-> RX": "", "<- TX": ""} def _print(self, data: bytes, msg: str): - if all(c in self.ASCII for c in data): + if data and all(c in self.ASCII for c in data): data = data.decode().replace("\r", "") while "\n" in data: line, _, data = data.partition("\n") @@ -118,12 +118,14 @@ def _print(self, data: bytes, msg: str): self.buf[msg] = "" if line: stream(f"{msg}: '{line}'") - self.buf[msg] = data + self.buf[msg] += data return if self.buf[msg]: stream(f"{msg}: '{self.buf[msg]}'") self.buf[msg] = "" + if not data: + return if data.isascii(): stream(f"{msg}: {data[0:128]}") @@ -138,6 +140,7 @@ def on_after_read(self, data: bytes) -> Optional[bytes]: return None def on_before_write(self, data: bytes) -> Optional[bytes]: + self._print(b"", "-> RX") # print leftover bytes self._print(data, "<- TX") return None From 7fdf451c4f2ab704ef7d4e960389b84dbc521ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 30 Oct 2023 16:44:42 +0100 Subject: [PATCH 03/24] [ambz2] Improve dumping buffer sizes, fix changing baudrate --- ltchiptool/soc/ambz2/util/ambz2tool.py | 38 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/ltchiptool/soc/ambz2/util/ambz2tool.py b/ltchiptool/soc/ambz2/util/ambz2tool.py index 2c91bc5..1e7e9c9 100644 --- a/ltchiptool/soc/ambz2/util/ambz2tool.py +++ b/ltchiptool/soc/ambz2/util/ambz2tool.py @@ -83,16 +83,20 @@ def command(self, cmd: str) -> None: def read(self, count: int = None) -> bytes: response = b"" end = time() + self.read_timeout + self.s.timeout = self.read_timeout while time() < end: + to_read = self.s.in_waiting + if not to_read: + continue if count: - read = self.s.read(count - len(response)) - else: - read = self.s.read_all() + to_read = min(to_read, count - len(response)) + read = self.s.read(to_read) + if not read: + continue + end = time() + self.read_timeout response += read if count and len(response) >= count: break - if read: - end = time() + self.read_timeout if not response: raise TimeoutError(f"Timeout in read({count}) - no data received") @@ -139,11 +143,9 @@ def pop_timeout(self) -> None: def error_flush(self) -> None: # try to clean up the serial buffer - # no data for 0.8 s means that the chip stopped sending bytes - self.push_timeout(0.8) - while True: - if not self.s.read(128): - break + # no data for 0.5 s means that the chip stopped sending bytes + self.push_timeout(0.5) + self.read() # read all available data self.pop_timeout() # pop timeout of the failing function self.pop_timeout() @@ -178,7 +180,9 @@ def change_baudrate(self, baudrate: int) -> None: self.command(f"ucfg {baudrate} 0 0") # change Serial port baudrate stream("-- UART: Changing port baudrate") + self.s.close() self.s.baudrate = baudrate + self.s.open() # wait up to 1 second for OK response self.push_timeout(1.0) try: @@ -197,7 +201,10 @@ def dump_words(self, start: int, count: int) -> Generator[List[int], None, None] self.push_timeout(max(min(count, 256), 16) * 1.5 / 500.0) # one line is 57 chars long, and it holds 4 words # make it 32 KiB at most - self.s.set_buffer_size(rx_size=min(32768, 57 * (count // 4))) + try: + self.s.set_buffer_size(rx_size=min(32768, 57 * (count // 4))) + except AttributeError: + pass read_count = 0 self.flush() @@ -227,7 +234,10 @@ def dump_bytes(self, start: int, count: int) -> Generator[bytes, None, None]: self.push_timeout(max(min(count, 1024), 64) * 0.5 / 500.0) # one line is 78 chars long, and it holds 16 bytes # make it 32 KiB at most - self.s.set_buffer_size(rx_size=min(32768, 78 * (count // 16))) + try: + self.s.set_buffer_size(rx_size=min(32768, 78 * (count // 16))) + except AttributeError: + pass read_count = 0 self.flush() @@ -403,6 +413,10 @@ def memory_read( # can only check SHA of flash hash_check = False + # determine a reliable maximum chunk size + baud_coef = int(1 / self.s.baudrate**0.5 * 2000) + chunk_size = min(2**baud_coef * 1024, chunk_size) + chunk = b"" sha = sha256() sha_size = 0 From 3363c2c2c602d2019e0daaffb4010afbd8115f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Nov 2023 17:41:29 +0100 Subject: [PATCH 04/24] [core] Allow attaching multiple stream hooks --- ltchiptool/util/streams.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/ltchiptool/util/streams.py b/ltchiptool/util/streams.py index 328d9c2..8fafe76 100644 --- a/ltchiptool/util/streams.py +++ b/ltchiptool/util/streams.py @@ -12,11 +12,18 @@ class StreamHook(ABC): + read_key: str + write_key: str + + def __init__(self) -> None: + self.read_key = f"_read_{id(self)}" + self.write_key = f"_write_{id(self)}" + def read(self, io: IO[bytes], n: int) -> bytes: - return getattr(io, "_read")(n) + return getattr(io, self.read_key)(n) def write(self, io: IO[bytes], data: bytes) -> int: - return getattr(io, "_write")(data) + return getattr(io, self.write_key)(data) def on_after_read(self, data: bytes) -> Optional[bytes]: return data @@ -25,13 +32,13 @@ def on_before_write(self, data: bytes) -> Optional[bytes]: return data def attach(self, io: IO[bytes], limit: int = 0) -> IO[bytes]: - if hasattr(io, "_read"): + if hasattr(io, self.read_key): return io - setattr(io, "_read", io.read) - setattr(io, "_write", io.write) + setattr(io, self.read_key, io.read) + setattr(io, self.write_key, io.write) try: end = io.tell() + limit - except UnsupportedOperation: + except (UnsupportedOperation, AttributeError): limit = 0 def read(n: int = -1) -> bytes: @@ -57,16 +64,15 @@ def write(data: bytes) -> int: setattr(io, "write", write) return io - @staticmethod - def detach(io: IO[bytes]) -> IO[bytes]: - read = getattr(io, "_read", None) - write = getattr(io, "_read", None) + def detach(self, io: IO[bytes]) -> IO[bytes]: + read = getattr(io, self.read_key, None) + write = getattr(io, self.write_key, None) if read is not None: setattr(io, "read", read) - delattr(io, "_read") + delattr(io, self.read_key) if write is not None: setattr(io, "write", write) - delattr(io, "_write") + delattr(io, self.write_key) return io @classmethod @@ -107,6 +113,7 @@ class LoggingStreamHook(StreamHook): buf: dict def __init__(self): + super().__init__() self.buf = {"-> RX": "", "<- TX": ""} def _print(self, data: bytes, msg: str): @@ -175,6 +182,7 @@ def on_after_read(self, data: bytes) -> Optional[bytes]: class ClickProgressCallback(ProgressCallback): def __init__(self, length: int = 0, width: int = 64): + super().__init__() self.bar = click.progressbar(length=length, width=width) def on_update(self, steps: int) -> None: From c334a40684b0ed6678012e4eb23a3c8dba2ddd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Nov 2023 17:42:04 +0100 Subject: [PATCH 05/24] [core] Extract base flasher class --- ltchiptool/soc/ambz2/util/ambz2tool.py | 99 ++------------------ ltchiptool/util/serialtool.py | 121 +++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 93 deletions(-) create mode 100644 ltchiptool/util/serialtool.py diff --git a/ltchiptool/soc/ambz2/util/ambz2tool.py b/ltchiptool/soc/ambz2/util/ambz2tool.py index 1e7e9c9..a032412 100644 --- a/ltchiptool/soc/ambz2/util/ambz2tool.py +++ b/ltchiptool/soc/ambz2/util/ambz2tool.py @@ -9,12 +9,12 @@ from typing import IO, Callable, Generator, List, Optional import click -from serial import Serial from xmodem import XMODEM from ltchiptool.util.intbin import align_down from ltchiptool.util.logging import LoggingHandler, stream, verbose from ltchiptool.util.misc import retry_catching, retry_generator +from ltchiptool.util.serialtool import SerialToolBase _T_XmodemCB = Optional[Callable[[int, int, int], None]] @@ -33,9 +33,8 @@ class AmbZ2FlashSpeed(IntEnum): QPI = 5 # QPI -class AmbZ2Tool: +class AmbZ2Tool(SerialToolBase): crc_speed_bps: int = 1500000 - prev_timeout_list: List[float] flash_mode: AmbZ2FlashMode = None flash_speed: AmbZ2FlashSpeed = AmbZ2FlashSpeed.SINGLE flash_hash_offset: int = None @@ -48,13 +47,8 @@ def __init__( read_timeout: float = 0.6, retry_count: int = 10, ): - self.prev_timeout_list = [] - self.link_timeout = link_timeout - self.read_timeout = read_timeout - self.retry_count = retry_count - + super().__init__(port, baudrate, link_timeout, read_timeout, retry_count) LoggingHandler.get().attach(logging.getLogger("xmodem.XMODEM")) - self.s = Serial(port, baudrate) self.xm = XMODEM( getc=lambda size, timeout=1: self.read(size) or None, putc=lambda data, timeout=1: self.write(data), @@ -65,95 +59,14 @@ def __init__( def flash_cfg(self) -> str: return f"{self.flash_speed} {self.flash_mode}" - ################################# - # Serial transmission utilities # - ################################# - - def close(self) -> None: - self.s.close() - self.s = None - - def write(self, data: bytes) -> None: - self.s.write(data) + ######################################### + # Basic commands - public low-level API # + ######################################### def command(self, cmd: str) -> None: self.flush() self.s.write(cmd.encode() + b"\n") - def read(self, count: int = None) -> bytes: - response = b"" - end = time() + self.read_timeout - self.s.timeout = self.read_timeout - while time() < end: - to_read = self.s.in_waiting - if not to_read: - continue - if count: - to_read = min(to_read, count - len(response)) - read = self.s.read(to_read) - if not read: - continue - end = time() + self.read_timeout - response += read - if count and len(response) >= count: - break - - if not response: - raise TimeoutError(f"Timeout in read({count}) - no data received") - if not count: - return response - response = response[:count] - if len(response) != count: - raise TimeoutError( - f"Timeout in read({count}) - not enough data received ({len(response)})" - ) - return response - - def readlines(self) -> Generator[str, None, None]: - response = b"" - end = time() + self.read_timeout - self.s.timeout = self.read_timeout - while time() < end: - read = self.s.read_all() - if not read: - continue - end = time() + self.read_timeout - while b"\n" in read: - line, _, read = read.partition(b"\n") - line = (response + line).decode().strip() - if not line: - continue - yield line - response = b"" - response += read - raise TimeoutError("Timeout in readlines() - no more data received") - - def flush(self) -> None: - self.s.read_all() - self.s.flush() - - def push_timeout(self, timeout: float) -> None: - verbose(f"push_timeout({timeout})") - self.prev_timeout_list.append(self.read_timeout) - self.read_timeout = timeout - - def pop_timeout(self) -> None: - verbose("pop_timeout()") - self.read_timeout = self.prev_timeout_list.pop(-1) - - def error_flush(self) -> None: - # try to clean up the serial buffer - # no data for 0.5 s means that the chip stopped sending bytes - self.push_timeout(0.5) - self.read() # read all available data - self.pop_timeout() - # pop timeout of the failing function - self.pop_timeout() - - ######################################### - # Basic commands - public low-level API # - ######################################### - def ping(self) -> None: self.command("ping") resp = self.read(4) diff --git a/ltchiptool/util/serialtool.py b/ltchiptool/util/serialtool.py new file mode 100644 index 0000000..d791fe6 --- /dev/null +++ b/ltchiptool/util/serialtool.py @@ -0,0 +1,121 @@ +# Copyright (c) Kuba Szczodrzyński 2023-10-30. + +from time import time +from typing import Generator, List + +from serial import Serial + +from .logging import verbose + + +class SerialToolBase: + prev_timeout_list: List[float] + + def __init__( + self, + port: str, + baudrate: int, + link_timeout: float = 10.0, + read_timeout: float = 0.5, + retry_count: int = 10, + ): + self.prev_timeout_list = [] + self.link_timeout = link_timeout + self.read_timeout = read_timeout + self.retry_count = retry_count + + self.s = Serial(port, baudrate) + + ################################# + # Serial transmission utilities # + ################################# + + def close(self) -> None: + self.s.close() + self.s = None + + def set_baudrate(self, baudrate: int) -> None: + self.s.close() + self.s.baudrate = baudrate + self.s.open() + + def write(self, data: bytes) -> None: + self.s.write(data) + + def read(self, count: int = None, max_count: int = None) -> bytes: + if max_count is not None: + count = max_count + response = b"" + end = time() + self.read_timeout + self.s.timeout = self.read_timeout + while time() < end: + to_read = self.s.in_waiting + if not to_read: + continue + if count: + to_read = min(to_read, count - len(response)) + read = self.s.read(to_read) + if not read: + continue + end = time() + self.read_timeout + response += read + if count and len(response) >= count: + break + + if not response: + raise TimeoutError(f"Timeout in read({count}) - no data received") + if not count: + return response + response = response[:count] + if max_count is None and len(response) != count: + raise TimeoutError( + f"Timeout in read({count}) - not enough data received ({len(response)})" + ) + return response + + def readlines(self) -> Generator[str, None, None]: + response = b"" + end = time() + self.read_timeout + self.s.timeout = self.read_timeout + while time() < end: + read = self.s.read_all() + if not read: + continue + end = time() + self.read_timeout + while b"\n" in read: + line, _, read = read.partition(b"\n") + line = (response + line).decode().strip() + if not line: + continue + yield line + response = b"" + response += read + raise TimeoutError("Timeout in readlines() - no more data received") + + def flush(self) -> None: + self.s.read_all() + self.s.flush() + + def push_timeout(self, timeout: float) -> None: + verbose(f"push_timeout({timeout})") + self.prev_timeout_list.append(self.read_timeout) + self.read_timeout = timeout + + def pop_timeout(self) -> None: + verbose("pop_timeout()") + self.read_timeout = self.prev_timeout_list.pop(-1) + + def timed_flush(self, timeout: float = 0.5) -> None: + # try to clean up the serial buffer + # no data for 0.5 s means that the chip stopped sending bytes + self.push_timeout(timeout) + try: + self.read() # read all available data + except TimeoutError: + pass + self.pop_timeout() + + def error_flush(self) -> None: + self.timed_flush() + # pop timeout of the failing function + self.pop_timeout() From 29359201dc13b9b2338d5b23155f6dae306d9418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Nov 2023 19:11:40 +0100 Subject: [PATCH 06/24] [ambz] Add new flashing tool --- ltchiptool/commands/soc.py | 1 + ltchiptool/soc/ambz/util/ambzcode.py | 187 ++++++++++ ltchiptool/soc/ambz/util/ambztool.py | 475 +++++++++++++++++++++++++ ltchiptool/soc/ambz2/util/ambz2tool.py | 7 +- ltchiptool/util/serialtool.py | 1 + 5 files changed, 666 insertions(+), 5 deletions(-) create mode 100644 ltchiptool/soc/ambz/util/ambzcode.py create mode 100644 ltchiptool/soc/ambz/util/ambztool.py diff --git a/ltchiptool/commands/soc.py b/ltchiptool/commands/soc.py index f70a667..5ef40d5 100644 --- a/ltchiptool/commands/soc.py +++ b/ltchiptool/commands/soc.py @@ -7,6 +7,7 @@ COMMANDS = { "bkpackager": "ltchiptool/soc/bk72xx/bkpackager.py", "rtltool": "ltchiptool/soc/ambz/util/rtltool.py", + "ambztool": "ltchiptool/soc/ambz/util/ambztool.py", "ambz2tool": "ltchiptool/soc/ambz2/util/ambz2tool.py", } diff --git a/ltchiptool/soc/ambz/util/ambzcode.py b/ltchiptool/soc/ambz/util/ambzcode.py new file mode 100644 index 0000000..ae7f6ca --- /dev/null +++ b/ltchiptool/soc/ambz/util/ambzcode.py @@ -0,0 +1,187 @@ +# Copyright (c) Kuba Szczodrzyński 2023-11-2. + +from ltchiptool.util.intbin import inttole32 + +AMBZ_DATA_ADDRESS = 0x10003000 + + +class AmbZCode: + @staticmethod + def rom_console() -> bytes: + # push {r3, lr} + # ldr r4, RtlConsolRom + # mov.w r0, #1000 + # blx r4 + # b #4 + # RtlConsolRom: .word 0x2204+1 + return ( + b"\x08\xb5\x02\x4c" + b"\x4f\xf4\x7a\x70" + b"\xa0\x47\xfb\xe7" + b"\x05\x22\x00\x00" # RtlConsolRom() + ) + + @staticmethod + def download_mode() -> bytes: + # movs r0, #2 + # ldr r3, UARTIMG_Download + # blx r3 + # movs r0, r0 + # UARTIMG_Download: .word 0x900+1 + return ( + b"\x02\x20\x01\x4b" + b"\x98\x47\x00\x00" + b"\x01\x09\x00\x00" # UARTIMG_Download() + ) + + @staticmethod + def uart_set_baud_rate(baudrate: int) -> bytes: + # ldr r0, XComUARTx + # ldr r0, [r0] + # ldr r3, UART_WaitBusy + # movs r1, #10 + # blx r3 + # ldr r0, XComUARTx + # ldr r0, [r0] + # ldr r1, baud_rate + # ldr r3, UART_SetBaud + # blx r3 + # b next + # movs r0, r0 + # XComUARTx: .word 0x10000FC4 + # UART_WaitBusy: .word 0xC1F8+1 + # UART_SetBaud: .word 0xBF00+1 + # baud_rate: .word 115200 + # next: + return ( + b"\x05\x48\x00\x68" + b"\x05\x4b\x0a\x21" + b"\x98\x47\x03\x48" + b"\x00\x68\x05\x49" + b"\x03\x4b\x98\x47" + b"\x08\xe0\x00\x00" + b"\xc4\x0f\x00\x10" # XComUARTx + b"\xf9\xc1\x00\x00" # UART_WaitBusy() + b"\x01\xbf\x00\x00" # UART_SetBaud() + ) + inttole32(baudrate) + + @staticmethod + def read_flash_id(offset: int = 0) -> bytes: + # movs r0, #0x9F + # movs r1, #3 + # ldr r2, read_data + # ldr r3, FLASH_RxCmd + # blx r3 + # b next + # FLASH_RxCmd: .word 0x7464+1 + # read_data: .word 0x10003000 + # next: + return ( + b"\x9f\x20\x03\x21" + b"\x02\x4a\x01\x4b" + b"\x98\x47\x03\xe0" + b"\x65\x74\x00\x00" # FLASH_RxCmd() + ) + inttole32(AMBZ_DATA_ADDRESS + offset) + + @staticmethod + def read_chip_id(offset: int = 0) -> bytes: + # ldr r0, CtrlSetting + # movs r1, #0xF8 + # ldr r2, read_data + # movs r3, #7 + # ldr r4, EFUSE_OneByteReadROM + # blx r4 + # b next + # movs r0, r0 + # CtrlSetting: .word 9902 + # EFUSE_OneByteReadROM: .word 0x6D64+1 + # read_data: .word 0x10003000 + # next: + return ( + b"\x03\x48\xf8\x21" + b"\x04\x4a\x07\x23" + b"\x02\x4c\xa0\x47" + b"\x06\xe0\x00\x00" + b"\xae\x26\x00\x00" # CtrlSetting + b"\x65\x6d\x00\x00" # EFUSE_OneByteReadROM() + ) + inttole32(AMBZ_DATA_ADDRESS + offset) + + @staticmethod + def read_efuse_logical_map(offset: int = 0) -> bytes: + # ldr r0, read_data + # ldr r3, EFUSE_LogicalMap_Read + # blx r3 + # b next + # EFUSE_LogicalMap_Read: .word 0x7090+1 + # read_data: .word 0x10003000 + # next: + return ( + b"\x02\x48\x01\x4b" + b"\x98\x47\x03\xe0" + b"\x91\x70\x00\x00" # EFUSE_LogicalMap_Read() + ) + inttole32(AMBZ_DATA_ADDRESS + offset) + + @staticmethod + def read_data_md5(address: int, length: int, offset: int = 0) -> bytes: + # ldr r0, context + # ldr r3, rt_md5_init + # blx r3 + # ldr r0, context + # ldr r1, input + # ldr r2, length + # ldr r3, rt_md5_append + # blx r3 + # ldr r0, digest + # ldr r1, context + # ldr r3, rt_md5_final + # blx r3 + # b next + # movs r0, r0 + # rt_md5_init: .word 0x11DF4+1 + # rt_md5_append: .word 0x11E24+1 + # rt_md5_final: .word 0x11EC8+1 + # input: .word 0 + # length: .word 0 + # digest: .word 0 + # context: .word 0 + # next: + return ( + ( + b"\x0c\x48\x06\x4b" + b"\x98\x47\x0b\x48" + b"\x07\x49\x08\x4a" + b"\x04\x4b\x98\x47" + b"\x07\x48\x08\x49" + b"\x03\x4b\x98\x47" + b"\x0e\xe0\x00\x00" + b"\xf5\x1d\x01\x00" # rt_md5_init() + b"\x25\x1e\x01\x00" # rt_md5_append() + b"\xc9\x1e\x01\x00" # rt_md5_final() + ) + + inttole32(address) + + inttole32(length) + + inttole32(AMBZ_DATA_ADDRESS + offset) + + inttole32(AMBZ_DATA_ADDRESS + offset + 16) + ) + + @staticmethod + def print_data(length: int, address: int = AMBZ_DATA_ADDRESS) -> bytes: + # ldr r0, read_data + # movs r1, #length + # ldr r3, xmodem_uart_putdata + # blx r3 + # b next + # movs r0, r0 + # xmodem_uart_putdata: .word 0xEC48+1 + # read_data: .word 0x10003000 + # next: + return ( + ( + b"\x03\x48\x04\x49" + b"\x01\x4b\x98\x47" + b"\x06\xe0\x00\x00" + b"\x49\xec\x00\x00" # xmodem_uart_putdata() + ) + + inttole32(address) + + inttole32(length) + ) diff --git a/ltchiptool/soc/ambz/util/ambztool.py b/ltchiptool/soc/ambz/util/ambztool.py new file mode 100644 index 0000000..b49d024 --- /dev/null +++ b/ltchiptool/soc/ambz/util/ambztool.py @@ -0,0 +1,475 @@ +# Copyright (c) Kuba Szczodrzyński 2023-10-30. + +import logging +import struct +from enum import IntEnum +from hashlib import md5 +from io import BytesIO +from logging import debug, warning +from time import sleep, time +from typing import IO, Callable, Generator, Optional + +import click +from xmodem import XMODEM + +from ltchiptool.util.intbin import align_down, align_up, inttole16, inttole24, inttole32 +from ltchiptool.util.logging import LoggingHandler, verbose +from ltchiptool.util.misc import retry_generator +from ltchiptool.util.serialtool import SerialToolBase +from ltchiptool.util.streams import StreamHook + +from .ambzcode import AmbZCode + +_T_XmodemCB = Optional[Callable[[int, int, int], None]] + +ACK = b"\x06" +NAK = b"\x15" + +AMBZ_ROM_BAUDRATE = 1500000 +AMBZ_DIAG_BAUDRATE = 115200 +AMBZ_FLASH_ADDRESS = 0x8000000 +AMBZ_RAM_ADDRESS = 0x10002000 + +AMBZ_CHIP_TYPE = { + 0xE0: "RTL8710BL", # ??? + 0xFF: "RTL8710BN", # CHIPID_8710BN / QFN32 + 0xFE: "RTL8710BU", # CHIPID_8710BU / QFN48 + 0xF6: "RTL8710BX", # found on an actual RTL8710BX + 0xFB: "RTL8710L0", # CHIPID_8710BN_L0 / QFN32 + 0xFD: "RTL8711BN", # CHIPID_8711BN / QFN48 + 0xFC: "RTL8711BU", # CHIPID_8711BG / QFN68 +} + +AMBZ_BAUDRATE_TABLE = [ + 110, + 300, + 600, + 1200, + 2400, + 4800, + 9600, + 14400, + 19200, + 28800, + 38400, + 57600, + 76800, + 115200, + 128000, + 153600, + 230400, + 380400, + 460800, + 500000, + 921600, + 1000000, + 1382400, + 1444400, + 1500000, + 1843200, + 2000000, + 2100000, + 2764800, + 3000000, + 3250000, + 3692300, + 3750000, + 4000000, + 6000000, +] + + +class AmbZCommand(IntEnum): + SET_BAUD_RATE = 0x05 + FLASH_ERASE = 0x17 + FLASH_READ = 0x19 + FLASH_GET_STATUS = 0x21 + FLASH_SET_STATUS = 0x26 + XMODEM_HANDSHAKE = 0x07 + XMODEM_CAN = 0x18 + + +class AmbZAddressHook(StreamHook): + def __init__(self, address: int): + super().__init__() + self.address = address + + def read(self, io: IO[bytes], n: int) -> bytes: + # read a data packet + data = super().read(io, n) + if not data: + return b"" + # prepend address bytes + data = inttole32(self.address) + data + # increment saved address + self.address += n + # add padding to force sending N+4 packet size + data = data.ljust(n + 4, b"\xFF") + return data + + +class AmbZTool(SerialToolBase): + crc_speed_bps: int = 1500000 + xm_send_code: Optional[bytes] = None + xm_fake_ack: bool = False + + def __init__( + self, + port: str, + baudrate: int, + link_timeout: float = 10.0, + read_timeout: float = 0.6, + retry_count: int = 10, + ): + super().__init__(port, baudrate, link_timeout, read_timeout, retry_count) + LoggingHandler.get().attach(logging.getLogger("xmodem.XMODEM")) + self.xm = XMODEM( + getc=self.xm_getc, + putc=self.xm_putc, + mode="xmodem1k", + ) + + ############################# + # Xmodem serial port access # + ############################# + + def xm_getc(self, size, _=1): + if self.xm_send_code: + code = self.xm_send_code + self.xm_send_code = None + return code + return self.read(size) or None + + def xm_putc(self, data, _=1): + self.write(data) + if self.xm_fake_ack and data == b"\x04": # EOT + self.xm_send_code = ACK + + ######################################### + # Basic commands - public low-level API # + ######################################### + + def link(self, disconnect: bool = False) -> None: + # clear any data before linking + self.flush() + if disconnect: + # break NAK stream to force resetting baudrate + self.quiet_handshake() + handshake = b"" + end = time() + self.link_timeout + while time() < end: + # check if we're in Loud-Handshake mode + try: + handshake += self.read(8) + except TimeoutError: + pass + handshake = handshake[-4:] + if len(handshake) == 4 and all(c == NAK[0] for c in handshake): + break + # try to enter Loud-Handshake mode + command = [ + # - Xmodem -> Handshake + # - Loud-Handshake -> Quiet-Handshake + AmbZCommand.XMODEM_CAN, + # - Handshake -> Xmodem + AmbZCommand.XMODEM_HANDSHAKE, + # - Xmodem -> Loud-Handshake (resets baud rate) + AmbZCommand.XMODEM_CAN, + ] + self.write(bytes(command)) + sleep(0.1) + self.set_baudrate(AMBZ_ROM_BAUDRATE) + else: + raise TimeoutError("Timeout while linking") + + self.loud_handshake() + + def quiet_handshake(self) -> None: + self.flush() + self.push_timeout(0.1) + end = time() + self.link_timeout + while time() < end: + self.write(ACK) + # discard everything from Loud-Handshake + try: + self.read(max_count=4) + except TimeoutError: + break + else: + self.pop_timeout() + raise TimeoutError("Timed out waiting for Quiet-Handshake") + self.pop_timeout() + + def loud_handshake(self) -> None: + self.flush() + self.write(struct.pack(" None: + if self.s.baudrate == baudrate: + return + self.flush() + self.write( + struct.pack( + " None: + resp = self.read(1) + if resp != ACK: + raise RuntimeError(f"No ACK after {doc}: {resp!r}") + + ####################################### + # Flash-related commands - public API # + ####################################### + + def flash_get_status(self) -> int: + self.write(struct.pack(" None: + self.write(struct.pack(" Generator[bytes, None, None]: + block_size = 1 << 12 + ack_size = 1 << 10 + ack_per_block = block_size // ack_size + offset = align_down(offset, 4) + length = align_up(length, block_size) + + self.loud_handshake() + + digest = md5() + for start in range(offset, offset + length, chunk_size): + count = min(start + chunk_size, offset + length) - start + debug(f"Dumping bytes: start=0x{start:X}, count=0x{count:X}") + + def dump(): + nonlocal start, count + + verbose(f"<- FLASH_READ(0x{start:X}, {count})") + # make sure there's no NAK in flash readout + self.quiet_handshake() + self.write( + struct.pack( + "> 12), # in 4096-byte blocks + ) + ) + + blocks_left = count // block_size + for block in range(blocks_left): + data = b"" + for ack in range(ack_per_block): + pos = f"block={block}/{blocks_left}, ack={ack}/{ack_per_block}" + verbose(f"-> READ({pos})") + data += self.read(ack_size) + verbose(f"<- ACK({pos})") + self.write(ACK) + yield data + if hash_check: + digest.update(data) + # increment offset and length for subsequent error retries + start += len(data) + count -= len(data) + # force Quiet-Handshake mode + self.write(ACK) + + def dump_error(): + # try to skip all chunks + blocks_left = count // block_size + warning( + f"Dumping failed at 0x{start:X}, " + f"discarding {blocks_left} blocks..." + ) + self.write(ACK * (blocks_left + 1)) + + yield from retry_generator( + retries=self.retry_count, + doc=f"Data read error at 0x{start:X}", + func=dump, + onerror=dump_error, + ) + + self.loud_handshake() + + if hash_check: + debug(f"Final hash check: start=0x{offset:X}, count=0x{length:X}") + hash_final = digest.digest() + hash_expected = self.ram_boot_read( + AmbZCode.read_data_md5( + address=AMBZ_FLASH_ADDRESS | offset, + length=length, + ) + + AmbZCode.print_data(16) + ) + if hash_final != hash_expected: + raise ValueError( + f"Chip MD5 value does not match calculated " + f"value (at 0x{offset:X}+0x{length:X}). Expected: " + f"{hash_expected.hex()}, calculated: {hash_final.hex()}" + ) + + def memory_write( + self, + address: int, + stream: IO[bytes], + callback: _T_XmodemCB = None, + fake_ack: bool = False, + ram_keep_baudrate: bool = False, + ) -> None: + prev_baudrate = self.s.baudrate + self.loud_handshake() + + hook = AmbZAddressHook(address) + hook.attach(stream) + + self.write(bytes([AmbZCommand.XMODEM_HANDSHAKE])) + self.expect_ack("Xmodem handshake") + self.xm.mode = "xmodem" + # fake a NAK to make xmodem happy + self.xm_send_code = NAK + # fake an ACK after EOT to make xmodem very happy + self.xm_fake_ack = fake_ack + + debug(f"XMODEM: transmitting to 0x{address:X}") + self.push_timeout(1.0) + if not self.xm.send(stream, callback=callback): + hook.detach(stream) + self.pop_timeout() + raise RuntimeError("XMODEM transmission failed") + hook.detach(stream) + self.pop_timeout() + + if (address >> 24) == 0x08: + # back to ROM download mode baudrate + self.set_baudrate(AMBZ_ROM_BAUDRATE) + # change it again + self.change_baudrate(prev_baudrate) + # do handshake, as we're still in download mode + self.loud_handshake() + elif (address >> 24) == 0x10 and not ram_keep_baudrate: + # Diag_UART re-enabled (xmodem_uart_port_deinit()) + # no handshake - download mode is over + self.set_baudrate(AMBZ_DIAG_BAUDRATE) + + def ram_boot( + self, + code: bytes = None, + address: int = None, + callback: _T_XmodemCB = None, + keep_baudrate: bool = False, + ) -> None: + ram_start_table = [ + 0x100021EE + 1, + 0x1000219A + 1, + 0x100021EE + 1, + 0x100020F4 + 1, + 0x100021EE + 1, + 0x08000540 + 1, + ] + if (code and address is not None) or (not code and address is None): + raise ValueError("Pass 'code' OR 'address'") + + if code: + ram_start_table[0] = AMBZ_RAM_ADDRESS + len(ram_start_table) * 4 + else: + ram_start_table[0] = address + ram_start_table[0] |= 1 + + data = struct.pack("<" + "I" * len(ram_start_table), *ram_start_table) + if code: + data += code + self.memory_write( + AMBZ_RAM_ADDRESS, + BytesIO(data), + callback, + # fake an ACK after EOT, because it's somehow lost after booting to RAM + fake_ack=True, + ram_keep_baudrate=keep_baudrate, + ) + + def ram_boot_read( + self, + code: bytes, + timeout: float = 5.0, + callback: _T_XmodemCB = None, + ) -> bytes: + # RAM booting prints messages on Diag_UART at 115200 + # set it now to avoid having to switch + prev_baudrate = self.s.baudrate + self.change_baudrate(AMBZ_DIAG_BAUDRATE) + + # go back into download mode after we're done + code = code + AmbZCode.download_mode() + + # messages printed by the ROM + # DiagPrintf changes "\n" to "\n\r" + msg_pre = b"\rclose xModem Transfer ...\r\n\r" + msg_post = b"UARTIMG_Download" + # send RAM code, exit download mode (changes baudrate to 115200) + self.ram_boot(code=code, callback=callback, keep_baudrate=True) + + self.push_timeout(0.1) + resp = b"" + end = time() + timeout + while time() < end: + try: + resp += self.read() + except TimeoutError: + pass + if msg_post in resp: + break + self.pop_timeout() + + if msg_pre in resp: + resp = resp.partition(msg_pre)[2] + elif msg_pre[-7:] in resp: + warning(f"Expected message not found: {resp!r}") + resp = resp.partition(msg_pre[-7:])[2] + else: + raise RuntimeError(f"Response unreadable: {resp!r}") + + if msg_post in resp: + resp = resp.partition(msg_post)[0] + else: + warning(f"Expected message not found: {resp!r}") + + self.set_baudrate(AMBZ_ROM_BAUDRATE) + self.loud_handshake() + if prev_baudrate != AMBZ_ROM_BAUDRATE: + self.change_baudrate(prev_baudrate) + return resp + + +@click.command( + help="AmebaZ flashing tool", +) +def cli(): + raise NotImplementedError() + + +if __name__ == "__main__": + cli() diff --git a/ltchiptool/soc/ambz2/util/ambz2tool.py b/ltchiptool/soc/ambz2/util/ambz2tool.py index a032412..954fd26 100644 --- a/ltchiptool/soc/ambz2/util/ambz2tool.py +++ b/ltchiptool/soc/ambz2/util/ambz2tool.py @@ -12,7 +12,7 @@ from xmodem import XMODEM from ltchiptool.util.intbin import align_down -from ltchiptool.util.logging import LoggingHandler, stream, verbose +from ltchiptool.util.logging import LoggingHandler, verbose from ltchiptool.util.misc import retry_catching, retry_generator from ltchiptool.util.serialtool import SerialToolBase @@ -92,10 +92,7 @@ def change_baudrate(self, baudrate: int) -> None: self.ping() self.command(f"ucfg {baudrate} 0 0") # change Serial port baudrate - stream("-- UART: Changing port baudrate") - self.s.close() - self.s.baudrate = baudrate - self.s.open() + self.set_baudrate(baudrate) # wait up to 1 second for OK response self.push_timeout(1.0) try: diff --git a/ltchiptool/util/serialtool.py b/ltchiptool/util/serialtool.py index d791fe6..309af24 100644 --- a/ltchiptool/util/serialtool.py +++ b/ltchiptool/util/serialtool.py @@ -35,6 +35,7 @@ def close(self) -> None: self.s = None def set_baudrate(self, baudrate: int) -> None: + verbose(f"-- UART: Port baudrate set to {baudrate}") self.s.close() self.s.baudrate = baudrate self.s.open() From c0c9e5cae2224b335c3b1946b98fd94c065cc797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Nov 2023 19:13:40 +0100 Subject: [PATCH 07/24] [gui] Fix forcing auto-detect when choosing UF2 file --- ltchiptool/gui/panels/flash.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index 4d2ce53..e337f43 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -139,11 +139,16 @@ def OnShow(self): def OnUpdate(self, target: wx.Window = None): writing = self.operation == FlashOp.WRITE reading = not writing - auto = self.auto_detect - manual = not auto + is_uf2 = self.detection is not None and self.detection.is_uf2 need_offset = self.detection is not None and self.detection.need_offset + auto = self.auto_detect + manual = not auto + if manual and is_uf2: + self.auto_detect = auto = True + manual = False + match target: case (self.Read | self.ReadROM) if self.file: # generate a new filename for reading, to prevent @@ -183,8 +188,6 @@ def OnUpdate(self, target: wx.Window = None): errors.append("File does not exist") else: self.FileType.ChangeValue(self.detection.title) - if manual and is_uf2: - self.auto_detect = auto = True if auto: self.family = self.detection.family if not need_offset: From c9686ebd28e85bdedf252598751900835e00146f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Nov 2023 19:13:12 +0100 Subject: [PATCH 08/24] [ambz] Replace rtltool usage with ambztool --- ltchiptool/soc/ambz/flash.py | 122 +++++++++++++++++++++------------- ltchiptool/soc/ambz2/flash.py | 3 +- ltchiptool/util/flash.py | 8 ++- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index aaccc82..238978b 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -1,17 +1,23 @@ # Copyright (c) Kuba Szczodrzyński 2022-07-29. from abc import ABC -from io import BytesIO +from logging import debug, warning from time import sleep from typing import IO, Generator, List, Optional, Union from ltchiptool import SocInterface from ltchiptool.util.flash import FlashConnection -from ltchiptool.util.intbin import gen2bytes, inttole32, letoint +from ltchiptool.util.intbin import gen2bytes, letoint from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext -from .util.rtltool import CAN, RTL_ROM_BAUD, RTLXMD +from .util.ambzcode import AmbZCode +from .util.ambztool import ( + AMBZ_CHIP_TYPE, + AMBZ_FLASH_ADDRESS, + AMBZ_ROM_BAUDRATE, + AmbZTool, +) AMEBAZ_GUIDE = [ "Connect UART2 of the Realtek chip to the USB-TTL adapter:", @@ -37,38 +43,39 @@ # noinspection PyProtectedMember class AmebaZFlash(SocInterface, ABC): - rtl: Optional[RTLXMD] = None - is_can_sent: bool = False + amb: Optional[AmbZTool] = None + chip_info: bytes = None def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: self.flash_disconnect() self.conn = connection - self.conn.fill_baudrate(RTL_ROM_BAUD) + # use 460800 max. as default, since most cheap adapters can't go faster anyway + self.conn.fill_baudrate(460800, link_baudrate=AMBZ_ROM_BAUDRATE) def flash_build_protocol(self, force: bool = False) -> None: - if not force and self.rtl: + if not force and self.amb: return self.flash_disconnect() - self.rtl = RTLXMD( + self.amb = AmbZTool( port=self.conn.port, - baud=self.conn.link_baudrate, - timeout=0.07, + baudrate=self.conn.link_baudrate, + read_timeout=0.2, ) self.flash_change_timeout(self.conn.timeout, self.conn.link_timeout) def flash_change_timeout(self, timeout: float = 0.0, link_timeout: float = 0.0): self.flash_build_protocol() if timeout: - self.rtl._port.timeout = timeout + self.amb.read_timeout = timeout self.conn.timeout = timeout if link_timeout: - self.rtl.sync_timeout = link_timeout + self.amb.link_timeout = link_timeout self.conn.link_timeout = link_timeout def flash_sw_reset(self) -> None: self.flash_build_protocol() - port = self.rtl._port + port = self.amb.s prev_baudrate = port.baudrate port.baudrate = 115200 sleep(0.1) @@ -78,38 +85,52 @@ def flash_sw_reset(self) -> None: sleep(0.5) port.baudrate = prev_baudrate - def flash_hw_reset(self) -> None: - self.flash_build_protocol() - self.rtl.connect() - def flash_connect(self) -> None: - if self.rtl and self.conn.linked: + if self.amb and self.conn.linked: return self.flash_build_protocol() - if not self.is_can_sent: - # try to exit interrupted write operations - # sending 'CAN' exits the download mode, unless it's invoked via hardware - self.rtl._port.write(CAN) - self.is_can_sent = True - if not self.rtl.sync(): - raise TimeoutError(f"Failed to connect on port {self.conn.port}") + assert self.amb + self.amb.link() + self.amb.change_baudrate(self.conn.baudrate) self.conn.linked = True def flash_disconnect(self) -> None: - if self.rtl: - self.rtl._port.close() - self.rtl._port = None - self.rtl = None + if self.amb: + try: + self.amb.link(disconnect=True) + except TimeoutError: + pass + self.amb.close() + self.amb = None if self.conn: self.conn.linked = False + def _read_chip_info(self) -> None: + self.flash_connect() + assert self.amb + self.chip_info = self.amb.ram_boot_read( + AmbZCode.read_chip_id(offset=0) + + AmbZCode.read_flash_id(offset=1) + + AmbZCode.print_data(length=4) + ) + debug(f"Received chip info: {self.chip_info.hex()}") + def flash_get_chip_info_string(self) -> str: - return "Realtek RTL87xxB" + if not self.chip_info: + self._read_chip_info() + chip_id = self.chip_info[0] + return AMBZ_CHIP_TYPE.get(chip_id, f"Unknown 0x{chip_id:02X}") def flash_get_guide(self) -> List[Union[str, list]]: return AMEBAZ_GUIDE def flash_get_size(self) -> int: + if not self.chip_info: + self._read_chip_info() + size_id = self.chip_info[3] + if 0x14 <= size_id <= 0x19: + return 1 << size_id + warning(f"Couldn't process flash ID: got {self.chip_info!r}") return 0x200000 def flash_get_rom_size(self) -> int: @@ -126,10 +147,13 @@ def flash_read_raw( if use_rom: self.flash_get_rom_size() self.flash_connect() - gen = self.rtl.ReadBlockFlashGenerator(offset, length) - success = yield from callback.update_with(gen) - if not success: - raise ValueError(f"Failed to read from 0x{offset:X}") + assert self.amb + gen = self.amb.flash_read( + offset=offset, + length=length, + hash_check=verify, + ) + yield from callback.update_with(gen) def flash_write_raw( self, @@ -140,12 +164,17 @@ def flash_write_raw( callback: ProgressCallback = ProgressCallback(), ) -> None: self.flash_connect() - offset |= 0x8000000 - callback.attach(data) - success = self.rtl.WriteBlockFlash(data, offset, length) - callback.detach(data) - if not success: - raise ValueError(f"Failed to write to 0x{offset:X}") + assert self.amb + callback.attach(data, limit=length) + try: + self.amb.memory_write( + address=AMBZ_FLASH_ADDRESS | offset, + stream=data, + ) + callback.detach(data) + except Exception as e: + callback.detach(data) + raise e def flash_write_uf2( self, @@ -156,14 +185,16 @@ def flash_write_uf2( # read system data to get active OTA index callback.on_message("Checking OTA index...") system = gen2bytes(self.flash_read_raw(0x9000, 256)) - if len(system) != 256: + if len(system) < 256: raise ValueError( f"Length invalid while reading from 0x9000 - {len(system)}" ) + # read OTA switch value ota_switch = f"{letoint(system[4:8]):032b}" # count 0-bits ota_idx = 1 + (ota_switch.count("0") % 2) + # validate OTA2 address in system data if ota_idx == 2: ota2_addr = letoint(system[0:4]) & 0xFFFFFF @@ -178,18 +209,15 @@ def flash_write_uf2( parts = ctx.collect_data( OTAScheme.FLASHER_DUAL_1 if ota_idx == 1 else OTAScheme.FLASHER_DUAL_2 ) - callback.on_total(sum(len(part.getvalue()) for part in parts.values()) + 4) + callback.on_total(sum(len(part.getvalue()) for part in parts.values())) callback.on_message(f"OTA {ota_idx}") # write blocks to flash for offset, data in parts.items(): length = len(data.getvalue()) - callback.on_message(f"OTA {ota_idx} (0x{offset:06X})") data.seek(0) + callback.on_message(f"OTA {ota_idx} (0x{offset:06X})") self.flash_write_raw(offset, length, data, verify, callback) callback.on_message("Booting firmware") - # [0x10002000] = 0x00005405 - stream = BytesIO(inttole32(0x00005405)) - self.rtl.WriteBlockSRAM(stream, 0x10002000, 4) - callback.on_update(4) + self.amb.ram_boot(address=0x00005405) diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index b0c0e54..63d5edd 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -139,7 +139,7 @@ def flash_write_uf2( # collect continuous blocks of data parts = ctx.collect_data(OTAScheme.FLASHER_DUAL_1) - callback.on_total(sum(len(part.getvalue()) for part in parts.values()) + 4) + callback.on_total(sum(len(part.getvalue()) for part in parts.values())) # write blocks to flash for offset, data in parts.items(): @@ -150,4 +150,3 @@ def flash_write_uf2( callback.on_message("Booting firmware") self.amb.disconnect() - callback.on_update(4) diff --git a/ltchiptool/util/flash.py b/ltchiptool/util/flash.py index 380e251..b989c34 100644 --- a/ltchiptool/util/flash.py +++ b/ltchiptool/util/flash.py @@ -28,9 +28,11 @@ class FlashConnection: link_timeout: float = 20.0 linked: bool = False - def fill_baudrate(self, baudrate: int) -> None: - self.link_baudrate = self.link_baudrate or baudrate - self.baudrate = self.baudrate or self.link_baudrate or baudrate + def fill_baudrate(self, baudrate: int, link_baudrate: int = None) -> None: + if link_baudrate is None: + link_baudrate = baudrate + self.link_baudrate = self.link_baudrate or link_baudrate + self.baudrate = self.baudrate or baudrate or self.link_baudrate def format_flash_guide(soc) -> List[str]: From 86396e7f59e7197f20d3c391000c5c7a0e290589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 14:12:20 +0100 Subject: [PATCH 09/24] [ambz] Use delayed marker message for UART data readout --- ltchiptool/soc/ambz/util/ambzcode.py | 42 +++++++++++++++++++++-- ltchiptool/soc/ambz/util/ambztool.py | 51 ++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/ltchiptool/soc/ambz/util/ambzcode.py b/ltchiptool/soc/ambz/util/ambzcode.py index ae7f6ca..9ca3d46 100644 --- a/ltchiptool/soc/ambz/util/ambzcode.py +++ b/ltchiptool/soc/ambz/util/ambzcode.py @@ -21,6 +21,41 @@ def rom_console() -> bytes: b"\x05\x22\x00\x00" # RtlConsolRom() ) + @staticmethod + def print_greeting(delay: float, data: bytes) -> bytes: + # ldr r0, ms_delay + # ldr r3, DelayMs + # blx r3 + # adr r0, message_data + # ldr r1, message_size + # ldr r3, xmodem_uart_putdata + # blx r3 + # b next + # DelayMs: .word 0x346C+1 + # xmodem_uart_putdata: .word 0xEC48+1 + # ms_delay: .word 1000 + # message_size: .word 16 + # message_data: .word 0,0,0,0 + # next: + ms_delay = int(delay * 1000) + message_size = len(data) + if message_size > 16: + raise ValueError("Message must be 16 bytes or shorter") + message_data = data.ljust(16, b"\x00") + return ( + ( + b"\x05\x48\x03\x4b" + b"\x98\x47\x06\xa0" + b"\x04\x49\x02\x4b" + b"\x98\x47\x0f\xe0" + b"\x6d\x34\x00\x00" # DelayMs() + b"\x49\xec\x00\x00" # xmodem_uart_putdata() + ) + + inttole32(ms_delay) + + inttole32(message_size) + + message_data + ) + @staticmethod def download_mode() -> bytes: # movs r0, #2 @@ -166,14 +201,15 @@ def read_data_md5(address: int, length: int, offset: int = 0) -> bytes: @staticmethod def print_data(length: int, address: int = AMBZ_DATA_ADDRESS) -> bytes: - # ldr r0, read_data - # movs r1, #length + # ldr r0, address + # ldr r1, length # ldr r3, xmodem_uart_putdata # blx r3 # b next # movs r0, r0 # xmodem_uart_putdata: .word 0xEC48+1 - # read_data: .word 0x10003000 + # address: .word 0x10003000 + # length: .word 4 # next: return ( ( diff --git a/ltchiptool/soc/ambz/util/ambztool.py b/ltchiptool/soc/ambz/util/ambztool.py index b49d024..5c99cb2 100644 --- a/ltchiptool/soc/ambz/util/ambztool.py +++ b/ltchiptool/soc/ambz/util/ambztool.py @@ -5,16 +5,17 @@ from enum import IntEnum from hashlib import md5 from io import BytesIO -from logging import debug, warning +from logging import debug, info, warning from time import sleep, time from typing import IO, Callable, Generator, Optional import click from xmodem import XMODEM +from ltchiptool.util.cli import DevicePortParamType from ltchiptool.util.intbin import align_down, align_up, inttole16, inttole24, inttole32 from ltchiptool.util.logging import LoggingHandler, verbose -from ltchiptool.util.misc import retry_generator +from ltchiptool.util.misc import retry_generator, sizeof from ltchiptool.util.serialtool import SerialToolBase from ltchiptool.util.streams import StreamHook @@ -29,6 +30,7 @@ AMBZ_DIAG_BAUDRATE = 115200 AMBZ_FLASH_ADDRESS = 0x8000000 AMBZ_RAM_ADDRESS = 0x10002000 +AMBZ_GREETING_TEXT = b"AmbZTool_Marker!" AMBZ_CHIP_TYPE = { 0xE0: "RTL8710BL", # ??? @@ -109,7 +111,6 @@ def read(self, io: IO[bytes], n: int) -> bytes: class AmbZTool(SerialToolBase): - crc_speed_bps: int = 1500000 xm_send_code: Optional[bytes] = None xm_fake_ack: bool = False @@ -120,6 +121,7 @@ def __init__( link_timeout: float = 10.0, read_timeout: float = 0.6, retry_count: int = 10, + quiet_timeout: float = 10.0, ): super().__init__(port, baudrate, link_timeout, read_timeout, retry_count) LoggingHandler.get().attach(logging.getLogger("xmodem.XMODEM")) @@ -128,6 +130,7 @@ def __init__( putc=self.xm_putc, mode="xmodem1k", ) + self.quiet_timeout = quiet_timeout ############################# # Xmodem serial port access # @@ -187,7 +190,7 @@ def link(self, disconnect: bool = False) -> None: def quiet_handshake(self) -> None: self.flush() self.push_timeout(0.1) - end = time() + self.link_timeout + end = time() + self.quiet_timeout while time() < end: self.write(ACK) # discard everything from Loud-Handshake @@ -422,12 +425,14 @@ def ram_boot_read( prev_baudrate = self.s.baudrate self.change_baudrate(AMBZ_DIAG_BAUDRATE) + # find actual response using a marker message + # wait before printing to let previous bytes through + code = AmbZCode.print_greeting(delay=0.4, data=AMBZ_GREETING_TEXT) + code # go back into download mode after we're done code = code + AmbZCode.download_mode() # messages printed by the ROM - # DiagPrintf changes "\n" to "\n\r" - msg_pre = b"\rclose xModem Transfer ...\r\n\r" + msg_pre = AMBZ_GREETING_TEXT msg_post = b"UARTIMG_Download" # send RAM code, exit download mode (changes baudrate to 115200) self.ram_boot(code=code, callback=callback, keep_baudrate=True) @@ -447,10 +452,10 @@ def ram_boot_read( if msg_pre in resp: resp = resp.partition(msg_pre)[2] elif msg_pre[-7:] in resp: - warning(f"Expected message not found: {resp!r}") + warning(f"Partial marker message found: {resp!r}") resp = resp.partition(msg_pre[-7:])[2] else: - raise RuntimeError(f"Response unreadable: {resp!r}") + raise RuntimeError(f"Marker message not found: {resp!r}") if msg_post in resp: resp = resp.partition(msg_post)[0] @@ -467,8 +472,34 @@ def ram_boot_read( @click.command( help="AmebaZ flashing tool", ) -def cli(): - raise NotImplementedError() +@click.option( + "-d", + "--device", + help="Target device port (default: auto detect)", + type=DevicePortParamType(), + default=(), +) +def cli(device: str): + amb = AmbZTool(port=device, baudrate=AMBZ_ROM_BAUDRATE) + info("Linking...") + amb.link() + + chip_info = amb.ram_boot_read( + AmbZCode.read_chip_id(offset=0) + + AmbZCode.read_flash_id(offset=1) + + AmbZCode.print_data(length=4) + ) + info(f"Received chip info: {chip_info.hex()}") + chip_id = chip_info[0] + size_id = chip_info[3] + info("Chip type: " + AMBZ_CHIP_TYPE.get(chip_id, f"Unknown 0x{chip_id:02X}")) + if 0x14 <= size_id <= 0x19: + info("Flash size: " + sizeof(1 << size_id)) + else: + warning(f"Couldn't process flash ID: got {chip_info!r}") + + info("Disconnecting...") + amb.link(disconnect=True) if __name__ == "__main__": From 2b1faa6107f93249dae79e11838892e32d493655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 16:41:40 +0100 Subject: [PATCH 10/24] [ambz2] Extract SystemData to common package --- ltchiptool/soc/amb/system.py | 79 +++++++++++++++++++ ltchiptool/soc/ambz2/util/models/enums.py | 18 ----- ltchiptool/soc/ambz2/util/models/images.py | 4 +- .../soc/ambz2/util/models/partitions.py | 36 +-------- 4 files changed, 84 insertions(+), 53 deletions(-) create mode 100644 ltchiptool/soc/amb/system.py diff --git a/ltchiptool/soc/amb/system.py b/ltchiptool/soc/amb/system.py new file mode 100644 index 0000000..db17167 --- /dev/null +++ b/ltchiptool/soc/amb/system.py @@ -0,0 +1,79 @@ +# Copyright (c) Kuba Szczodrzyński 2023-11-3. + +from dataclasses import dataclass +from enum import IntEnum + +from datastruct import DataStruct +from datastruct.fields import adapter, alignto, bitfield, field + +FF_16 = b"\xFF" * 16 + + +class FlashSpeed(IntEnum): + F_100MHZ = 0xFFFF + F_83MHZ = 0x7FFF + F_71MHZ = 0x3FFF + F_62MHZ = 0x1FFF + F_55MHZ = 0x0FFF + F_50MHZ = 0x07FF + F_45MHZ = 0x03FF + + +class FlashMode(IntEnum): + QIO = 0xFFFF # Quad IO + QO = 0x7FFF # Quad Output + DIO = 0x3FFF # Dual IO + DO = 0x1FFF # Dual Output + SINGLE = 0x0FFF # One IO + + +@dataclass +class SystemData(DataStruct): + @dataclass + class ForceOldOTA: + is_disabled: bool + port: int + pin: int + + @dataclass + class RSIPMask: + length: int + offset: int + is_disabled: bool + + # OTA section + ota2_address: int = field("I", default=0xFFFFFFFF) + ota2_switch: int = field("I", default=0xFFFFFFFF) + force_old_ota: ForceOldOTA = bitfield("b1P1u1u5", ForceOldOTA, 0xFF) + # RDP section (AmebaZ only) + _1: ... = alignto(0x10) + rdp_address: int = field("I", default=0xFFFFFFFF) + rdp_length: int = field("I", default=0xFFFFFFFF) + # Flash section + _2: ... = alignto(0x20) + flash_mode: FlashMode = field("H", default=FlashMode.QIO) + flash_speed: FlashSpeed = field("H", default=FlashSpeed.F_100MHZ) # AmebaZ only + flash_id: int = field("H", default=0xFFFF) + flash_size_mb: int = adapter( + encode=lambda v, ctx: 0xFFFF if v == 2 else (v << 10) - 1, + decode=lambda v, ctx: 2 if v == 0xFFFF else (v + 1) >> 10, + )(field("H", default=2)) + flash_status: int = field("H", default=0x0000) + # Log UART section + _3: ... = alignto(0x30) + baudrate: int = adapter( + encode=lambda v, ctx: 0xFFFFFFFF if v == 115200 else v, + decode=lambda v, ctx: 115200 if v == 0xFFFFFFFF else v, + )(field("I", default=115200)) + # Calibration data (AmebaZ2 only) + _4: ... = alignto(0x40) + spic_calibration: bytes = field("16s", default=FF_16) + # RSIP section (AmebaZ only) + _5: ... = alignto(0x50) + rsip_mask1: RSIPMask = bitfield("u7P2u22u1", RSIPMask, 0xFFFFFFFF) + rsip_mask2: RSIPMask = bitfield("u7P2u22u1", RSIPMask, 0xFFFFFFFF) + # Calibration data (AmebaZ2 only) + _6: ... = alignto(0xFE0) + bt_ftl_gc_status: int = field("I", default=0xFFFFFFFF) + _7: ... = alignto(0xFF0) + bt_calibration: bytes = field("16s", default=FF_16) diff --git a/ltchiptool/soc/ambz2/util/models/enums.py b/ltchiptool/soc/ambz2/util/models/enums.py index 4ddf14f..6f96c8f 100644 --- a/ltchiptool/soc/ambz2/util/models/enums.py +++ b/ltchiptool/soc/ambz2/util/models/enums.py @@ -40,24 +40,6 @@ class SectionType(IntEnum): XIP = 0x85 -class FlashSpeed(IntEnum): - QIO = 0xFFFF # Quad IO - QO = 0x7FFF # Quad Output - DIO = 0x3FFF # Dual IO - DO = 0x1FFF # Dual Output - SINGLE = 0x0FFF # One IO - - -class FlashSize(IntEnum): - F_2MB = 0xFFFF - F_32MB = 0x7FFF - F_16MB = 0x3FFF - F_8MB = 0x1FFF - F_4MB = 0x0FFF - F_2MB_ = 0x07FF - F_1MB = 0x03FF - - class EncAlgo(IntEnum): AES_EBC = 0 AES_CBC = 1 diff --git a/ltchiptool/soc/ambz2/util/models/images.py b/ltchiptool/soc/ambz2/util/models/images.py index 4cf7507..388ff7e 100644 --- a/ltchiptool/soc/ambz2/util/models/images.py +++ b/ltchiptool/soc/ambz2/util/models/images.py @@ -21,8 +21,10 @@ switch, ) +from ltchiptool.soc.amb.system import SystemData + from .headers import ImageHeader, Keyblock, KeyblockOTA, header_is_last -from .partitions import Bootloader, Firmware, PartitionTable, SystemData +from .partitions import Bootloader, Firmware, PartitionTable from .utils import FF_32 FLASH_CALIBRATION = b"\x99\x99\x96\x96\x3F\xCC\x66\xFC\xC0\x33\xCC\x03\xE5\xDC\x31\x62" diff --git a/ltchiptool/soc/ambz2/util/models/partitions.py b/ltchiptool/soc/ambz2/util/models/partitions.py index 966d2bd..ad9cfda 100644 --- a/ltchiptool/soc/ambz2/util/models/partitions.py +++ b/ltchiptool/soc/ambz2/util/models/partitions.py @@ -9,7 +9,6 @@ action, adapter, align, - alignto, bitfield, built, field, @@ -19,9 +18,9 @@ subfield, ) -from .enums import FlashSize, FlashSpeed, PartitionType +from .enums import PartitionType from .headers import FST, EntryHeader, SectionHeader, header_is_last -from .utils import FF_16, FF_32, FF_48, BitFlag, index +from .utils import FF_32, BitFlag, index @dataclass @@ -77,37 +76,6 @@ class KeyExport(IntEnum): user_data: bytes = field(lambda ctx: ctx.user_data_len, default=b"") -@dataclass -class SystemData(DataStruct): - @dataclass - class ForceOldOTA: - is_active: bool - port: int - pin: int - - # OTA section - _1: ... = alignto(0x08 + 3) - force_old_ota: ForceOldOTA = bitfield("b1P1u1u5", ForceOldOTA, 0xFF) - # Flash section - _2: ... = alignto(0x22) - flash_speed: FlashSpeed = field("H", default=FlashSpeed.QIO) - flash_size: FlashSize = field("H", default=FlashSize.F_2MB) - flash_id: bytes = field("3s", default=b"\xFF\xFF\xFF") - # Log UART section - _3: ... = alignto(0x30) - baudrate: int = adapter( - encode=lambda v, ctx: 0xFFFFFFFF if v == 115200 else v, - decode=lambda v, ctx: 115200 if v == 0xFFFFFFFF else v, - )(field("I", default=115200)) - # Calibration data - _4: ... = alignto(0x40) - spic_calibration: bytes = field("48s", default=FF_48) - _5: ... = alignto(0xFE0) - bt_ftl_gc_status: int = field("I", default=0xFFFFFFFF) - _6: ... = alignto(0xFF0) - bt_calibration: bytes = field("16s", default=FF_16) - - @dataclass class Bootloader(DataStruct): entry: EntryHeader = subfield() From 96bff891119a7f215cbe4e015b571474be7d5948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 16:41:57 +0100 Subject: [PATCH 11/24] [gui] Improve flashing progress messages --- ltchiptool/gui/work/flash.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ltchiptool/gui/work/flash.py b/ltchiptool/gui/work/flash.py index 5b28f2b..213a9de 100644 --- a/ltchiptool/gui/work/flash.py +++ b/ltchiptool/gui/work/flash.py @@ -96,15 +96,16 @@ def _link(self): elapsed += 1 if self.should_stop(): return + self.callback.on_message("Reading chip info...") chip_info = self.soc.flash_get_chip_info_string() self.on_chip_info(f"Chip info: {chip_info}") def _do_write(self): if self.should_stop(): return - self.callback.on_message(None) if self.ctx: + self.callback.on_message(None) self.soc.flash_write_uf2( ctx=self.ctx, verify=self.verify, @@ -127,6 +128,7 @@ def read(n: int = -1) -> bytes | None: if self.skip + self.length > file_size: raise ValueError(f"File is too small (requested to write too much data)") + self.callback.on_message("Checking flash size...") max_length = self.soc.flash_get_size() if self.offset > max_length - self.length: raise ValueError( @@ -138,6 +140,7 @@ def read(n: int = -1) -> bytes | None: tell = file.tell() debug(f"Starting file position: {tell} / 0x{tell:X} / {sizeof(tell)}") self.callback.on_total(self.length) + self.callback.on_message(None) self.soc.flash_write_raw( offset=self.offset, length=self.length, @@ -149,8 +152,8 @@ def read(n: int = -1) -> bytes | None: def _do_read(self): if self.should_stop(): return - self.callback.on_message(None) + self.callback.on_message("Checking flash size...") if self.operation == FlashOp.READ_ROM: max_length = self.soc.flash_get_rom_size() else: @@ -167,6 +170,7 @@ def _do_read(self): makedirs(dirname(self.file), exist_ok=True) file = open(self.file, "wb") self.callback.on_total(self.length) + self.callback.on_message(None) for chunk in self.soc.flash_read_raw( offset=self.offset, length=self.length, From c3309f498bfc8da5bdcc8423e3faafb21c327cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 17:36:05 +0100 Subject: [PATCH 12/24] [core] Allow unregistering hooks during use --- ltchiptool/util/streams.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/ltchiptool/util/streams.py b/ltchiptool/util/streams.py index 8fafe76..26008bd 100644 --- a/ltchiptool/util/streams.py +++ b/ltchiptool/util/streams.py @@ -42,6 +42,8 @@ def attach(self, io: IO[bytes], limit: int = 0) -> IO[bytes]: limit = 0 def read(n: int = -1) -> bytes: + if self.is_unregistered(type(io)): + return getattr(io, self.read_key)(n) if limit > 0: # read at most 'limit' bytes pos = io.tell() @@ -55,6 +57,8 @@ def read(n: int = -1) -> bytes: return data def write(data: bytes) -> int: + if self.is_unregistered(type(io)): + return getattr(io, self.write_key)(data) data_new = self.on_before_write(data) if isinstance(data_new, bytes): data = data_new @@ -77,24 +81,27 @@ def detach(self, io: IO[bytes]) -> IO[bytes]: @classmethod def register(cls, target: Type, *hook_args, **hook_kwargs) -> None: - if hasattr(target, "__init_hook__"): + if hasattr(target, f"__hook_unregistered_{cls.__name__}__"): + delattr(target, f"__hook_unregistered_{cls.__name__}__") + if hasattr(target, f"__init_hook_{cls.__name__}__"): return - setattr(target, "__init_hook__", target.__init__) + setattr(target, f"__init_hook_{cls.__name__}__", target.__init__) # noinspection PyArgumentList def init(self, *args, **kwargs): - self.__init_hook__(*args, **kwargs) + getattr(target, f"__init_hook_{cls.__name__}__")(self, *args, **kwargs) hook = cls(*hook_args, **hook_kwargs) hook.attach(self) setattr(target, "__init__", init) - @staticmethod - def unregister(target: Type): - __init__ = getattr(target, "__init_hook__", None) + @classmethod + def unregister(cls, target: Type): + setattr(target, f"__hook_unregistered_{cls.__name__}__", True) + __init__ = getattr(target, f"__init_hook_{cls.__name__}__", None) if __init__ is not None: setattr(target, "__init__", __init__) - delattr(target, "__init_hook__") + delattr(target, f"__init_hook_{cls.__name__}__") @classmethod def set_registered(cls, target: Type, registered: bool): @@ -103,9 +110,13 @@ def set_registered(cls, target: Type, registered: bool): else: cls.unregister(target) - @staticmethod - def is_registered(target: Type): - return hasattr(target, "__init_hook__") + @classmethod + def is_registered(cls, target: Type): + return hasattr(target, f"__init_hook_{cls.__name__}__") + + @classmethod + def is_unregistered(cls, target: Type): + return hasattr(target, f"__hook_unregistered_{cls.__name__}__") class LoggingStreamHook(StreamHook): From de0035572c0934396c08b1d66d12dc880952c377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 17:37:39 +0100 Subject: [PATCH 13/24] [ambz] Adjust OTA2 address during UF2 flashing, improve disconnection --- ltchiptool/soc/ambz/flash.py | 41 ++++++++++++++++++++-------- ltchiptool/soc/ambz/util/ambzcode.py | 17 ++++++++++-- ltchiptool/soc/ambz/util/ambztool.py | 33 +++++++++++----------- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index 238978b..029efdd 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -1,13 +1,16 @@ # Copyright (c) Kuba Szczodrzyński 2022-07-29. from abc import ABC +from io import BytesIO from logging import debug, warning from time import sleep from typing import IO, Generator, List, Optional, Union from ltchiptool import SocInterface +from ltchiptool.soc.amb.system import SystemData from ltchiptool.util.flash import FlashConnection -from ltchiptool.util.intbin import gen2bytes, letoint +from ltchiptool.util.intbin import gen2bytes +from ltchiptool.util.logging import verbose from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext @@ -97,7 +100,7 @@ def flash_connect(self) -> None: def flash_disconnect(self) -> None: if self.amb: try: - self.amb.link(disconnect=True) + self.amb.disconnect() except TimeoutError: pass self.amb.close() @@ -184,26 +187,40 @@ def flash_write_uf2( ) -> None: # read system data to get active OTA index callback.on_message("Checking OTA index...") - system = gen2bytes(self.flash_read_raw(0x9000, 256)) - if len(system) < 256: + system_data = gen2bytes(self.flash_read_raw(0x9000, 4096, verify=False)) + if len(system_data) != 4096: raise ValueError( - f"Length invalid while reading from 0x9000 - {len(system)}" + f"Length invalid while reading from 0x9000 - {len(system_data)}" ) + system = SystemData.unpack(system_data) + verbose(f"Realtek System Data: {system}") # read OTA switch value - ota_switch = f"{letoint(system[4:8]):032b}" + ota_switch = f"{system.ota2_switch:032b}" # count 0-bits ota_idx = 1 + (ota_switch.count("0") % 2) - # validate OTA2 address in system data - if ota_idx == 2: - ota2_addr = letoint(system[0:4]) & 0xFFFFFF + # check OTA2 address + try: + ota2_addr = system.ota2_address & 0xFFFFFF part_addr = ctx.get_offset("ota2", 0) if ota2_addr != part_addr: - raise ValueError( - f"Invalid OTA2 address on chip - " - f"found {ota2_addr}, expected {part_addr}", + # if it differs, correct it + system.ota2_address = AMBZ_FLASH_ADDRESS | part_addr + # reset OTA switch to use OTA1 + system.ota2_switch = 0xFFFFFFFF + ota_idx = 1 + # flash new system data + system_data = system.pack() + callback.on_message("Adjusting OTA2 address...") + self.flash_write_raw( + offset=0x9000, + length=len(system_data), + data=BytesIO(system_data), + callback=callback, ) + except ValueError: + warning("OTA2 partition not found in UF2 package") # collect continuous blocks of data parts = ctx.collect_data( diff --git a/ltchiptool/soc/ambz/util/ambzcode.py b/ltchiptool/soc/ambz/util/ambzcode.py index 9ca3d46..c9fc83e 100644 --- a/ltchiptool/soc/ambz/util/ambzcode.py +++ b/ltchiptool/soc/ambz/util/ambzcode.py @@ -58,15 +58,26 @@ def print_greeting(delay: float, data: bytes) -> bytes: @staticmethod def download_mode() -> bytes: + """Disable booting to SRAM and run download mode again.""" + # ldr r3, uartimg_boot_sram + # ldr r0, [r3] + # ldr r1, uartimg_boot_mask + # ands r0, r0, r1 + # str r0, [r3] # movs r0, #2 # ldr r3, UARTIMG_Download # blx r3 - # movs r0, r0 # UARTIMG_Download: .word 0x900+1 + # uartimg_boot_sram: .word 0x40000210 + # uartimg_boot_mask: .word 0xEFFFFFFF return ( - b"\x02\x20\x01\x4b" - b"\x98\x47\x00\x00" + b"\x04\x4b\x18\x68" + b"\x04\x49\x08\x40" + b"\x18\x60\x02\x20" + b"\x00\x4b\x98\x47" b"\x01\x09\x00\x00" # UARTIMG_Download() + b"\x10\x02\x00\x40" # uartimg_boot_sram + b"\xff\xff\xff\xef" # uartimg_boot_mask ) @staticmethod diff --git a/ltchiptool/soc/ambz/util/ambztool.py b/ltchiptool/soc/ambz/util/ambztool.py index 5c99cb2..3af56eb 100644 --- a/ltchiptool/soc/ambz/util/ambztool.py +++ b/ltchiptool/soc/ambz/util/ambztool.py @@ -152,12 +152,22 @@ def xm_putc(self, data, _=1): # Basic commands - public low-level API # ######################################### - def link(self, disconnect: bool = False) -> None: + def disconnect(self) -> None: + # try to enter Loud-Handshake mode + command = [ + # - Xmodem -> Handshake + # - Loud-Handshake -> Quiet-Handshake + AmbZCommand.XMODEM_CAN, + # - Handshake -> Xmodem + AmbZCommand.XMODEM_HANDSHAKE, + # - Xmodem -> Loud-Handshake (resets baud rate) + AmbZCommand.XMODEM_CAN, + ] + self.write(bytes(command)) + + def link(self) -> None: # clear any data before linking self.flush() - if disconnect: - # break NAK stream to force resetting baudrate - self.quiet_handshake() handshake = b"" end = time() + self.link_timeout while time() < end: @@ -169,17 +179,7 @@ def link(self, disconnect: bool = False) -> None: handshake = handshake[-4:] if len(handshake) == 4 and all(c == NAK[0] for c in handshake): break - # try to enter Loud-Handshake mode - command = [ - # - Xmodem -> Handshake - # - Loud-Handshake -> Quiet-Handshake - AmbZCommand.XMODEM_CAN, - # - Handshake -> Xmodem - AmbZCommand.XMODEM_HANDSHAKE, - # - Xmodem -> Loud-Handshake (resets baud rate) - AmbZCommand.XMODEM_CAN, - ] - self.write(bytes(command)) + self.disconnect() sleep(0.1) self.set_baudrate(AMBZ_ROM_BAUDRATE) else: @@ -351,7 +351,6 @@ def memory_write( self.write(bytes([AmbZCommand.XMODEM_HANDSHAKE])) self.expect_ack("Xmodem handshake") - self.xm.mode = "xmodem" # fake a NAK to make xmodem happy self.xm_send_code = NAK # fake an ACK after EOT to make xmodem very happy @@ -499,7 +498,7 @@ def cli(device: str): warning(f"Couldn't process flash ID: got {chip_info!r}") info("Disconnecting...") - amb.link(disconnect=True) + amb.disconnect() if __name__ == "__main__": From 7102d73f666cea668edecbcc09eecc054f267a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 3 Nov 2023 20:07:40 +0100 Subject: [PATCH 14/24] [gui] Add flashing guide and docs, update guides --- ltchiptool/gui/ltchiptool.wxui | 37 ++++++++++++-- ltchiptool/gui/ltchiptool.xrc | 56 +++++++++++++++++++-- ltchiptool/gui/main.py | 4 +- ltchiptool/gui/panels/flash.py | 90 +++++++++++++++++++++++++++++++--- ltchiptool/soc/ambz/flash.py | 44 ++++++++++------- ltchiptool/soc/ambz2/flash.py | 8 ++- ltchiptool/soc/bk72xx/flash.py | 32 +++++++----- ltchiptool/soc/interface.py | 20 +++++--- ltchiptool/util/flash.py | 11 +++++ 9 files changed, 251 insertions(+), 51 deletions(-) diff --git a/ltchiptool/gui/ltchiptool.wxui b/ltchiptool/gui/ltchiptool.wxui index df9569e..0b67f71 100644 --- a/ltchiptool/gui/ltchiptool.wxui +++ b/ltchiptool/gui/ltchiptool.wxui @@ -259,10 +259,9 @@ var_name="radio_baudrate_921600" /> + + + + + + + 2,1 1,1 - wxALL|wxEXPAND + wxALL 5 - - wxHORIZONTAL + + 0 + 3 + 0 + 0 wxALL 5 @@ -300,6 +303,20 @@ + + wxALL + 5 + + -1 + + + + wxALL + 5 + + + + wxALL 5 @@ -307,6 +324,13 @@ + + wxALL + 5 + + + + @@ -449,6 +473,32 @@ 1,1 wxALL|wxEXPAND 0 + + 0 + 2 + 0 + 0 + + wxALL|wxEXPAND + 5 + + + + + + wxALL|wxEXPAND + 5 + + + + + + + + 8,1 + 1,1 + wxALL|wxEXPAND + 0 0 2 diff --git a/ltchiptool/gui/main.py b/ltchiptool/gui/main.py index 677dce8..6338b3f 100644 --- a/ltchiptool/gui/main.py +++ b/ltchiptool/gui/main.py @@ -145,8 +145,8 @@ def __init__(self, *args, **kw): self.Notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging) self.Notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged) - self.SetSize((700, 800)) - self.SetMinSize((600, 700)) + self.SetSize((750, 850)) + self.SetMinSize((700, 800)) self.SetIcon(wx.Icon(icon, wx.BITMAP_TYPE_ICO)) self.CreateStatusBar() diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index e337f43..fd65ef7 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -1,12 +1,14 @@ # Copyright (c) Kuba Szczodrzyński 2023-1-2. import os +import webbrowser from datetime import datetime from logging import debug, info from os.path import dirname, isfile, realpath import wx import wx.adv +import wx.lib.agw.genericmessagedialog as GMD import wx.xrc from ltchiptool import Family, SocInterface @@ -16,7 +18,7 @@ from ltchiptool.util.cli import list_serial_ports from ltchiptool.util.detection import Detection from ltchiptool.util.fileio import chname -from ltchiptool.util.flash import FlashOp +from ltchiptool.util.flash import FlashFeatures, FlashOp, format_flash_guide from ltchiptool.util.logging import verbose from .base import BasePanel @@ -42,10 +44,14 @@ def __init__(self, parent: wx.Window, frame): self.Write = self.BindRadioButton("radio_write") self.Read = self.BindRadioButton("radio_read") self.ReadROM = self.BindRadioButton("radio_read_rom") + self.ReadEfuse = self.BindRadioButton("radio_read_efuse") + self.ReadInfo = self.BindRadioButton("radio_read_info") self.AutoDetect = self.BindCheckBox("checkbox_auto_detect") self.FileText = self.FindStaticText("text_file") self.File = self.BindTextCtrl("input_file") self.Family = self.BindComboBox("combo_family") + self.Guide = self.BindButton("button_guide", self.OnGuideClick) + self.Docs = self.BindButton("button_docs", self.OnDocsClick) self.BindButton("button_browse", self.OnBrowseClick) self.Start: wx.adv.CommandLinkButton = self.BindButton( "button_start", self.OnStartClick @@ -137,6 +143,35 @@ def OnShow(self): self.StartWork(PortWatcher(self.OnPortsUpdated), freeze_ui=False) def OnUpdate(self, target: wx.Window = None): + if target == self.Family: + # update components based on SocInterface feature set + soc = self.soc + if soc: + features = soc.flash_get_features() + guide = soc.flash_get_guide() + docs = soc.flash_get_docs_url() + else: + features = FlashFeatures() + guide = None + docs = None + self.Write.Enable(features.can_write) + self.Read.Enable(features.can_read) + self.ReadROM.Enable(features.can_read_rom) + self.ReadEfuse.Enable(features.can_read_efuse) + self.ReadInfo.Enable(features.can_read_info) + self.Guide.Enable(bool(guide)) + self.Docs.Enable(bool(docs)) + if not features.can_write and self.Write.GetValue(): + self.Read.SetValue(True) + if not features.can_read and self.Read.GetValue(): + self.Write.SetValue(True) + if not features.can_read_rom and self.ReadROM.GetValue(): + self.Read.SetValue(True) + if not features.can_read_efuse and self.ReadEfuse.GetValue(): + self.Read.SetValue(True) + if not features.can_read_info and self.ReadInfo.GetValue(): + self.Read.SetValue(True) + writing = self.operation == FlashOp.WRITE reading = not writing @@ -145,12 +180,12 @@ def OnUpdate(self, target: wx.Window = None): auto = self.auto_detect manual = not auto - if manual and is_uf2: + if writing and manual and is_uf2: self.auto_detect = auto = True manual = False match target: - case (self.Read | self.ReadROM) if self.file: + case (self.Read | self.ReadROM | self.ReadEfuse) if self.file: # generate a new filename for reading, to prevent # accidentally overwriting firmware files if not self.prev_file: @@ -321,6 +356,10 @@ def operation(self): return FlashOp.READ if self.ReadROM.GetValue(): return FlashOp.READ_ROM + if self.ReadEfuse.GetValue(): + return FlashOp.READ_EFUSE + if self.ReadInfo.GetValue(): + return FlashOp.READ_INFO @operation.setter def operation(self, value: FlashOp): @@ -334,6 +373,12 @@ def operation(self, value: FlashOp): case FlashOp.READ_ROM: self.ReadROM.SetValue(True) self.DoUpdate(self.ReadROM) + case FlashOp.READ_EFUSE: + self.ReadEfuse.SetValue(True) + self.DoUpdate(self.ReadEfuse) + case FlashOp.READ_INFO: + self.ReadInfo.SetValue(True) + self.DoUpdate(self.ReadInfo) @property def auto_detect(self): @@ -359,6 +404,15 @@ def family(self, value: Family | None): self.Family.SetValue(family.description) self.DoUpdate(self.Family) + @property + def soc(self) -> SocInterface | None: + if not self.family: + return None + if self.operation == FlashOp.WRITE and self.auto_detect and self.detection: + return self.detection.soc or SocInterface.get(self.family) + else: + return SocInterface.get(self.family) + @property def file(self): return self.File.GetValue() @@ -453,6 +507,31 @@ def OnPortsUpdated(self, ports: list[tuple[str, bool, str]]): def OnRescanClick(self): self.OnPortsUpdated(list_serial_ports()) + @on_event + def OnGuideClick(self): + guide = self.soc.flash_get_guide() + if not guide: + self.Guide.Disable() + return + dialog = GMD.GenericMessageDialog( + parent=self, + message="\n".join(format_flash_guide(self.soc)), + caption="Flashing guide", + agwStyle=wx.ICON_INFORMATION | wx.OK, + ) + font = wx.Font(wx.FontInfo(10).Family(wx.MODERN)) + dialog.SetFont(font) + dialog.ShowModal() + dialog.Destroy() + + @on_event + def OnDocsClick(self): + docs = self.soc.flash_get_docs_url() + if not docs: + self.Docs.Disable() + return + webbrowser.open_new_tab(docs) + @on_event def OnBrowseClick(self): if self.operation == FlashOp.WRITE: @@ -473,10 +552,7 @@ def OnBrowseClick(self): @on_event def OnStartClick(self): - if self.operation == FlashOp.WRITE and self.auto_detect and self.detection: - soc = self.detection.soc or SocInterface.get(self.family) - else: - soc = SocInterface.get(self.family) + soc = self.soc if self.operation != FlashOp.WRITE: if self.file == self.auto_file: diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index 029efdd..13e2ee6 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -8,7 +8,7 @@ from ltchiptool import SocInterface from ltchiptool.soc.amb.system import SystemData -from ltchiptool.util.flash import FlashConnection +from ltchiptool.util.flash import FlashConnection, FlashFeatures from ltchiptool.util.intbin import gen2bytes from ltchiptool.util.logging import verbose from ltchiptool.util.streams import ProgressCallback @@ -28,19 +28,22 @@ ("PC", "RTL8710B"), ("RX", "TX2 (Log_TX / PA30)"), ("TX", "RX2 (Log_RX / PA29)"), - ("RTS", "CEN (or RST, optional)"), - ("DTR", "TX2 (Log_TX / PA30, optional)"), ("", ""), ("GND", "GND"), ], - "Make sure to use a good 3.3V power supply, otherwise the adapter might\n" - "lose power during chip reset. Usually, the adapter's power regulator\n" - "is not enough and an external power supply is needed (like AMS1117).", - "If you didn't connect RTS and DTR, you need to put the chip in download\n" - "mode manually. This is done by connecting CEN to GND, while holding TX2 (Log_TX)\n" - "to GND as well. After doing that, you need to disconnect TX2 from GND.", - "If the download mode is enabled, you'll see a few garbage characters\n" - "printed to the serial console every second.", + "Using a good, stable 3.3V power supply is crucial. Most flashing issues\n" + "are caused by either voltage drops during intensive flash operations,\n" + "or bad/loose wires.", + "The UART adapter's 3.3V power regulator is usually not enough. Instead,\n" + "a regulated bench power supply, or a linear 1117-type regulator is recommended.", + "In order to flash the chip, you need to enable download mode.\n" + "This is done by pulling CEN to GND briefly, while still keeping the TX2 pin\n" + "connected to GND.", + "Do this, in order:\n" + " - connect CEN to GND\n" + " - connect TX2 to GND\n" + " - release CEN from GND\n" + " - release TX2 from GND", ] @@ -49,6 +52,19 @@ class AmebaZFlash(SocInterface, ABC): amb: Optional[AmbZTool] = None chip_info: bytes = None + def flash_get_features(self) -> FlashFeatures: + return FlashFeatures( + can_read_rom=False, + can_read_efuse=False, + can_read_info=False, + ) + + def flash_get_guide(self) -> List[Union[str, list]]: + return AMEBAZ_GUIDE + + def flash_get_docs_url(self) -> Optional[str]: + return "https://docs.libretiny.eu/link/flashing-realtek-ambz" + def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: self.flash_disconnect() @@ -124,9 +140,6 @@ def flash_get_chip_info_string(self) -> str: chip_id = self.chip_info[0] return AMBZ_CHIP_TYPE.get(chip_id, f"Unknown 0x{chip_id:02X}") - def flash_get_guide(self) -> List[Union[str, list]]: - return AMEBAZ_GUIDE - def flash_get_size(self) -> int: if not self.chip_info: self._read_chip_info() @@ -136,9 +149,6 @@ def flash_get_size(self) -> int: warning(f"Couldn't process flash ID: got {self.chip_info!r}") return 0x200000 - def flash_get_rom_size(self) -> int: - raise NotImplementedError("ROM is not readable via UART on RTL87xxB") - def flash_read_raw( self, offset: int, diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index 63d5edd..d91cbf3 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -4,7 +4,7 @@ from typing import IO, Generator, Optional from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection +from ltchiptool.util.flash import FlashConnection, FlashFeatures from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext @@ -14,6 +14,12 @@ class AmebaZ2Flash(SocInterface, ABC): amb: Optional[AmbZ2Tool] = None + def flash_get_features(self) -> FlashFeatures: + return FlashFeatures( + can_read_efuse=False, + can_read_info=False, + ) + def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: self.flash_disconnect() diff --git a/ltchiptool/soc/bk72xx/flash.py b/ltchiptool/soc/bk72xx/flash.py index 6ae8eaf..36c5e1d 100644 --- a/ltchiptool/soc/bk72xx/flash.py +++ b/ltchiptool/soc/bk72xx/flash.py @@ -9,7 +9,7 @@ from bk7231tools.serial import BK7231Serial from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection +from ltchiptool.util.flash import FlashConnection, FlashFeatures from ltchiptool.util.intbin import inttole32 from ltchiptool.util.logging import VERBOSE, verbose from ltchiptool.util.streams import ProgressCallback @@ -21,22 +21,35 @@ ("PC", "BK7231"), ("RX", "TX1 (GPIO11 / P11)"), ("TX", "RX1 (GPIO10 / P10)"), - ("RTS", "CEN (or RST, optional)"), ("", ""), ("GND", "GND"), ], - "Make sure to use a good 3.3V power supply, otherwise the adapter might\n" - "lose power during chip reset. Usually, the adapter's power regulator\n" - "is not enough and an external power supply is needed (like AMS1117).", - "If you didn't connect RTS to CEN, after running the command you have\n" - "around 20 seconds to reset the chip manually. In order to do that,\n" - "you need to bridge CEN to GND with a wire.", + "Using a good, stable 3.3V power supply is crucial. Most flashing issues\n" + "are caused by either voltage drops during intensive flash operations,\n" + "or bad/loose wires.", + "The UART adapter's 3.3V power regulator is usually not enough. Instead,\n" + "a regulated bench power supply, or a linear 1117-type regulator is recommended.", + "To enter download mode, the chip has to be rebooted while the flashing program\n" + "is trying to establish communication.\n" + "In order to do that, you need to bridge CEN pin to GND with a wire.", ] class BK72XXFlash(SocInterface, ABC): bk: Optional[BK7231Serial] = None + def flash_get_features(self) -> FlashFeatures: + return FlashFeatures( + can_read_efuse=False, + can_read_info=False, + ) + + def flash_get_guide(self) -> List[Union[str, list]]: + return BK72XX_GUIDE + + def flash_get_docs_url(self) -> Optional[str]: + return "https://docs.libretiny.eu/link/flashing-beken-72xx" + def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: self.flash_disconnect() @@ -95,9 +108,6 @@ def flash_get_chip_info_string(self) -> str: ] return " / ".join(items) - def flash_get_guide(self) -> List[Union[str, list]]: - return BK72XX_GUIDE - def flash_get_size(self) -> int: return 0x200000 diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 14a0ec1..87a8729 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -5,7 +5,7 @@ from ltchiptool import Board, Family from ltchiptool.models import OTAType -from ltchiptool.util.flash import FlashConnection +from ltchiptool.util.flash import FlashConnection, FlashFeatures from ltchiptool.util.fwbinary import FirmwareBinary from ltchiptool.util.streams import ProgressCallback from uf2tool import UploadContext @@ -106,6 +106,18 @@ def detect_file_type( # Flashing - reading/writing raw files and UF2 packages # ######################################################### + def flash_get_features(self) -> FlashFeatures: + """Check which flasher features are supported.""" + return FlashFeatures() # Optional; do not fail here + + def flash_get_guide(self) -> List[Union[str, list]]: + """Get a short textual guide for putting the chip in download mode.""" + return [] # Optional; do not fail here + + def flash_get_docs_url(self) -> Optional[str]: + """Get a link to flashing documentation.""" + return None # Optional; do not fail here + def flash_set_connection(self, connection: FlashConnection) -> None: """Configure device connection parameters.""" raise NotImplementedError() @@ -139,10 +151,6 @@ def flash_get_chip_info_string(self) -> str: """Read chip info from the protocol as a string.""" raise NotImplementedError() - def flash_get_guide(self) -> List[Union[str, list]]: - """Get a short textual guide for putting the chip in download mode.""" - return [] # Optional; do not fail here - def flash_get_size(self) -> int: """Retrieve the flash size, in bytes.""" raise NotImplementedError() @@ -150,7 +158,7 @@ def flash_get_size(self) -> int: def flash_get_rom_size(self) -> int: """Retrieve the ROM size, in bytes. Raises NotImplementedError() if ROM is not available or not readable.""" - raise NotImplementedError() + raise NotImplementedError("No ROM, or reading not possible via UART") def flash_read_raw( self, diff --git a/ltchiptool/util/flash.py b/ltchiptool/util/flash.py index b989c34..0a9c4e5 100644 --- a/ltchiptool/util/flash.py +++ b/ltchiptool/util/flash.py @@ -17,6 +17,8 @@ class FlashOp(Enum): WRITE = "write" READ = "read" READ_ROM = "read_rom" + READ_EFUSE = "read_efuse" + READ_INFO = "read_info" @dataclass @@ -35,6 +37,15 @@ def fill_baudrate(self, baudrate: int, link_baudrate: int = None) -> None: self.baudrate = self.baudrate or baudrate or self.link_baudrate +@dataclass +class FlashFeatures: + can_write: bool = True + can_read: bool = True + can_read_rom: bool = True + can_read_efuse: bool = True + can_read_info: bool = True + + def format_flash_guide(soc) -> List[str]: guide = [] dash_line = "-" * 6 From 072d2b7212176f6701564cc6e6ea3eec255dfc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 4 Nov 2023 17:56:37 +0100 Subject: [PATCH 15/24] [ambz] Implement eFuse reading --- ltchiptool/commands/flash/read.py | 27 ++++++++------ ltchiptool/gui/panels/flash.py | 10 ++++- ltchiptool/gui/work/flash.py | 34 +++++++++++------ ltchiptool/soc/ambz/flash.py | 51 ++++++++++++++++---------- ltchiptool/soc/ambz/util/ambzcode.py | 55 ++++++++++++++++++++++++++++ ltchiptool/soc/ambz/util/ambztool.py | 2 + ltchiptool/soc/ambz2/flash.py | 17 +++++---- ltchiptool/soc/bk72xx/flash.py | 25 +++++++------ ltchiptool/soc/interface.py | 23 ++++++------ ltchiptool/util/flash.py | 6 +++ 10 files changed, 176 insertions(+), 74 deletions(-) diff --git a/ltchiptool/commands/flash/read.py b/ltchiptool/commands/flash/read.py index 8727994..5f9f59d 100644 --- a/ltchiptool/commands/flash/read.py +++ b/ltchiptool/commands/flash/read.py @@ -9,7 +9,7 @@ from ltchiptool import Family, SocInterface from ltchiptool.models import FamilyParamType from ltchiptool.util.cli import AutoIntParamType, DevicePortParamType -from ltchiptool.util.flash import FlashConnection +from ltchiptool.util.flash import FlashConnection, FlashMemoryType from ltchiptool.util.logging import graph from ltchiptool.util.misc import sizeof from ltchiptool.util.streams import ClickProgressCallback @@ -62,8 +62,16 @@ @click.option( "-R", "--rom", + "memory", + flag_value=FlashMemoryType.ROM, help="Read from ROM instead of Flash (default: False)", - is_flag=True, +) +@click.option( + "-E", + "--efuse", + "memory", + flag_value=FlashMemoryType.EFUSE, + help="Read from eFuse instead of Flash (default: False)", ) def cli( family: Family, @@ -74,7 +82,7 @@ def cli( length: int, timeout: float, check: bool, - rom: bool, + memory: FlashMemoryType, ): """ Read flash contents to a file. @@ -94,10 +102,8 @@ def cli( soc.flash_set_connection(FlashConnection(device, baudrate)) flash_link_interactive(soc, timeout) - if rom: - max_length = soc.flash_get_rom_size() - else: - max_length = soc.flash_get_size() + memory = memory or FlashMemoryType.FLASH + max_length = soc.flash_get_size(memory) offset = offset or 0 length = length or (max_length - offset) @@ -108,17 +114,14 @@ def cli( f"chip capacity ({sizeof(max_length)})", ) - if rom: - graph(0, f"Reading ROM ({sizeof(length)}) to '{file.name}'") - else: - graph(0, f"Reading {sizeof(length)} @ 0x{offset:X} to '{file.name}'") + graph(0, f"Reading {memory.value} ({sizeof(length)}) to '{file.name}'") with ClickProgressCallback(length) as callback: for chunk in soc.flash_read_raw( offset=offset, length=length, verify=check, - use_rom=rom, + memory=memory, callback=callback, ): file.write(chunk) diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index fd65ef7..690acd6 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -314,6 +314,10 @@ def OnBlur(self, event: wx.FocusEvent, target: wx.Window): if target == self.File: self.file = self.file + def EnableAll(self): + super().EnableAll() + self.DoUpdate(self.Family) + @property def port(self): if not self.ports: @@ -476,10 +480,11 @@ def make_dump_filename(self): return date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") rom = "_rom" if self.operation == FlashOp.READ_ROM else "" + efuse = "_efuse" if self.operation == FlashOp.READ_EFUSE else "" if self.family: - filename = f"ltchiptool_{self.family.code}_{date}{rom}.bin" + filename = f"ltchiptool_{self.family.code}_{date}{rom}{efuse}.bin" else: - filename = f"ltchiptool_dump_{date}{rom}.bin" + filename = f"ltchiptool_dump_{date}{rom}{efuse}.bin" self.auto_file = chname(self.file, filename) verbose(f"Generated dump filename: {self.auto_file}") return self.auto_file @@ -519,6 +524,7 @@ def OnGuideClick(self): caption="Flashing guide", agwStyle=wx.ICON_INFORMATION | wx.OK, ) + # noinspection PyUnresolvedReferences font = wx.Font(wx.FontInfo(10).Family(wx.MODERN)) dialog.SetFont(font) dialog.ShowModal() diff --git a/ltchiptool/gui/work/flash.py b/ltchiptool/gui/work/flash.py index 213a9de..c0ec2ed 100644 --- a/ltchiptool/gui/work/flash.py +++ b/ltchiptool/gui/work/flash.py @@ -6,7 +6,12 @@ from typing import Callable from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection, FlashOp, format_flash_guide +from ltchiptool.util.flash import ( + FlashConnection, + FlashMemoryType, + FlashOp, + format_flash_guide, +) from ltchiptool.util.logging import LoggingHandler from ltchiptool.util.misc import sizeof from ltchiptool.util.streams import ClickProgressCallback @@ -14,6 +19,12 @@ from .base import BaseThread +OP_TO_MEMORY = { + FlashOp.READ: FlashMemoryType.FLASH, + FlashOp.READ_ROM: FlashMemoryType.ROM, + FlashOp.READ_EFUSE: FlashMemoryType.EFUSE, +} + class FlashThread(BaseThread): callback: ClickProgressCallback @@ -54,10 +65,13 @@ def run_impl(self): self.callback = ClickProgressCallback() with self.callback: self._link() - if self.operation == FlashOp.WRITE: - self._do_write() - else: - self._do_read() + match self.operation: + case FlashOp.WRITE: + self._do_write() + case FlashOp.READ | FlashOp.READ_ROM | FlashOp.READ_EFUSE: + self._do_read() + case FlashOp.READ_INFO: + pass self.soc.flash_disconnect() def stop(self): @@ -153,11 +167,9 @@ def _do_read(self): if self.should_stop(): return - self.callback.on_message("Checking flash size...") - if self.operation == FlashOp.READ_ROM: - max_length = self.soc.flash_get_rom_size() - else: - max_length = self.soc.flash_get_size() + memory = OP_TO_MEMORY[self.operation] + self.callback.on_message(f"Checking {memory.value} size...") + max_length = self.soc.flash_get_size(memory) self.length = self.length or max(max_length - self.offset, 0) @@ -175,7 +187,7 @@ def _do_read(self): offset=self.offset, length=self.length, verify=self.verify, - use_rom=self.operation == FlashOp.READ_ROM, + memory=memory, callback=self.callback, ): file.write(chunk) diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index 13e2ee6..b3b0528 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -8,7 +8,7 @@ from ltchiptool import SocInterface from ltchiptool.soc.amb.system import SystemData -from ltchiptool.util.flash import FlashConnection, FlashFeatures +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.intbin import gen2bytes from ltchiptool.util.logging import verbose from ltchiptool.util.streams import ProgressCallback @@ -17,6 +17,8 @@ from .util.ambzcode import AmbZCode from .util.ambztool import ( AMBZ_CHIP_TYPE, + AMBZ_EFUSE_LOGICAL_SIZE, + AMBZ_EFUSE_PHYSICAL_SIZE, AMBZ_FLASH_ADDRESS, AMBZ_ROM_BAUDRATE, AmbZTool, @@ -55,7 +57,6 @@ class AmebaZFlash(SocInterface, ABC): def flash_get_features(self) -> FlashFeatures: return FlashFeatures( can_read_rom=False, - can_read_efuse=False, can_read_info=False, ) @@ -140,33 +141,45 @@ def flash_get_chip_info_string(self) -> str: chip_id = self.chip_info[0] return AMBZ_CHIP_TYPE.get(chip_id, f"Unknown 0x{chip_id:02X}") - def flash_get_size(self) -> int: - if not self.chip_info: - self._read_chip_info() - size_id = self.chip_info[3] - if 0x14 <= size_id <= 0x19: - return 1 << size_id - warning(f"Couldn't process flash ID: got {self.chip_info!r}") - return 0x200000 + def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: + if memory == FlashMemoryType.FLASH: + if not self.chip_info: + self._read_chip_info() + size_id = self.chip_info[3] + if 0x14 <= size_id <= 0x19: + return 1 << size_id + warning(f"Couldn't process flash ID: got {self.chip_info!r}") + return 0x200000 + if memory == FlashMemoryType.EFUSE: + return AMBZ_EFUSE_PHYSICAL_SIZE + AMBZ_EFUSE_LOGICAL_SIZE + raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( self, offset: int, length: int, verify: bool = True, - use_rom: bool = False, + memory: FlashMemoryType = FlashMemoryType.FLASH, callback: ProgressCallback = ProgressCallback(), ) -> Generator[bytes, None, None]: - if use_rom: - self.flash_get_rom_size() + if memory == FlashMemoryType.ROM: + self.flash_get_size(memory) self.flash_connect() assert self.amb - gen = self.amb.flash_read( - offset=offset, - length=length, - hash_check=verify, - ) - yield from callback.update_with(gen) + if memory == FlashMemoryType.FLASH: + gen = self.amb.flash_read( + offset=offset, + length=length, + hash_check=verify, + ) + yield from callback.update_with(gen) + elif memory == FlashMemoryType.EFUSE: + data = self.amb.ram_boot_read( + AmbZCode.read_efuse_raw(offset=0) + + AmbZCode.read_efuse_logical_map(offset=AMBZ_EFUSE_PHYSICAL_SIZE) + + AmbZCode.print_data(length=self.flash_get_size(memory)) + ) + yield data[offset : offset + length] def flash_write_raw( self, diff --git a/ltchiptool/soc/ambz/util/ambzcode.py b/ltchiptool/soc/ambz/util/ambzcode.py index c9fc83e..72d2220 100644 --- a/ltchiptool/soc/ambz/util/ambzcode.py +++ b/ltchiptool/soc/ambz/util/ambzcode.py @@ -152,6 +152,61 @@ def read_chip_id(offset: int = 0) -> bytes: b"\x65\x6d\x00\x00" # EFUSE_OneByteReadROM() ) + inttole32(AMBZ_DATA_ADDRESS + offset) + @staticmethod + def read_efuse_raw(start: int = 0, length: int = 256, offset: int = 0) -> bytes: + # ldr r5, start + # loop: + # ldr r0, CtrlSetting + # movs r1, r5 + # ldr r2, read_data + # adds r2, r2, r5 + # movs r3, #7 + # ldr r4, EFUSE_OneByteReadROM + # blx r4 + # adds r5, r5, #1 + # ldr r0, length + # cmp r5, r0 + # bne loop + # b next + # movs r0, r0 + # CtrlSetting: .word 9902 + # EFUSE_OneByteReadROM: .word 0x6D64+1 + # read_data: .word 0x10003000 + # start: .word 0 + # length: .word 256 + # next: + return ( + ( + b"\x09\x4d\x06\x48" + b"\x29\x00\x07\x4a" + b"\x52\x19\x07\x23" + b"\x04\x4c\xa0\x47" + b"\x6d\x1c\x06\x48" + b"\x85\x42\xf4\xd1" + b"\x0a\xe0\x00\x00" + b"\xae\x26\x00\x00" # CtrlSetting + b"\x65\x6d\x00\x00" # EFUSE_OneByteReadROM() + ) + + inttole32(AMBZ_DATA_ADDRESS + offset) + + inttole32(start) + + inttole32(length) + ) + + @staticmethod + def read_efuse_otp(offset: int = 0) -> bytes: + # ldr r0, read_data + # ldr r3, EFUSE_OTP_Read32B + # blx r3 + # b next + # EFUSE_OTP_Read32B: .word 0x3C20+1 + # read_data: .word 0x10003000 + # next: + return ( + b"\x02\x48\x01\x4b" + b"\x98\x47\x03\xe0" + b"\x21\x3C\x00\x00" # EFUSE_OTP_Read32B() + ) + inttole32(AMBZ_DATA_ADDRESS + offset) + @staticmethod def read_efuse_logical_map(offset: int = 0) -> bytes: # ldr r0, read_data diff --git a/ltchiptool/soc/ambz/util/ambztool.py b/ltchiptool/soc/ambz/util/ambztool.py index 3af56eb..dc88e59 100644 --- a/ltchiptool/soc/ambz/util/ambztool.py +++ b/ltchiptool/soc/ambz/util/ambztool.py @@ -31,6 +31,8 @@ AMBZ_FLASH_ADDRESS = 0x8000000 AMBZ_RAM_ADDRESS = 0x10002000 AMBZ_GREETING_TEXT = b"AmbZTool_Marker!" +AMBZ_EFUSE_PHYSICAL_SIZE = 256 +AMBZ_EFUSE_LOGICAL_SIZE = 512 AMBZ_CHIP_TYPE = { 0xE0: "RTL8710BL", # ??? diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index d91cbf3..ec08a76 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -4,7 +4,7 @@ from typing import IO, Generator, Optional from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection, FlashFeatures +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext @@ -77,18 +77,19 @@ def flash_get_chip_info_string(self) -> str: ] return " / ".join(items) - def flash_get_size(self) -> int: - return 0x200000 - - def flash_get_rom_size(self) -> int: - return 384 * 1024 + def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: + if memory == FlashMemoryType.FLASH: + return 0x200000 + if memory == FlashMemoryType.ROM: + return 384 * 1024 + raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( self, offset: int, length: int, verify: bool = True, - use_rom: bool = False, + memory: FlashMemoryType = FlashMemoryType.FLASH, callback: ProgressCallback = ProgressCallback(), ) -> Generator[bytes, None, None]: self.flash_connect() @@ -96,7 +97,7 @@ def flash_read_raw( gen = self.amb.memory_read( offset=offset, length=length, - use_flash=not use_rom, + use_flash=memory == FlashMemoryType.FLASH, hash_check=verify, yield_size=1024, ) diff --git a/ltchiptool/soc/bk72xx/flash.py b/ltchiptool/soc/bk72xx/flash.py index 36c5e1d..450b111 100644 --- a/ltchiptool/soc/bk72xx/flash.py +++ b/ltchiptool/soc/bk72xx/flash.py @@ -9,7 +9,7 @@ from bk7231tools.serial import BK7231Serial from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection, FlashFeatures +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.intbin import inttole32 from ltchiptool.util.logging import VERBOSE, verbose from ltchiptool.util.streams import ProgressCallback @@ -108,26 +108,27 @@ def flash_get_chip_info_string(self) -> str: ] return " / ".join(items) - def flash_get_size(self) -> int: - return 0x200000 - - def flash_get_rom_size(self) -> int: - self.flash_connect() - if self.bk.chip_info != "0x7231c": - raise NotImplementedError("Only BK7231N has built-in ROM") - return 16 * 1024 + def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: + if memory == FlashMemoryType.FLASH: + return 0x200000 + if memory == FlashMemoryType.ROM: + self.flash_connect() + if self.bk.chip_info != "0x7231c": + raise NotImplementedError("Only BK7231N has built-in ROM") + return 16 * 1024 + raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( self, offset: int, length: int, verify: bool = True, - use_rom: bool = False, + memory: FlashMemoryType = FlashMemoryType.FLASH, callback: ProgressCallback = ProgressCallback(), ) -> Generator[bytes, None, None]: self.flash_connect() - if use_rom: + if memory == FlashMemoryType.ROM: if offset % 4 != 0 or length % 4 != 0: raise ValueError("Offset and length must be 4-byte aligned") for address in range(offset, offset + length, 4): @@ -135,6 +136,8 @@ def flash_read_raw( yield inttole32(reg) callback.on_update(4) return + elif memory != FlashMemoryType.FLASH: + raise NotImplementedError("Memory type not readable via UART") crc_offset = offset crc_length = 0 diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 87a8729..7dbec7f 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -5,7 +5,7 @@ from ltchiptool import Board, Family from ltchiptool.models import OTAType -from ltchiptool.util.flash import FlashConnection, FlashFeatures +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.fwbinary import FirmwareBinary from ltchiptool.util.streams import ProgressCallback from uf2tool import UploadContext @@ -148,33 +148,34 @@ def flash_disconnect(self) -> None: raise NotImplementedError() def flash_get_chip_info_string(self) -> str: - """Read chip info from the protocol as a string.""" + """Read chip info **summary** from the protocol as a string.""" raise NotImplementedError() - def flash_get_size(self) -> int: - """Retrieve the flash size, in bytes.""" + def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: + """Retrieve the size of specified memory, in bytes. + Raises NotImplementedError() if the memory type is not available + or not readable.""" raise NotImplementedError() def flash_get_rom_size(self) -> int: - """Retrieve the ROM size, in bytes. Raises NotImplementedError() if ROM is - not available or not readable.""" - raise NotImplementedError("No ROM, or reading not possible via UART") + """Deprecated, use flash_get_size(FlashMemoryType.ROM).""" + return self.flash_get_size(FlashMemoryType.ROM) def flash_read_raw( self, offset: int, length: int, verify: bool = True, - use_rom: bool = False, + memory: FlashMemoryType = FlashMemoryType.FLASH, callback: ProgressCallback = ProgressCallback(), ) -> Generator[bytes, None, None]: """ - Read 'length' bytes from the flash, starting at 'offset'. + Read 'length' bytes from the chip, starting at 'offset'. :param offset: start memory offset :param length: length of data to read :param verify: whether to verify checksums - :param use_rom: whether to read from ROM instead of Flash + :param memory: type of memory to read from :param callback: reading progress callback :return: a generator yielding the chunks being read """ @@ -189,7 +190,7 @@ def flash_write_raw( callback: ProgressCallback = ProgressCallback(), ) -> None: """ - Write 'length' bytes (represented by 'data'), starting at 'offset' of the flash. + Write 'length' bytes (represented by 'data') to the chip, starting at 'offset'. :param offset: start memory offset :param length: length of data to write diff --git a/ltchiptool/util/flash.py b/ltchiptool/util/flash.py index 0a9c4e5..ec34410 100644 --- a/ltchiptool/util/flash.py +++ b/ltchiptool/util/flash.py @@ -21,6 +21,12 @@ class FlashOp(Enum): READ_INFO = "read_info" +class FlashMemoryType(Enum): + FLASH = "Flash" + ROM = "ROM" + EFUSE = "eFuse" + + @dataclass class FlashConnection: port: str From d1414551747af8b315884432bcf8b80fd49426ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 4 Nov 2023 19:24:50 +0100 Subject: [PATCH 16/24] [gui] Call OnUpdate on main thread only --- ltchiptool/gui/base/panel.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ltchiptool/gui/base/panel.py b/ltchiptool/gui/base/panel.py index d89769b..4225009 100644 --- a/ltchiptool/gui/base/panel.py +++ b/ltchiptool/gui/base/panel.py @@ -1,17 +1,22 @@ # Copyright (c) Kuba Szczodrzyński 2023-1-3. +import threading from logging import warning +from queue import Queue from typing import Any, Callable, Tuple import wx import wx.xrc +from ltchiptool.gui.utils import on_event + from .window import BaseWindow # noinspection PyPep8Naming class BasePanel(wx.Panel, BaseWindow): _components: list[wx.Window] + _events: Queue[wx.Window | None] def __init__(self, parent: wx.Window, frame): super().__init__(parent) @@ -19,6 +24,8 @@ def __init__(self, parent: wx.Window, frame): self.Frame = frame self.Xrc: wx.xrc.XmlResource = frame.Xrc self._components = [] + self._events = Queue() + self.Bind(wx.EVT_IDLE, self.OnIdle) def OnShow(self): self.OnUpdate() @@ -41,10 +48,18 @@ def _OnUpdate(self, event: wx.Event | None): def DoUpdate(self, target: wx.Window = None): if self._in_update: return + if threading.current_thread() != threading.main_thread(): + self._events.put(target) + return self._in_update = True self.OnUpdate(target) self._in_update = False + @on_event + def OnIdle(self): + while not self._events.empty(): + self.OnUpdate(self._events.get()) + def OnUpdate(self, target: wx.Window = None): pass @@ -131,7 +146,7 @@ def EnableAll(self): return for window in self._components: window.Enable() - self.OnUpdate() + self.DoUpdate() def DisableAll(self): if self.is_closing: From 54af7d9e5db12acac88e10bfe9723073e52d0664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 4 Nov 2023 19:26:19 +0100 Subject: [PATCH 17/24] [ambz] Implement reading chip info --- ltchiptool/gui/base/window.py | 14 ++++++ ltchiptool/gui/panels/flash.py | 46 ++++++++++++------ ltchiptool/gui/work/flash.py | 18 +++++-- ltchiptool/soc/ambz/flash.py | 89 +++++++++++++++++++++++++++------- ltchiptool/soc/interface.py | 6 ++- 5 files changed, 136 insertions(+), 37 deletions(-) diff --git a/ltchiptool/gui/base/window.py b/ltchiptool/gui/base/window.py index 32aae3b..685e339 100644 --- a/ltchiptool/gui/base/window.py +++ b/ltchiptool/gui/base/window.py @@ -3,6 +3,7 @@ import sys from os.path import dirname, isfile, join +import wx.lib.agw.genericmessagedialog as GMD import wx.xrc from ltchiptool.gui.colors import ColorPalette @@ -77,3 +78,16 @@ def EnableAll(self): def DisableAll(self): pass + + def MessageDialogMonospace(self, message: str, caption: str): + dialog = GMD.GenericMessageDialog( + parent=self, + message=message, + caption=caption, + agwStyle=wx.ICON_INFORMATION | wx.OK, + ) + # noinspection PyUnresolvedReferences + font = wx.Font(wx.FontInfo(10).Family(wx.MODERN)) + dialog.SetFont(font) + dialog.ShowModal() + dialog.Destroy() diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index 690acd6..777e51c 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -8,8 +8,8 @@ import wx import wx.adv -import wx.lib.agw.genericmessagedialog as GMD import wx.xrc +from prettytable import PrettyTable from ltchiptool import Family, SocInterface from ltchiptool.gui.utils import int_or_zero, on_event, with_target @@ -31,6 +31,7 @@ class FlashPanel(BasePanel): prev_file: str | None = None auto_file: str | None = None delayed_port: str | None = None + chip_info: list[tuple[str, str]] | None = None def __init__(self, parent: wx.Window, frame): super().__init__(parent, frame) @@ -52,7 +53,7 @@ def __init__(self, parent: wx.Window, frame): self.Family = self.BindComboBox("combo_family") self.Guide = self.BindButton("button_guide", self.OnGuideClick) self.Docs = self.BindButton("button_docs", self.OnDocsClick) - self.BindButton("button_browse", self.OnBrowseClick) + self.Browse = self.BindButton("button_browse", self.OnBrowseClick) self.Start: wx.adv.CommandLinkButton = self.BindButton( "button_start", self.OnStartClick ) @@ -143,6 +144,11 @@ def OnShow(self): self.StartWork(PortWatcher(self.OnPortsUpdated), freeze_ui=False) def OnUpdate(self, target: wx.Window = None): + if self.chip_info: + chip_info = self.chip_info + self.chip_info = None + self.ShowChipInfo(chip_info) + if target == self.Family: # update components based on SocInterface feature set soc = self.soc @@ -174,13 +180,15 @@ def OnUpdate(self, target: wx.Window = None): writing = self.operation == FlashOp.WRITE reading = not writing + reading_info = self.operation == FlashOp.READ_INFO is_uf2 = self.detection is not None and self.detection.is_uf2 need_offset = self.detection is not None and self.detection.need_offset + force_auto = (writing and is_uf2) or reading_info auto = self.auto_detect manual = not auto - if writing and manual and is_uf2: + if manual and force_auto: self.auto_detect = auto = True manual = False @@ -209,7 +217,9 @@ def OnUpdate(self, target: wx.Window = None): self.SkipText.Enable(writing) self.Skip.Enable(writing and manual) self.Length.Enable(manual) - self.AutoDetect.Enable(reading or not is_uf2) + self.AutoDetect.Enable(not force_auto) + self.File.Enable(not reading_info) + self.Browse.Enable(not reading_info) errors = [] warnings = [] @@ -267,7 +277,7 @@ def OnUpdate(self, target: wx.Window = None): self.FileText.SetLabel("Output file") self.LengthText.SetLabel("Reading length") self.FileType.ChangeValue("") - if not self.file: + if not self.file and not reading_info: errors.append("Choose an output file") self.skip = 0 if auto: @@ -508,6 +518,20 @@ def OnPortsUpdated(self, ports: list[tuple[str, bool, str]]): self.port = user_port self.delayed_port = None + def OnChipInfoFull(self, chip_info: list[tuple[str, str]]): + self.chip_info = chip_info + + def ShowChipInfo(self, chip_info: list[tuple[str, str]]): + table = PrettyTable() + table.field_names = ["Name", "Value"] + table.align = "l" + for key, value in chip_info: + table.add_row([key, value]) + self.MessageDialogMonospace( + message=table.get_string(), + caption="Chip info", + ) + @on_event def OnRescanClick(self): self.OnPortsUpdated(list_serial_ports()) @@ -518,17 +542,10 @@ def OnGuideClick(self): if not guide: self.Guide.Disable() return - dialog = GMD.GenericMessageDialog( - parent=self, + self.MessageDialogMonospace( message="\n".join(format_flash_guide(self.soc)), caption="Flashing guide", - agwStyle=wx.ICON_INFORMATION | wx.OK, ) - # noinspection PyUnresolvedReferences - font = wx.Font(wx.FontInfo(10).Family(wx.MODERN)) - dialog.SetFont(font) - dialog.ShowModal() - dialog.Destroy() @on_event def OnDocsClick(self): @@ -583,7 +600,8 @@ def OnStartClick(self): length=self.length, verify=True, ctx=self.detection and self.detection.get_uf2_ctx(), - on_chip_info=self.Start.SetNote, + on_chip_info_summary=self.Start.SetNote, + on_chip_info_full=self.OnChipInfoFull, ) self.StartWork(work) self.Start.SetNote("") diff --git a/ltchiptool/gui/work/flash.py b/ltchiptool/gui/work/flash.py index c0ec2ed..b762fb7 100644 --- a/ltchiptool/gui/work/flash.py +++ b/ltchiptool/gui/work/flash.py @@ -41,7 +41,8 @@ def __init__( length: int | None, verify: bool, ctx: UploadContext | None, - on_chip_info: Callable[[str], None], + on_chip_info_summary: Callable[[str], None], + on_chip_info_full: Callable[[list[tuple[str, str]]], None], ): super().__init__() self.port = port @@ -54,7 +55,8 @@ def __init__( self.length = length self.verify = verify self.ctx = ctx - self.on_chip_info = on_chip_info + self.on_chip_info_summary = on_chip_info_summary + self.on_chip_info_full = on_chip_info_full def run_impl(self): debug( @@ -65,13 +67,14 @@ def run_impl(self): self.callback = ClickProgressCallback() with self.callback: self._link() + self.callback.on_message("Starting operation...") match self.operation: case FlashOp.WRITE: self._do_write() case FlashOp.READ | FlashOp.READ_ROM | FlashOp.READ_EFUSE: self._do_read() case FlashOp.READ_INFO: - pass + self._do_info() self.soc.flash_disconnect() def stop(self): @@ -112,7 +115,7 @@ def _link(self): return self.callback.on_message("Reading chip info...") chip_info = self.soc.flash_get_chip_info_string() - self.on_chip_info(f"Chip info: {chip_info}") + self.on_chip_info_summary(f"Chip info: {chip_info}") def _do_write(self): if self.should_stop(): @@ -194,3 +197,10 @@ def _do_read(self): if self.should_stop(): break file.close() + + def _do_info(self): + if self.should_stop(): + return + + chip_info = self.soc.flash_get_chip_info() + self.on_chip_info_full(chip_info) diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index b3b0528..0a11e81 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -4,13 +4,14 @@ from io import BytesIO from logging import debug, warning from time import sleep -from typing import IO, Generator, List, Optional, Union +from typing import IO, Generator, List, Optional, Tuple, Union from ltchiptool import SocInterface from ltchiptool.soc.amb.system import SystemData from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType -from ltchiptool.util.intbin import gen2bytes +from ltchiptool.util.intbin import gen2bytes, letoint from ltchiptool.util.logging import verbose +from ltchiptool.util.misc import sizeof from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext @@ -52,12 +53,13 @@ # noinspection PyProtectedMember class AmebaZFlash(SocInterface, ABC): amb: Optional[AmbZTool] = None - chip_info: bytes = None + chip_id: int = None + flash_id: bytes = None + info: bytes = None def flash_get_features(self) -> FlashFeatures: return FlashFeatures( can_read_rom=False, - can_read_info=False, ) def flash_get_guide(self) -> List[Union[str, list]]: @@ -125,30 +127,81 @@ def flash_disconnect(self) -> None: if self.conn: self.conn.linked = False - def _read_chip_info(self) -> None: + def flash_get_chip_info(self) -> List[Tuple[str, str]]: self.flash_connect() assert self.amb - self.chip_info = self.amb.ram_boot_read( - AmbZCode.read_chip_id(offset=0) - + AmbZCode.read_flash_id(offset=1) - + AmbZCode.print_data(length=4) + if not self.info: + info = self.amb.ram_boot_read( + AmbZCode.read_efuse_raw(offset=0) + + AmbZCode.read_efuse_logical_map(offset=256) + + AmbZCode.read_flash_id(offset=256 + 512) + + AmbZCode.print_data(length=256 + 512 + 16) + + AmbZCode.print_data(length=16, address=0x400001F0) + + AmbZCode.print_data(length=128, address=AMBZ_FLASH_ADDRESS | 0x9000) + ) + if len(info) != 256 + 512 + 16 + 16 + 128: + raise RuntimeError( + f"Read data length invalid: " f"{len(info)} != {256+512+16+16+128}" + ) + self.info = info + self.chip_id = info[0xF8] + self.flash_id = info[256 + 512 : 256 + 512 + 3] + chip_type = AMBZ_CHIP_TYPE.get(self.chip_id, f"Unknown 0x{self.chip_id:02X}") + size_id = self.flash_id[2] + if 0x14 <= size_id <= 0x19: + flash_size = sizeof(1 << size_id) + else: + flash_size = "Unknown" + syscfg0 = letoint(self.info[256 + 512 + 16 + 0 : 256 + 512 + 16 + 0 + 4]) + syscfg1 = letoint(self.info[256 + 512 + 16 + 4 : 256 + 512 + 16 + 4 + 4]) + syscfg2 = letoint(self.info[256 + 512 + 16 + 8 : 256 + 512 + 16 + 8 + 4]) + system_data = self.info[256 + 512 + 16 + 16 : 256 + 512 + 16 + 16 + 128].ljust( + 4096, b"\xFF" ) - debug(f"Received chip info: {self.chip_info.hex()}") + system = SystemData.unpack(system_data) + return [ + ("Chip Type", chip_type), + ("MAC Address", self.info[0x21A : 0x21A + 6].hex(":").upper()), + ("", ""), + ("Flash ID", self.flash_id.hex(" ").upper()), + ("Flash Size (real)", flash_size), + ("", ""), + ("OTA2 Address", f"0x{system.ota2_address:X}"), + ("RDP Address", f"0x{system.rdp_address:X}"), + ("RDP Length", f"0x{system.rdp_length:X}"), + ("Flash SPI Mode", system.flash_mode.name), + ("Flash SPI Speed", system.flash_speed.name[2:]), + ("Flash ID (system)", f"{system.flash_id:04X}"), + ("Flash Size (system)", sizeof(system.flash_size_mb << 20)), + ("LOG UART Baudrate", system.baudrate), + ("", ""), + ("SYSCFG 0/1/2", f"{syscfg0:08X} / {syscfg1:08X} / {syscfg2:08X}"), + ("ROM Version", f"V{syscfg2 >> 4}.{syscfg2 & 0xF}"), + ("CUT Version", f"{(syscfg0 & 0xFF) >> 4:X}"), + ] def flash_get_chip_info_string(self) -> str: - if not self.chip_info: - self._read_chip_info() - chip_id = self.chip_info[0] - return AMBZ_CHIP_TYPE.get(chip_id, f"Unknown 0x{chip_id:02X}") + if not self.chip_id or not self.flash_id: + self.flash_connect() + assert self.amb + data = self.amb.ram_boot_read( + AmbZCode.read_chip_id(offset=0) + + AmbZCode.read_flash_id(offset=1) + + AmbZCode.print_data(length=4) + ) + self.chip_id = data[0] + self.flash_id = data[1:4] + debug(f"Received chip info: {data.hex()}") + return AMBZ_CHIP_TYPE.get(self.chip_id, f"Unknown 0x{self.chip_id:02X}") def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: if memory == FlashMemoryType.FLASH: - if not self.chip_info: - self._read_chip_info() - size_id = self.chip_info[3] + if not self.flash_id: + self.flash_get_chip_info_string() + size_id = self.flash_id[2] if 0x14 <= size_id <= 0x19: return 1 << size_id - warning(f"Couldn't process flash ID: got {self.chip_info!r}") + warning(f"Couldn't process flash ID: got {self.flash_id.hex()}") return 0x200000 if memory == FlashMemoryType.EFUSE: return AMBZ_EFUSE_PHYSICAL_SIZE + AMBZ_EFUSE_LOGICAL_SIZE diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 7dbec7f..0858047 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -1,7 +1,7 @@ # Copyright (c) Kuba Szczodrzyński 2022-07-29. from abc import ABC -from typing import IO, Dict, Generator, List, Optional, Union +from typing import IO, Dict, Generator, List, Optional, Tuple, Union from ltchiptool import Board, Family from ltchiptool.models import OTAType @@ -147,6 +147,10 @@ def flash_disconnect(self) -> None: """Close the serial port, if it's open.""" raise NotImplementedError() + def flash_get_chip_info(self) -> List[Tuple[str, str]]: + """Read all available chip info as a dictionary.""" + raise NotImplementedError() + def flash_get_chip_info_string(self) -> str: """Read chip info **summary** from the protocol as a string.""" raise NotImplementedError() From 32eb2ee4d711f2ec172481fa43edcef7072207a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 5 Nov 2023 18:44:06 +0100 Subject: [PATCH 18/24] [bk72xx] Implement reading eFuse and chip info --- ltchiptool/soc/bk72xx/flash.py | 94 +++++++++++++++++++++++++++++----- pyproject.toml | 2 +- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/ltchiptool/soc/bk72xx/flash.py b/ltchiptool/soc/bk72xx/flash.py index 450b111..ce6238e 100644 --- a/ltchiptool/soc/bk72xx/flash.py +++ b/ltchiptool/soc/bk72xx/flash.py @@ -1,17 +1,20 @@ # Copyright (c) Kuba Szczodrzyński 2022-07-29. import logging +import struct from abc import ABC from binascii import crc32 from logging import DEBUG, debug -from typing import IO, Generator, List, Optional, Union +from typing import IO, Generator, List, Optional, Tuple, Union from bk7231tools.serial import BK7231Serial +from bk7231tools.serial.protocol import CHIP_BY_CRC from ltchiptool import SocInterface from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType -from ltchiptool.util.intbin import inttole32 +from ltchiptool.util.intbin import gen2bytes, inttole32 from ltchiptool.util.logging import VERBOSE, verbose +from ltchiptool.util.misc import sizeof from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext @@ -34,15 +37,16 @@ "In order to do that, you need to bridge CEN pin to GND with a wire.", ] +SCTRL_EFUSE_CTRL = 0x00800074 +SCTRL_EFUSE_OPTR = 0x00800078 + class BK72XXFlash(SocInterface, ABC): bk: Optional[BK7231Serial] = None + info: List[Tuple[str, str]] = None def flash_get_features(self) -> FlashFeatures: - return FlashFeatures( - can_read_efuse=False, - can_read_info=False, - ) + return FlashFeatures() def flash_get_guide(self) -> List[Union[str, list]]: return BK72XX_GUIDE @@ -99,23 +103,71 @@ def flash_disconnect(self) -> None: if self.conn: self.conn.linked = False - def flash_get_chip_info_string(self) -> str: + def flash_get_chip_info(self) -> List[Tuple[str, str]]: self.flash_connect() - items = [ - self.bk.chip_info, - f"Flash ID: {self.bk.flash_id.hex(' ', -1) if self.bk.flash_id else None}", - f"Protocol: {self.bk.protocol_type.name}", + if self.info: + return self.info + crc = self.bk.read_flash_range_crc(0, 256) ^ 0xFFFFFFFF + if crc in CHIP_BY_CRC: + chip_type, boot_version = CHIP_BY_CRC[crc] + else: + chip_type = f"Unrecognized (0x{crc:08X})" + boot_version = None + self.info = [ + ("Chip Type", chip_type), + ("Bootloader Version", boot_version or "Unspecified"), ] - return " / ".join(items) + if self.bk.check_protocol(0x11): # CMD_ReadBootVersion + self.info.append(("Bootloader Message", self.bk.chip_info)) + if self.bk.check_protocol(0x03): # CMD_ReadReg + chip_id = hex(self.bk.register_read(0x800000)) # SCTRL_CHIP_ID + self.info.append(("Chip ID", chip_id)) + self.info += [ + ("Protocol Type", self.bk.protocol_type.name), + ] + if chip_type == "BK7231N": + tlv = self.bk.read_flash_4k(0x1D0000) + elif chip_type == "BK7231T": + tlv = self.bk.read_flash_4k(0x1E0000) + else: + tlv = None + if tlv and tlv[0x1C:0x24] == b"\x02\x11\x11\x11\x06\x00\x00\x00": + self.info += [ + ("", ""), + ("MAC Address", tlv and tlv[0x24:0x2A].hex(":").upper() or "Unknown"), + ] + if self.bk.check_protocol(0x0E, True): # # CMD_FlashGetMID + flash_id = self.bk.flash_read_id() + self.info += [ + ("", ""), + ("Flash ID", flash_id["id"].hex(" ").upper()), + ("Flash Size (real)", sizeof(flash_id["size"])), + ] + if self.bk.check_protocol(0x03): # CMD_ReadReg + efuse = gen2bytes(self.flash_read_raw(0, 16, memory=FlashMemoryType.EFUSE)) + coeffs = struct.unpack(" str: + self.flash_connect() + return self.bk.chip_info def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: if memory == FlashMemoryType.FLASH: return 0x200000 + self.flash_connect() if memory == FlashMemoryType.ROM: - self.flash_connect() - if self.bk.chip_info != "0x7231c": + if not self.bk.check_protocol(0x03): # CMD_ReadReg raise NotImplementedError("Only BK7231N has built-in ROM") return 16 * 1024 + if memory == FlashMemoryType.EFUSE: + if not self.bk.check_protocol(0x01): # CMD_WriteReg + raise NotImplementedError("Only BK7231N can read eFuse via UART") + return 32 raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( @@ -136,6 +188,20 @@ def flash_read_raw( yield inttole32(reg) callback.on_update(4) return + elif memory == FlashMemoryType.EFUSE: + for addr in range(offset, offset + length): + reg = self.bk.register_read(SCTRL_EFUSE_CTRL) + reg = (reg & ~0x1F02) | (addr << 8) | 1 + self.bk.register_write(SCTRL_EFUSE_CTRL, reg) + while reg & 1: + reg = self.bk.register_read(SCTRL_EFUSE_CTRL) + reg = self.bk.register_read(SCTRL_EFUSE_OPTR) + if reg & 0x100: + yield bytes([reg & 0xFF]) + callback.on_update(1) + else: + raise RuntimeError(f"eFuse data {addr} invalid: {hex(reg)}") + return elif memory != FlashMemoryType.FLASH: raise NotImplementedError("Memory type not readable via UART") diff --git a/pyproject.toml b/pyproject.toml index 79b37da..9ed7cd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ click = "^8.1.3" colorama = "^0.4.5" importlib-metadata = "*" prettytable = "^3.3.0" -bk7231tools = "^1.3.6" +bk7231tools = "^1.4.0" xmodem = "^0.4.6" wxPython = {version = "^4.2.0", optional = true} pywin32 = {version = "^305", optional = true, markers = "sys_platform == 'win32'"} From a9bdd72970b26b3a15d1dc40ee310534a42d1c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 7 Nov 2023 16:42:51 +0100 Subject: [PATCH 19/24] [ambz] Convert eFuse logical map locally --- ltchiptool/soc/amb/efuse.py | 47 +++++++++++++++++++++ ltchiptool/soc/ambz/flash.py | 75 +++++++++++++++++++++++----------- ltchiptool/soc/bk72xx/flash.py | 2 +- 3 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 ltchiptool/soc/amb/efuse.py diff --git a/ltchiptool/soc/amb/efuse.py b/ltchiptool/soc/amb/efuse.py new file mode 100644 index 0000000..cc1a147 --- /dev/null +++ b/ltchiptool/soc/amb/efuse.py @@ -0,0 +1,47 @@ +# Copyright (c) Kuba Szczodrzyński 2023-11-7. + +from io import BytesIO + + +def efuse_physical_to_logical(efuse: bytes) -> bytes: + efuse = BytesIO(efuse) + logical = bytearray([0xFF] * 512) + + def copy_words(cell_idx: int, word_map: int): + logi_addr = 8 * cell_idx + for i in range(0, 4): + if not (word_map & (1 << i)): + logical[logi_addr] = efuse.read(1)[0] + logical[logi_addr + 1] = efuse.read(1)[0] + logi_addr += 2 + + while True: + header = efuse.read(1)[0] + if header == 0xFF: + break + if (header & 0x1F) != 0xF: + copy_words( + cell_idx=header >> 4, + word_map=header & 0xF, + ) + continue + + header_ext = efuse.read(1)[0] + if (header_ext & 0xF) == 0xF: + continue + if header_ext & 0x80: + if not (header_ext & 1): + efuse.read(2) + if not (header_ext & 2): + efuse.read(2) + if not (header_ext & 4): + efuse.read(2) + if not (header_ext & 8): + efuse.read(2) + continue + + copy_words( + cell_idx=(header >> 5) | ((header_ext & 0xF0) >> 1), + word_map=header_ext & 0xF, + ) + return logical diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index 0a11e81..b2c64e8 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -6,7 +6,10 @@ from time import sleep from typing import IO, Generator, List, Optional, Tuple, Union +from hexdump import hexdump + from ltchiptool import SocInterface +from ltchiptool.soc.amb.efuse import efuse_physical_to_logical from ltchiptool.soc.amb.system import SystemData from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.intbin import gen2bytes, letoint @@ -55,7 +58,7 @@ class AmebaZFlash(SocInterface, ABC): amb: Optional[AmbZTool] = None chip_id: int = None flash_id: bytes = None - info: bytes = None + info: List[Tuple[str, str]] = None def flash_get_features(self) -> FlashFeatures: return FlashFeatures( @@ -127,41 +130,61 @@ def flash_disconnect(self) -> None: if self.conn: self.conn.linked = False + @staticmethod + def _check_efuse(physical: bytes, logical: bytes) -> None: + logical_read = logical + logical_conv = efuse_physical_to_logical(physical) + if logical_read != logical_conv: + warning("eFuse Logical Map different from ROM and local conversion!") + print("Read:") + hexdump(logical_read) + print("Converted:") + hexdump(logical_conv) + def flash_get_chip_info(self) -> List[Tuple[str, str]]: + if self.info: + return self.info self.flash_connect() assert self.amb - if not self.info: - info = self.amb.ram_boot_read( - AmbZCode.read_efuse_raw(offset=0) - + AmbZCode.read_efuse_logical_map(offset=256) - + AmbZCode.read_flash_id(offset=256 + 512) - + AmbZCode.print_data(length=256 + 512 + 16) - + AmbZCode.print_data(length=16, address=0x400001F0) - + AmbZCode.print_data(length=128, address=AMBZ_FLASH_ADDRESS | 0x9000) + + data = self.amb.ram_boot_read( + AmbZCode.read_efuse_raw(offset=0) + + AmbZCode.read_efuse_logical_map(offset=256) + + AmbZCode.read_flash_id(offset=256 + 512) + + AmbZCode.print_data(length=256 + 512 + 16) + + AmbZCode.print_data(length=16, address=0x400001F0) + + AmbZCode.print_data(length=128, address=AMBZ_FLASH_ADDRESS | 0x9000) + ) + if len(data) != 256 + 512 + 16 + 16 + 128: + raise RuntimeError( + f"Read data length invalid: " f"{len(data)} != {256+512+16+16+128}" ) - if len(info) != 256 + 512 + 16 + 16 + 128: - raise RuntimeError( - f"Read data length invalid: " f"{len(info)} != {256+512+16+16+128}" - ) - self.info = info - self.chip_id = info[0xF8] - self.flash_id = info[256 + 512 : 256 + 512 + 3] + + efuse_phys = data[0:256] + efuse_logi = data[256:768] + self.flash_id = data[768 : 768 + 3] + self.chip_id = efuse_phys[0xF8] + self._check_efuse(efuse_phys, efuse_logi) + chip_type = AMBZ_CHIP_TYPE.get(self.chip_id, f"Unknown 0x{self.chip_id:02X}") size_id = self.flash_id[2] if 0x14 <= size_id <= 0x19: flash_size = sizeof(1 << size_id) else: flash_size = "Unknown" - syscfg0 = letoint(self.info[256 + 512 + 16 + 0 : 256 + 512 + 16 + 0 + 4]) - syscfg1 = letoint(self.info[256 + 512 + 16 + 4 : 256 + 512 + 16 + 4 + 4]) - syscfg2 = letoint(self.info[256 + 512 + 16 + 8 : 256 + 512 + 16 + 8 + 4]) - system_data = self.info[256 + 512 + 16 + 16 : 256 + 512 + 16 + 16 + 128].ljust( + + syscfg0 = letoint(data[256 + 512 + 16 + 0 : 256 + 512 + 16 + 0 + 4]) + syscfg1 = letoint(data[256 + 512 + 16 + 4 : 256 + 512 + 16 + 4 + 4]) + syscfg2 = letoint(data[256 + 512 + 16 + 8 : 256 + 512 + 16 + 8 + 4]) + + system_data = data[256 + 512 + 16 + 16 : 256 + 512 + 16 + 16 + 128].ljust( 4096, b"\xFF" ) system = SystemData.unpack(system_data) + return [ ("Chip Type", chip_type), - ("MAC Address", self.info[0x21A : 0x21A + 6].hex(":").upper()), + ("MAC Address", efuse_logi[0x11A : 0x11A + 6].hex(":").upper()), ("", ""), ("Flash ID", self.flash_id.hex(" ").upper()), ("Flash Size (real)", flash_size), @@ -204,7 +227,7 @@ def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int warning(f"Couldn't process flash ID: got {self.flash_id.hex()}") return 0x200000 if memory == FlashMemoryType.EFUSE: - return AMBZ_EFUSE_PHYSICAL_SIZE + AMBZ_EFUSE_LOGICAL_SIZE + return AMBZ_EFUSE_PHYSICAL_SIZE raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( @@ -230,7 +253,13 @@ def flash_read_raw( data = self.amb.ram_boot_read( AmbZCode.read_efuse_raw(offset=0) + AmbZCode.read_efuse_logical_map(offset=AMBZ_EFUSE_PHYSICAL_SIZE) - + AmbZCode.print_data(length=self.flash_get_size(memory)) + + AmbZCode.print_data( + length=AMBZ_EFUSE_PHYSICAL_SIZE + AMBZ_EFUSE_LOGICAL_SIZE + ) + ) + self._check_efuse( + physical=data[0:AMBZ_EFUSE_PHYSICAL_SIZE], + logical=data[AMBZ_EFUSE_PHYSICAL_SIZE:], ) yield data[offset : offset + length] diff --git a/ltchiptool/soc/bk72xx/flash.py b/ltchiptool/soc/bk72xx/flash.py index ce6238e..23aafeb 100644 --- a/ltchiptool/soc/bk72xx/flash.py +++ b/ltchiptool/soc/bk72xx/flash.py @@ -104,9 +104,9 @@ def flash_disconnect(self) -> None: self.conn.linked = False def flash_get_chip_info(self) -> List[Tuple[str, str]]: - self.flash_connect() if self.info: return self.info + self.flash_connect() crc = self.bk.read_flash_range_crc(0, 256) ^ 0xFFFFFFFF if crc in CHIP_BY_CRC: chip_type, boot_version = CHIP_BY_CRC[crc] From c893a8f2b348298e0d59ac69333138b2aac956d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 7 Nov 2023 16:46:30 +0100 Subject: [PATCH 20/24] [ambz2] Implement running code in SRAM, add ROM console --- ltchiptool/soc/ambz2/util/ambz2tool.py | 186 ++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 6 deletions(-) diff --git a/ltchiptool/soc/ambz2/util/ambz2tool.py b/ltchiptool/soc/ambz2/util/ambz2tool.py index 954fd26..a5f56be 100644 --- a/ltchiptool/soc/ambz2/util/ambz2tool.py +++ b/ltchiptool/soc/ambz2/util/ambz2tool.py @@ -6,18 +6,49 @@ from logging import debug, warning from math import ceil from time import time -from typing import IO, Callable, Generator, List, Optional +from typing import IO, Callable, Generator, List, Optional, Tuple import click +from hexdump import hexdump, restore +from serial import Serial from xmodem import XMODEM -from ltchiptool.util.intbin import align_down +from ltchiptool.util.cli import DevicePortParamType +from ltchiptool.util.intbin import align_down, biniter, gen2bytes, letoint, pad_data from ltchiptool.util.logging import LoggingHandler, verbose from ltchiptool.util.misc import retry_catching, retry_generator from ltchiptool.util.serialtool import SerialToolBase _T_XmodemCB = Optional[Callable[[int, int, int], None]] +AMBZ2_FALLBACK_CMD = b"Rtk8710C\n" +AMBZ2_FALLBACK_RESP = [ + b"\r\n$8710c>" * 2, + b"Rtk8710C\r\nCommand NOT found.\r\n$8710c>", +] +USED_COMMANDS = [ + "ping", + "disc", + "ucfg", + "DW", + "DB", + "EW", + "EB", + "WDTRST", + "hashq", + "fwd", + "fwdram", +] + +AMBZ2_CODE_ADDR = 0x10037000 +AMBZ2_DATA_ADDR = 0x10038000 +AMBZ2_EFUSE_PHYSICAL_SIZE = 512 +AMBZ2_EFUSE_LOGICAL_SIZE = 512 + +AMBZ2_CHIP_TYPE = { + 0xFE: "RTL87x0CF", +} + class AmbZ2FlashMode(IntEnum): RTL8720CX_CM = 0 # PIN_A7_A12 @@ -38,6 +69,8 @@ class AmbZ2Tool(SerialToolBase): flash_mode: AmbZ2FlashMode = None flash_speed: AmbZ2FlashSpeed = AmbZ2FlashSpeed.SINGLE flash_hash_offset: int = None + in_fallback_mode: bool = False + boot_cmd: Tuple[int, str] = None def __init__( self, @@ -65,18 +98,26 @@ def flash_cfg(self) -> str: def command(self, cmd: str) -> None: self.flush() - self.s.write(cmd.encode() + b"\n") + cmd = cmd.encode() + self.s.write(cmd + b"\n") + if self.in_fallback_mode: + self.s.read(len(cmd) + 2) def ping(self) -> None: self.command("ping") resp = self.read(4) if resp != b"ping": raise RuntimeError(f"Incorrect ping response: {resp!r}") + resp = self.s.read_all() + if b"$8710c" in resp: + raise RuntimeError(f"Got fallback mode ping: {resp!r}") def disconnect(self) -> None: self.command("disc") def link(self) -> None: + # try linking in fallback mode - 'ping' before that would break it + self.link_fallback() end = time() + self.link_timeout while time() < end: try: @@ -86,6 +127,26 @@ def link(self) -> None: pass raise TimeoutError("Timeout while linking") + def link_fallback(self) -> None: + self.flush() + self.write(AMBZ2_FALLBACK_CMD) + self.push_timeout(0.1) + try: + response = self.read() + if response not in AMBZ2_FALLBACK_RESP: + return + except TimeoutError: + return + finally: + self.pop_timeout() + debug(f"Found fallback mode with response: {response}") + self.in_fallback_mode = True + # check ROM version + chip_ver = (self.register_read(0x4000_01F0) >> 4) & 0xF + # jump to download mode + self.memory_boot(0x0 if chip_ver > 2 else 0x1443C) + self.in_fallback_mode = False + def change_baudrate(self, baudrate: int) -> None: if self.s.baudrate == baudrate: return @@ -125,7 +186,7 @@ def dump_words(self, start: int, count: int) -> Generator[List[int], None, None] line = line.split() addr = int(line[0].rstrip(":"), 16) if addr != start + read_count: - raise ValueError("Got invalid read address") + raise ValueError(f"Got invalid read address: {line}") chunk = list() for i, value in enumerate(line[1 : 1 + 4]): @@ -184,6 +245,27 @@ def register_write(self, address: int, value: int) -> None: self.command(f"EW {address:X} {value:X}") next(self.readlines()) + def register_read_bytes(self, address: int, length: int) -> bytes: + start = align_down(address, 4) + return gen2bytes(self.dump_bytes(start, length))[0:length] + + def register_write_bytes(self, address: int, value: bytes) -> None: + start = align_down(address, 4) + value = pad_data(value, 4, 0x00) + words = [] + for word in biniter(value, 4): + words.append(f"{letoint(word):X}") + # 'EW' command can theoretically write at most 8 words, + # but it seems to cut the command off at around 80 bytes + for i in range(0, len(words), 7): + chunk = words[i : i + 7] + command = f"EW {start + i * 4:X} " + command += " ".join(chunk) + self.command(command) + lines = self.readlines() + for _ in chunk: + next(lines) + def sw_reset(self) -> None: self.command("WDTRST") @@ -428,12 +510,104 @@ def memory_write( f"{hash_expected.hex()}, calculated: {hash_final.hex()}" ) + def memory_boot( + self, + address: int, + force_find: bool = False, + ) -> None: + address |= 1 + if self.boot_cmd is None or force_find: + # find ROM console command array + cmd_array = self.register_read(0x1002F050 + 4) + cmd_size = 4 * 3 + # try all commands to find an unused one + for cmd_ptr in range(cmd_array, cmd_array + 8 * cmd_size, cmd_size): + # read command name pointer + name_ptr = self.register_read(cmd_ptr + 0) + if name_ptr == 0: + break + # read command name + cmd_name = b"".join(self.dump_bytes(name_ptr, 16)) + cmd_name = cmd_name.partition(b"\x00")[0] + if not cmd_name.isascii(): + warning(f"Non-ASCII command string @ 0x{name_ptr:X}: {cmd_name}") + continue + cmd_name = cmd_name.decode() + if cmd_name in USED_COMMANDS: + continue + func_ptr = cmd_ptr + 4 + self.boot_cmd = func_ptr, cmd_name + if self.boot_cmd is None: + raise RuntimeError("No unused ROM command found, cannot boot from SRAM") + + func_ptr, cmd_name = self.boot_cmd + # write new command handler address + self.register_write(func_ptr, address) + debug(f"Jumping to 0x{address:X} with command '{cmd_name}'") + # execute command to jump to the function + self.command(cmd_name) + @click.command( help="AmebaZ2 flashing tool", ) -def cli(): - raise NotImplementedError() +@click.option( + "-d", + "--device", + help="Target device port (default: auto detect)", + type=DevicePortParamType(), + default=(), +) +def cli(device: str): + s = Serial(device, 115200) + s.timeout = 0.01 + + while True: + cmd = input("> ") + + if cmd == "m": + try: + while True: + read = s.read_all() + if read: + print(read.decode(errors="replace"), end="") + except KeyboardInterrupt: + continue + + s.write(cmd.encode()) + s.write(b"\r\n") + response = b"" + start = time() + + if cmd.startswith("ucfg"): + s.close() + baud = int(cmd.split(" ")[1]) + s = Serial(device, baud) + + if cmd.startswith("DB"): + f = open(cmd + ".bin", "wb") + while True: + try: + read = s.read_all() + if read: + print(read.decode(), end="") + response += read + while b"\n" in response: + line, _, response = response.partition(b"\n") + line = line.decode() + line = line.strip() + if line and "[Addr]" not in line: + f.write(restore(line)) + except KeyboardInterrupt: + break + f.close() + continue + + while True: + response += s.read_all() + if time() > start + 0.5: + break + hexdump(response) if __name__ == "__main__": From a63b3e6f85961a20e14b126d55252da6cabb5171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 7 Nov 2023 16:49:59 +0100 Subject: [PATCH 21/24] [ambz2] Implement reading eFuse --- ltchiptool/soc/ambz2/flash.py | 15 ++++++++-- ltchiptool/soc/ambz2/util/ambz2code.py | 40 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 ltchiptool/soc/ambz2/util/ambz2code.py diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index ec08a76..341fac7 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -8,7 +8,13 @@ from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext -from .util.ambz2tool import AmbZ2Tool +from .util.ambz2code import AMBZ2_CODE_EFUSE_READ +from .util.ambz2tool import ( + AMBZ2_CODE_ADDR, + AMBZ2_DATA_ADDR, + AMBZ2_EFUSE_PHYSICAL_SIZE, + AmbZ2Tool, +) class AmebaZ2Flash(SocInterface, ABC): @@ -16,7 +22,6 @@ class AmebaZ2Flash(SocInterface, ABC): def flash_get_features(self) -> FlashFeatures: return FlashFeatures( - can_read_efuse=False, can_read_info=False, ) @@ -82,6 +87,8 @@ def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int return 0x200000 if memory == FlashMemoryType.ROM: return 384 * 1024 + if memory == FlashMemoryType.EFUSE: + return AMBZ2_EFUSE_PHYSICAL_SIZE raise NotImplementedError("Memory type not readable via UART") def flash_read_raw( @@ -94,6 +101,10 @@ def flash_read_raw( ) -> Generator[bytes, None, None]: self.flash_connect() assert self.amb + if memory == FlashMemoryType.EFUSE: + self.amb.register_write_bytes(AMBZ2_CODE_ADDR, AMBZ2_CODE_EFUSE_READ) + self.amb.memory_boot(AMBZ2_CODE_ADDR, force_find=True) + offset |= AMBZ2_DATA_ADDR gen = self.amb.memory_read( offset=offset, length=length, diff --git a/ltchiptool/soc/ambz2/util/ambz2code.py b/ltchiptool/soc/ambz2/util/ambz2code.py new file mode 100644 index 0000000..2ba6c7d --- /dev/null +++ b/ltchiptool/soc/ambz2/util/ambz2code.py @@ -0,0 +1,40 @@ +# Copyright (c) Kuba Szczodrzyński 2023-11-7. + +AMBZ2_CODE_EFUSE_READ = ( + # push {r3-r5,lr} + # movs r5, #0 + # loop: + # ldr r0, EFUSE_CTRL_SETTING + # movs r1, r5 + # ldr r2, read_data + # add r2, r5 + # movs r3, #0xAA + # strb r3, [r2] + # movs r3, #0 + # ldr r4, hal_efuse_stubs + # adds r4, r4, #8 + # ldr r4, [r4] + # blx r4 + # ldr r3, read_size + # adds r5, r5, #1 + # cmp r5, r3 + # bne loop + # pop {r3-r5,pc} + # EFUSE_CTRL_SETTING: .word 0x33300000 + # hal_efuse_stubs: .word 0x500 + # read_size: .word 512 + # read_data: .word 0x10038000 + b"\x38\xb5\x00\x25" + b"\x07\x48\x29\x00" + b"\x09\x4a\x2a\x44" + b"\xaa\x23\x13\x70" + b"\x00\x23\x05\x4c" + b"\x08\x34\x24\x68" + b"\xa0\x47\x04\x4b" + b"\x6d\x1c\x9d\x42" + b"\xf0\xd1\x38\xbd" + b"\x00\x00\x30\x33" # EFUSE_CTRL_SETTING + b"\x00\x05\x00\x00" # hal_efuse_stubs + b"\x00\x02\x00\x00" # read_size + b"\x00\x80\x03\x10" # read_data +) From 6f8bffe9546a402df9164a0c239d19c65470c969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 7 Nov 2023 16:50:24 +0100 Subject: [PATCH 22/24] [ambz2] Implement reading chip info --- ltchiptool/soc/ambz2/flash.py | 75 ++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index 341fac7..b83470e 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -1,15 +1,18 @@ # Copyright (c) Kuba Szczodrzyński 2022-12-28. from abc import ABC -from typing import IO, Generator, Optional +from typing import IO, Generator, List, Optional, Tuple from ltchiptool import SocInterface +from ltchiptool.soc.amb.efuse import efuse_physical_to_logical from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType +from ltchiptool.util.intbin import gen2bytes, letoint from ltchiptool.util.streams import ProgressCallback from uf2tool import OTAScheme, UploadContext from .util.ambz2code import AMBZ2_CODE_EFUSE_READ from .util.ambz2tool import ( + AMBZ2_CHIP_TYPE, AMBZ2_CODE_ADDR, AMBZ2_DATA_ADDR, AMBZ2_EFUSE_PHYSICAL_SIZE, @@ -19,11 +22,12 @@ class AmebaZ2Flash(SocInterface, ABC): amb: Optional[AmbZ2Tool] = None + chip_id: int = None + flash_id: bytes = None + info: List[Tuple[str, str]] = None def flash_get_features(self) -> FlashFeatures: - return FlashFeatures( - can_read_info=False, - ) + return FlashFeatures() def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: @@ -66,21 +70,62 @@ def flash_disconnect(self) -> None: if self.conn: self.conn.linked = False - def flash_get_chip_info_string(self) -> str: + def flash_get_chip_info(self) -> List[Tuple[str, str]]: + if self.info: + return self.info self.flash_connect() assert self.amb self.amb.flash_init(configure=False) - reg = self.amb.register_read(0x4000_01F0) - vid = (reg >> 8) & 0xF - ver = (reg >> 4) & 0xF - rom_ver = "2.1" if ver <= 2 else "3.0" - items = [ - self.amb.flash_mode.name.replace("_", "/"), - f"Chip VID: {vid}", - f"Version: {ver}", - f"ROM: v{rom_ver}", + + efuse_phys = gen2bytes( + self.flash_read_raw( + offset=0, + length=AMBZ2_EFUSE_PHYSICAL_SIZE, + memory=FlashMemoryType.EFUSE, + ), + ) + efuse_logi = efuse_physical_to_logical(efuse_phys) + # self.flash_id = + self.chip_id = efuse_phys[0x1F8] + + chip_type = AMBZ2_CHIP_TYPE.get(self.chip_id, f"Unknown 0x{self.chip_id:02X}") + # size_id = self.flash_id[2] + # if 0x14 <= size_id <= 0x19: + # flash_size = sizeof(1 << size_id) + # else: + # flash_size = "Unknown" + + syscfg0 = self.amb.register_read(0x4000_01F0) + vid = (syscfg0 >> 8) & 0xF + ver = (syscfg0 >> 4) & 0xF + + boot_debug = not (letoint(efuse_logi[0x18:0x1C]) & 0x100000) + secure_boot = (efuse_logi[0x19] ^ 0x80) <= 0x7E + + self.info = [ + ("Chip VID", str(vid)), + ("Chip Version", str(ver)), + ("ROM Version", "v2.1" if ver <= 2 else "v3.0"), + ("", ""), + ("Chip Type", chip_type), + ("MAC Address (Wi-Fi)", efuse_logi[0x11A : 0x11A + 6].hex(":").upper()), + ("MAC Address (BT)", efuse_logi[0x190 : 0x190 + 6].hex(":").upper()), + ("Boot Debugging", "Enabled" if boot_debug else "Disabled"), + ("Secure Boot", "Enabled" if secure_boot else "Disabled"), + ("", ""), + # ("Flash ID", self.flash_id.hex(" ").upper()), + # ("Flash Size (real)", flash_size), + ("Flash Type", self.amb.flash_mode.name.replace("_", "/")), + ("Flash Mode", self.amb.flash_speed.name), ] - return " / ".join(items) + + return self.info + + def flash_get_chip_info_string(self) -> str: + self.flash_connect() + assert self.amb + self.amb.flash_init(configure=False) + return self.amb.flash_mode.name.replace("_", "/") def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: if memory == FlashMemoryType.FLASH: From 543d9738f50eb86c8850f68fe3865c0ddaf2ffe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 15 Nov 2023 20:32:44 +0100 Subject: [PATCH 23/24] [gui] Remember window state, fix flashing length check --- ltchiptool/gui/main.py | 16 +++++++++++++++- ltchiptool/gui/panels/flash.py | 8 ++++---- ltchiptool/util/cli.py | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ltchiptool/gui/main.py b/ltchiptool/gui/main.py index 6338b3f..8ae78b6 100644 --- a/ltchiptool/gui/main.py +++ b/ltchiptool/gui/main.py @@ -31,6 +31,7 @@ class MainFrame(wx.Frame): Panels: dict[str, BasePanel] Menus: dict[str, wx.Menu] MenuItems: dict[str, dict[str, wx.MenuItem]] + NormalSize: wx.Size init_params: dict Zeroconf: Zeroconf = None @@ -142,6 +143,7 @@ def __init__(self, *args, **kw): self.Bind(wx.EVT_SHOW, self.OnShow) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_MENU, self.OnMenu) + self.Bind(wx.EVT_SIZE, self.OnSize) self.Notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging) self.Notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged) @@ -161,13 +163,15 @@ def _settings(self, value: dict): # noinspection PyPropertyAccess def GetSettings(self) -> dict: pos: wx.Point = self.GetPosition() - size: wx.Size = self.GetSize() + size: wx.Size = self.NormalSize or self.GetSize() + maximized: bool = self.IsMaximized() split: int = self.Splitter.GetSashPosition() page: str = self.NotebookPageName palette: str = self.palette return dict( pos=[pos.x, pos.y], size=[size.x, size.y], + maximized=maximized, split=split, page=page, palette=palette, @@ -177,6 +181,7 @@ def SetSettings( self, pos: tuple[int, int] = None, size: tuple[int, int] = None, + maximized: bool = None, split: int = None, page: str = None, palette: str = None, @@ -186,6 +191,8 @@ def SetSettings( self.SetPosition(pos) if size: self.SetSize(size) + if maximized: + self.Maximize() if split: self.Splitter.SetSashPosition(split) if page is not None: @@ -274,6 +281,13 @@ def OnClose(self, *_): info(f"Saved settings to {self.config_file}") self.Destroy() + @with_event + def OnSize(self, event: wx.SizeEvent): + event.Skip() + if self.IsMaximized(): + return + self.NormalSize = event.GetSize() + @with_target def OnMenu(self, event: wx.CommandEvent, target: wx.Menu): if not isinstance(target, wx.Menu): diff --git a/ltchiptool/gui/panels/flash.py b/ltchiptool/gui/panels/flash.py index 777e51c..6fe2c35 100644 --- a/ltchiptool/gui/panels/flash.py +++ b/ltchiptool/gui/panels/flash.py @@ -259,17 +259,17 @@ def OnUpdate(self, target: wx.Window = None): if manual: warnings.append("Warning: using custom options") - if self.skip >= self.detection.length: + if self.skip >= self.detection.size: errors.append( f"Skip offset (0x{self.skip:X}) " f"not within input file bounds " - f"(0x{self.detection.length:X})" + f"(0x{self.detection.size:X})" ) - elif self.skip + (self.length or 0) > self.detection.length: + elif self.skip + (self.length or 0) > self.detection.size: errors.append( f"Writing length (0x{self.skip:X} + 0x{self.length:X}) " f"not within input file bounds " - f"(0x{self.detection.length:X})" + f"(0x{self.detection.size:X})" ) errors.append("") diff --git a/ltchiptool/util/cli.py b/ltchiptool/util/cli.py index a02bb5c..96f4e1a 100644 --- a/ltchiptool/util/cli.py +++ b/ltchiptool/util/cli.py @@ -67,7 +67,7 @@ def find_serial_port() -> Optional[str]: return ports[0][0] -def run_subprocess(*args) -> int: +def run_subprocess(*args, cwd: str = None) -> int: def stream(io: IO[bytes], func: Callable[[str], None]): for line in iter(io.readline, b""): func(line.decode("utf-8").rstrip()) @@ -77,6 +77,7 @@ def stream(io: IO[bytes], func: Callable[[str], None]): args=args, stdout=PIPE, stderr=PIPE, + cwd=cwd, ) threads = [ Thread(target=stream, args=(p.stdout, info)), From 946d9cd84949cee0c4756e87c07b0eb2e2b5c199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 18 Nov 2023 19:41:38 +0100 Subject: [PATCH 24/24] [ambz2] Add flashing guide --- ltchiptool/soc/ambz2/flash.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ltchiptool/soc/ambz2/flash.py b/ltchiptool/soc/ambz2/flash.py index b83470e..606e5dd 100644 --- a/ltchiptool/soc/ambz2/flash.py +++ b/ltchiptool/soc/ambz2/flash.py @@ -1,7 +1,7 @@ # Copyright (c) Kuba Szczodrzyński 2022-12-28. from abc import ABC -from typing import IO, Generator, List, Optional, Tuple +from typing import IO, Generator, List, Optional, Tuple, Union from ltchiptool import SocInterface from ltchiptool.soc.amb.efuse import efuse_physical_to_logical @@ -19,6 +19,30 @@ AmbZ2Tool, ) +AMEBAZ2_GUIDE = [ + "Connect UART2 of the Realtek chip to the USB-TTL adapter:", + [ + ("PC", "RTL8720C"), + ("RX", "TX2 (Log_TX / PA16)"), + ("TX", "RX2 (Log_RX / PA15)"), + ("", ""), + ("GND", "GND"), + ], + "Using a good, stable 3.3V power supply is crucial. Most flashing issues\n" + "are caused by either voltage drops during intensive flash operations,\n" + "or bad/loose wires.", + "The UART adapter's 3.3V power regulator is usually not enough. Instead,\n" + "a regulated bench power supply, or a linear 1117-type regulator is recommended.", + "In order to flash the chip, you need to enable download mode.\n" + "This is similar to ESP8266/ESP32, but the strapping pin (GPIO 0 / PA00)\n" + "has to be pulled *to 3.3V*, not GND.", + "Additionally, make sure that pin PA13 (RX0) is NOT pulled to GND.", + "Do this, in order:\n" + " - connect PA00 to 3.3V\n" + " - apply power to the device OR shortly connect CEN to GND\n" + " - start the flashing process", +] + class AmebaZ2Flash(SocInterface, ABC): amb: Optional[AmbZ2Tool] = None @@ -29,6 +53,9 @@ class AmebaZ2Flash(SocInterface, ABC): def flash_get_features(self) -> FlashFeatures: return FlashFeatures() + def flash_get_guide(self) -> List[Union[str, list]]: + return AMEBAZ2_GUIDE + def flash_set_connection(self, connection: FlashConnection) -> None: if self.conn: self.flash_disconnect()