From 00606f1ba1f410002d53dc7fb6192c6d3acae104 Mon Sep 17 00:00:00 2001 From: lamauny Date: Fri, 3 Jan 2025 18:05:11 +0100 Subject: [PATCH 01/12] ln882h support: first version --- ltchiptool/commands/soc.py | 2 + ltchiptool/soc/interface.py | 4 + ltchiptool/soc/ln882x/__init__.py | 7 + ltchiptool/soc/ln882x/binary.py | 61 +++ ltchiptool/soc/ln882x/flash.py | 318 +++++++++++ ltchiptool/soc/ln882x/main.py | 34 ++ ltchiptool/soc/ln882x/util/__init__.py | 7 + ltchiptool/soc/ln882x/util/boot_header.py | 122 +++++ ltchiptool/soc/ln882x/util/image_header.py | 189 +++++++ ltchiptool/soc/ln882x/util/ln_tools.py | 75 +++ ltchiptool/soc/ln882x/util/makeimage.py | 498 ++++++++++++++++++ .../soc/ln882x/util/ota_image_generator.py | 261 +++++++++ ltchiptool/soc/ln882x/util/part_desc_info.py | 151 ++++++ 13 files changed, 1729 insertions(+) create mode 100644 ltchiptool/soc/ln882x/__init__.py create mode 100644 ltchiptool/soc/ln882x/binary.py create mode 100644 ltchiptool/soc/ln882x/flash.py create mode 100644 ltchiptool/soc/ln882x/main.py create mode 100644 ltchiptool/soc/ln882x/util/__init__.py create mode 100644 ltchiptool/soc/ln882x/util/boot_header.py create mode 100644 ltchiptool/soc/ln882x/util/image_header.py create mode 100644 ltchiptool/soc/ln882x/util/ln_tools.py create mode 100644 ltchiptool/soc/ln882x/util/makeimage.py create mode 100644 ltchiptool/soc/ln882x/util/ota_image_generator.py create mode 100644 ltchiptool/soc/ln882x/util/part_desc_info.py diff --git a/ltchiptool/commands/soc.py b/ltchiptool/commands/soc.py index 5ef40d5..b3fd07a 100644 --- a/ltchiptool/commands/soc.py +++ b/ltchiptool/commands/soc.py @@ -9,6 +9,8 @@ "rtltool": "ltchiptool/soc/ambz/util/rtltool.py", "ambztool": "ltchiptool/soc/ambz/util/ambztool.py", "ambz2tool": "ltchiptool/soc/ambz2/util/ambz2tool.py", + "ln-makeimage": "ltchiptool/soc/ln882x/util/makeimage.py", + "ln-otagen": "ltchiptool/soc/ln882x/util/ota_image_generator.py", } diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 0858047..4af3055 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -28,6 +28,9 @@ def get(cls, family: Family) -> "SocInterface": if family.is_child_of("realtek-ambz2"): from .ambz2 import AmebaZ2Main return AmebaZ2Main(family) + if family.is_child_of("lightning-ln882x"): + from .ln882x import LN882xMain + return LN882xMain(family) # fmt: on raise NotImplementedError(f"Unsupported family - {family.name}") @@ -38,6 +41,7 @@ def get_family_names(cls) -> List[str]: "beken-72xx", "realtek-ambz", "realtek-ambz2", + "lightning-ln882x", ] ######################### diff --git a/ltchiptool/soc/ln882x/__init__.py b/ltchiptool/soc/ln882x/__init__.py new file mode 100644 index 0000000..852c182 --- /dev/null +++ b/ltchiptool/soc/ln882x/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +from .main import LN882xMain + +__all__ = [ + "LN882xMain", +] diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882x/binary.py new file mode 100644 index 0000000..1b5a414 --- /dev/null +++ b/ltchiptool/soc/ln882x/binary.py @@ -0,0 +1,61 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +from abc import ABC +from datetime import datetime +from logging import warning +from os import stat +from typing import IO, List, Optional, Union + +from ltchiptool import SocInterface +from ltchiptool.util.detection import Detection +from ltchiptool.util.fileio import chext +from os.path import dirname, expanduser, isdir, isfile, join, realpath +from ltchiptool.util.fwbinary import FirmwareBinary + +from ltchiptool.util.lvm import LVM + +from .util import MakeImageTool + + +class LN882xBinary(SocInterface, ABC): + def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: + toolchain = self.board.toolchain + lvm = LVM.get() + print("lvm:", lvm.path()) + + bootfile = self.board["build.bootfile"] + flashpart = self.board["build.flashpart"] + + # build output names + output = FirmwareBinary( + location=input, + name=f"firmware", + subname="", + ext="bin", + title="Flash Image", + description="Complete image with boot for flashing to ln882x memory at offset 0", + public=True, + ) + + fw_bin = chext(input, "bin") + # objcopy ELF -> raw BIN + toolchain.objcopy(input, fw_bin) + + mkimage = MakeImageTool() + mkimage.boot_filepath = join(lvm.path(), f"cores", self.family.name, f"misc", bootfile) + mkimage.app_filepath = fw_bin + mkimage.flashimage_filepath = output.path + mkimage.part_cfg_filepath = join(lvm.path(), f"cores", self.family.name, f"base/config", flashpart) + mkimage.ver_str = "1.0" + mkimage.swd_crp = 0 + mkimage.doAllWork() + + return output.group() + + def detect_file_type( + self, + file: IO[bytes], + length: int, + ) -> Optional[Detection]: + + return None diff --git a/ltchiptool/soc/ln882x/flash.py b/ltchiptool/soc/ln882x/flash.py new file mode 100644 index 0000000..9189e8d --- /dev/null +++ b/ltchiptool/soc/ln882x/flash.py @@ -0,0 +1,318 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +import logging +import struct +from abc import ABC +from binascii import crc32 +from logging import DEBUG, debug, warning +from typing import IO, Generator, List, Optional, Tuple, Union + +from bk7231tools.serial import BK7231Serial +from bk7231tools.serial.base import BkChipType +from bk7231tools.serial.base.packets import ( + BkFlashReg24ReadCmnd, + BkReadRegCmnd, + BkWriteRegCmnd, +) + +from ltchiptool import SocInterface +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType +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 + +LN882x_GUIDE = [ + "Connect UART1 of the LN882x to the USB-TTL adapter:", + [ + ("PC", "LN882x"), + ("RX", "TX1 (GPIO11 / P11)"), + ("TX", "RX1 (GPIO10 / P10)"), + ("", ""), + ("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.", + "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.", +] + +SCTRL_EFUSE_CTRL = 0x00800074 +SCTRL_EFUSE_OPTR = 0x00800078 + + +class LN882xFlash(SocInterface, ABC): + bk: Optional[BK7231Serial] = None + info: List[Tuple[str, str]] = None + + def flash_get_features(self) -> FlashFeatures: + return FlashFeatures() + + 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() + self.conn = connection + self.conn.fill_baudrate(115200) + + def flash_build_protocol(self, force: bool = False) -> None: + if not force and self.bk: + return + self.flash_disconnect() + self.bk = BK7231Serial( + port=self.conn.port, + baudrate=self.conn.baudrate, + link_baudrate=self.conn.link_baudrate, + ) + loglevel = logging.getLogger().getEffectiveLevel() + if loglevel <= DEBUG: + self.bk.info = lambda *args: debug(" ".join(map(str, args))) + if loglevel <= VERBOSE: + self.bk.debug = lambda *args: verbose(" ".join(map(str, args))) + self.bk.warn = lambda *args: warning(" ".join(map(str, args))) + 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.bk.cmnd_timeout = timeout + self.conn.timeout = timeout + if link_timeout: + self.bk.link_timeout = link_timeout + self.conn.link_timeout = link_timeout + + def flash_hw_reset(self) -> None: + self.flash_build_protocol() + self.bk.hw_reset() + + def flash_connect(self) -> None: + if self.bk and self.conn.linked: + return + self.flash_build_protocol() + self.bk.connect() + self.conn.linked = True + + def flash_disconnect(self) -> None: + if self.bk: + # avoid printing retry warnings + self.bk.warn = lambda *_: None + self.bk.close() + self.bk = None + if self.conn: + self.conn.linked = False + + def flash_get_chip_info(self) -> List[Tuple[str, str]]: + if self.info: + return self.info + self.flash_connect() + + self.info = [ + ("Protocol Type", self.bk.protocol_type.name), + ( + "Chip Type", + self.bk.chip_type and self.bk.chip_type.name or "Unrecognized", + ), + ( + "Bootloader Type", + self.bk.bootloader + and f"{self.bk.bootloader.chip.name} {self.bk.bootloader.version or ''}" + or "Unrecognized", + ), + ( + "Chip ID", + self.bk.bk_chip_id and hex(self.bk.bk_chip_id) or "N/A", + ), + ( + "Boot Version String", + self.bk.bk_boot_version or "N/A", + ), + ] + + if self.bk.chip_type == BkChipType.BK7231N: + tlv = self.bk.flash_read_bytes(0x1D0000, 0x1000) + elif self.bk.chip_type == BkChipType.BK7231T: + tlv = self.bk.flash_read_bytes(0x1E0000, 0x1000) + 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"), + ] + + self.info += [ + ("", ""), + ] + if self.bk.check_protocol(BkFlashReg24ReadCmnd): + flash_id = self.bk.flash_read_id() + self.info += [ + ("Flash ID", flash_id["id"].hex(" ").upper()), + ("Flash Size (by ID)", sizeof(flash_id["size"])), + ] + if self.bk.bootloader and self.bk.bootloader.flash_size: + self.info += [ + ("Flash Size (by BL)", sizeof(self.bk.bootloader.flash_size)), + ] + if self.bk.flash_size_detected: + self.info += [ + ("Flash Size (detected)", sizeof(self.bk.flash_size)), + ] + else: + flash_size = self.bk.flash_detect_size() + self.info += [ + ("Flash Size (detected)", sizeof(flash_size)), + ] + + if self.bk.check_protocol(BkReadRegCmnd): + 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: + self.flash_connect() + if memory == FlashMemoryType.FLASH: + return self.bk.flash_size + if memory == FlashMemoryType.ROM: + if not self.bk.check_protocol(BkReadRegCmnd): + raise NotImplementedError("Only BK7231N has built-in ROM") + return 16 * 1024 + if memory == FlashMemoryType.EFUSE: + if not self.bk.check_protocol(BkWriteRegCmnd): + raise NotImplementedError("Only BK7231N can read eFuse via UART") + return 32 + raise NotImplementedError("Memory type not readable via UART") + + def flash_read_raw( + self, + offset: int, + length: int, + verify: bool = True, + memory: FlashMemoryType = FlashMemoryType.FLASH, + callback: ProgressCallback = ProgressCallback(), + ) -> Generator[bytes, None, None]: + self.flash_connect() + + 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): + reg = self.bk.register_read(address) + 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") + + crc_offset = offset + crc_length = 0 + crc_value = 0 + + for chunk in self.bk.flash_read(start=offset, length=length, crc_check=False): + if not verify: + yield chunk + continue + + crc_length += len(chunk) + crc_value = crc32(chunk, crc_value) + # check CRC every each 32 KiB, or by the end of file + if crc_length < 32 * 1024 and crc_offset + crc_length != offset + length: + yield chunk + callback.on_update(len(chunk)) + continue + + debug(f"Checking CRC @ 0x{crc_offset:X}..0x{crc_offset + crc_length:X}") + crc_expected = self.bk.read_flash_range_crc( + crc_offset, + crc_offset + crc_length, + ) + if crc_expected != crc_value: + raise ValueError( + f"Chip CRC value {crc_expected:X} does not match calculated " + f"CRC value {crc_value:X} (at 0x{crc_offset:X})" + ) + crc_offset += crc_length + crc_length = 0 + crc_value = 0 + yield chunk + callback.on_update(len(chunk)) + + def flash_write_raw( + self, + offset: int, + length: int, + data: IO[bytes], + verify: bool = True, + callback: ProgressCallback = ProgressCallback(), + ) -> None: + self.flash_connect() + gen = self.bk.program_flash( + io=data, + io_size=length, + start=offset, + crc_check=verify, + ) + callback.update_from(gen) + + def flash_write_uf2( + self, + ctx: UploadContext, + verify: bool = True, + callback: ProgressCallback = ProgressCallback(), + ) -> None: + # collect continuous blocks of data (before linking, as this takes time) + parts = ctx.collect_data(OTAScheme.FLASHER_SINGLE) + callback.on_total(sum(len(part.getvalue()) for part in parts.values())) + + # connect to chip + self.flash_connect() + + # write blocks to flash + for offset, data in parts.items(): + length = len(data.getvalue()) + data.seek(0) + callback.on_message(f"Writing (0x{offset:06X})") + gen = self.bk.program_flash( + io=data, + io_size=length, + start=offset, + crc_check=verify, + dry_run=False, + really_erase=True, + ) + callback.update_from(gen) + + callback.on_message("Booting firmware") + # reboot the chip + self.bk.reboot_chip() diff --git a/ltchiptool/soc/ln882x/main.py b/ltchiptool/soc/ln882x/main.py new file mode 100644 index 0000000..cf207e9 --- /dev/null +++ b/ltchiptool/soc/ln882x/main.py @@ -0,0 +1,34 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +from abc import ABC +from logging import info +from typing import Optional + +from ltchiptool import Family +from ltchiptool.models import OTAType +from ltchiptool.soc import SocInterfaceCommon + +from .binary import LN882xBinary +from .flash import LN882xFlash + + +class LN882xMain( + LN882xBinary, + LN882xFlash, + SocInterfaceCommon, + ABC, +): + def __init__(self, family: Family) -> None: + super().__init__() + self.family = family + + def hello(self): + info("Hello from LN882x") + + @property + def ota_type(self) -> Optional[OTAType]: + return OTAType.SINGLE + + @property + def ota_supports_format_1(self) -> bool: + return True diff --git a/ltchiptool/soc/ln882x/util/__init__.py b/ltchiptool/soc/ln882x/util/__init__.py new file mode 100644 index 0000000..fc57745 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +from .makeimage import MakeImageTool + +__all__ = [ + "MakeImageTool", +] diff --git a/ltchiptool/soc/ln882x/util/boot_header.py b/ltchiptool/soc/ln882x/util/boot_header.py new file mode 100644 index 0000000..508a930 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/boot_header.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# +# Copyright 2021 Shanghai Lightning Semiconductor Technology Co., LTD + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import zlib +import struct +from .ln_tools import * + + +class BootHeader: + + BOOT_HEADER_SIZE = (4 + 2 + 2 + 4 * 4) + + CRP_VALID_FLAG = 0x46505243 + + BOOT_START_ADDR = 0 + BOOT_SIZE_LIMIT = (1024 * 24) + + def __init__(self, other_buf) -> None: + self.__bootram_target_addr = 0 + self.__bootram_bin_length = 0 # 2bytes + self.__bootram_crc_offset = 0 # 2bytes + self.__bootram_crc_value = 0 + self.__bootram_vector_addr = 0 + self.__crp_flag = 0 + self.__boot_header_crc = 0 + + if not (isinstance(other_buf, bytearray) or isinstance(other_buf, bytes)): + raise TypeError("Error: other_buf MUST be a bytearray or bytes!!!") + + if len(other_buf) < BootHeader.BOOT_HEADER_SIZE: + raise ValueError("Error: other_buf MUST have at least {} bytes!!!".format(BootHeader.BOOT_HEADER_SIZE)) + + self.__buffer = bytearray(BootHeader.BOOT_HEADER_SIZE) + self.__buffer[:] = other_buf[0:BootHeader.BOOT_HEADER_SIZE] + + items = struct.unpack(" bytearray: + struct.pack_into("> (8*shift) ) + return val + + +def dump_bytes_in_hex(byte_arr=None, lineSize=16, bytesMax=256, title=""): + """ + Print byte array in hex format. + lineSize: print how many items each line. + bytesMax: print how many items at most. (-1, print the whole byte array.) + title: + """ + + if title: + print("\n---------- {} ----------".format(title)) + + if bytesMax == -1: + bytesMax = len(byte_arr) + elif bytesMax > len(byte_arr): + bytesMax = len(byte_arr) + else: + pass + + for cnt in range(0, bytesMax): + if cnt % lineSize == 0: + print("{_so:08X} |".format(_so=cnt), end=" ") + print("{_b:02X}".format(_b=byte_arr[cnt]), end=" ") + if cnt % lineSize == (lineSize-1): + print("") + + +def check_python_version(): + major = sys.version_info.major + minor = sys.version_info.minor + if (major == 3) and (minor >= 6): + return True + else: + print('WARNING: Python 2 or Python 3 versions older than 3.6 are not supported.', file=sys.stderr) + exit(-100) + return False diff --git a/ltchiptool/soc/ln882x/util/makeimage.py b/ltchiptool/soc/ln882x/util/makeimage.py new file mode 100644 index 0000000..5d22340 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/makeimage.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# +# Copyright 2021 Shanghai Lightning Semiconductor Technology Co., LTD + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import click +from typing import List + +from .boot_header import * +from .image_header import * +from .part_desc_info import * + + +class MakeImageTool: + def __init__(self) -> None: + self.__boot_filepath = None + self.__app_filepath = None + self.__flashimage_filepath = None + self.__part_cfg_filepath = None + self.__ver_str = None + self.__ver_major = 0 + self.__ver_minor = 0 + self.__swd_crp = 0 + self.__verbose = 0 + + self.__part_desc_info_list = [] + self.__partbuf_bootram = None + self.__partbuf_parttab = None + self.__partbuf_nvds = None + self.__partbuf_app = None + self.__partbuf_kv = None + self.__partbuf_eeprom = None + + def readPartCfg(self) -> bool: + try: + with open(self.part_cfg_filepath, "r", encoding="utf-8") as fObj: + root_node = json.load(fp=fObj) + vendor_node = root_node["vendor_define"] + user_node = root_node["user_define"] + + for node in vendor_node: + parttype = part_type_str2num(node["partition_type"]) + startaddr = int(node["start_addr"], 16) + partsize = node["size_KB"] * 1024 + + part_info = PartDescInfo(parttype=parttype, startaddr=startaddr, partsize=partsize) + self.__part_desc_info_list.append(part_info) + + for node in user_node: + parttype = part_type_str2num(node["partition_type"]) + startaddr = int(node["start_addr"], 16) + partsize = node["size_KB"] * 1024 + + part_info = PartDescInfo(parttype=parttype, startaddr=startaddr, partsize=partsize) + self.__part_desc_info_list.append(part_info) + + except Exception as err: + print("Error: open partition cfg file failed: {}".format(str(err))) + return False + + if len(self.__part_desc_info_list) >= 4: + print("----------" * 10) + for item in self.__part_desc_info_list: + print(item) + print("----------" * 10) + + return True + + return False + + def genPartBufPartTab(self) -> bool: + parttab_part = self.getPartDescInfoFromList(PART_TYPE_PART_TAB) + if not parttab_part: + print("Error: partition table has not been found!!!") + return False + + part_tab_buffer = bytearray(parttab_part.part_size) + part_tab_buffer = part_tab_buffer.replace(b'\x00', b'\xFF') + + offset = 0 + for part in self.__part_desc_info_list: + if isinstance(part, PartDescInfo): + if (part.part_type == PART_TYPE_BOOT) or (part.part_type == PART_TYPE_PART_TAB) or (part.part_type == PART_TYPE_INVALID): + continue + part_tab_buffer[offset:(offset+PARTITION_DESC_INFO_SIZE)] = part.toBytes() + offset += PARTITION_DESC_INFO_SIZE + + part_tab_buffer[offset:(offset+PARTITION_DESC_INFO_SIZE)] = bytearray(PARTITION_DESC_INFO_SIZE)[:] + + self.__partbuf_parttab = part_tab_buffer + return True + + def getPartDescInfoFromList(self, part_type) -> PartDescInfo: + if not isinstance(part_type, int): + raise TypeError("Error: part_type MUST be an int value!!!") + + for part_info in self.__part_desc_info_list: + if isinstance(part_info, PartDescInfo): + if part_info.part_type == part_type: + return part_info + return None + + def checkFileSize(self) -> bool: + boot_fileinfo = os.stat(self.boot_filepath) + app_fileinfo = os.stat(self.app_filepath) + + max_boot_filesize = self.getPartDescInfoFromList(PART_TYPE_BOOT).part_size + max_app_filesize = self.getPartDescInfoFromList(PART_TYPE_APP).part_size + + if boot_fileinfo.st_size >= max_boot_filesize: + print("FAIL -- checking {}".format(self.boot_filepath)) + return False + print("PASS -- checking {}".format(self.boot_filepath)) + + if app_fileinfo.st_size >= max_app_filesize: + print("FAIL -- checking {}".format(self.app_filepath)) + return False + print("PASS -- checking {}".format(self.app_filepath)) + + + return True + + def genPartBufBootRam(self) -> bool: + boot_part = self.getPartDescInfoFromList(PART_TYPE_BOOT) + if not boot_part: + print("Error: BOOT partition has not been found!!!") + return False + + bootram_buffer = bytearray(boot_part.part_size) + bootram_buffer = bootram_buffer.replace(b'\x00', b'\xFF') + + fileInfo = os.stat(self.boot_filepath) + try: + with open(self.boot_filepath, "rb") as fObj: + bootram_content = fObj.read() + bootheader = BootHeader(bootram_content) + if self.swd_crp == 0: + bootheader.crp_flag = 0 + else: + bootheader.crp_flag = BootHeader.CRP_VALID_FLAG + + bootram_buffer[0:BootHeader.BOOT_HEADER_SIZE] = bootheader.toByteArray()[:] + bootram_buffer[BootHeader.BOOT_HEADER_SIZE:fileInfo.st_size] = bootram_content[BootHeader.BOOT_HEADER_SIZE:fileInfo.st_size] + self.__partbuf_bootram = bootram_buffer + + except Exception as err: + print("Error: open boot file failed: {}".format(str(err))) + return False + + return True + + def genPartBufKV(self) -> bool: + kv_part = self.getPartDescInfoFromList(PART_TYPE_KV) + if kv_part: + kv_buffer = bytearray(kv_part.part_size) + kv_buffer = kv_buffer.replace(b'\x00', b'\xFF') + self.__partbuf_kv = kv_buffer + return True + + def genPartBufEEPROM(self) -> bool: + eeprom_part = self.getPartDescInfoFromList(PART_TYPE_SIMU_EEPROM) + if eeprom_part: + eeprom_buffer = bytearray(eeprom_part.part_size) + eeprom_buffer = eeprom_buffer.replace(b'\x00', b'\xFF') + self.__partbuf_eeprom = eeprom_buffer + return True + + def genPartBufAPP(self) -> bool: + app_part = self.getPartDescInfoFromList(PART_TYPE_APP) + if not app_part: + print("Error: APP part is not found in the partition table!!!") + return False + + try: + with open(self.app_filepath, "rb") as fObj: + app_content = fObj.read() + + image_header = ImageHeader(bytearray(256)) + image_header.image_type = IMAGE_TYPE_ORIGINAL + image_header.setVerMajor(self.__ver_major) + image_header.setVerMinor(self.__ver_minor) + image_header.img_size_orig = len(app_content) + image_header.img_crc32_orig = zlib.crc32(app_content) + + temp = bytearray(image_header.toBytes()) + temp.extend(app_content) + self.__partbuf_app = temp + except Exception as err: + print("Error: open app file failed: {}".format(str(err))) + return False + + if not self.__partbuf_app: + return False + + return True + + def writeOutputFile(self) -> bool: + if not self.__partbuf_bootram: + print("Error: ramcode has not been processed!!!") + return False + + if not self.__partbuf_parttab: + print("Error: partition table has not been processed!!!") + return False + + if not self.__partbuf_nvds: + nvds_part = self.getPartDescInfoFromList(PART_TYPE_NVDS) + if nvds_part: + nvds_buffer = bytearray(nvds_part.part_size) + nvds_buffer = nvds_buffer.replace(b'\x00', b'\xFF') + self.__partbuf_nvds = nvds_buffer + + if not self.__partbuf_app: + print("Error: app has not been processed!!!") + return False + + if not self.__partbuf_kv: + print("Error: KV has not been processed!!!") + return False + + try: + with open(self.flashimage_filepath, "wb") as fObj: + # ram code + ramcode_part = self.getPartDescInfoFromList(PART_TYPE_BOOT) + fObj.seek(ramcode_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_bootram) + + # partition table + parttab_part = self.getPartDescInfoFromList(PART_TYPE_PART_TAB) + fObj.seek(parttab_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_parttab) + + # APP + app_part = self.getPartDescInfoFromList(PART_TYPE_APP) + fObj.seek(app_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_app) + + # NVDS + nvds_part = self.getPartDescInfoFromList(PART_TYPE_NVDS) + if (not nvds_part) and nvds_part.start_addr < app_part.start_addr: + fObj.seek(nvds_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_nvds) + + # KV + kv_part = self.getPartDescInfoFromList(PART_TYPE_KV) + if kv_part.start_addr < app_part.start_addr: + fObj.seek(kv_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_kv) + + # SIMU_EEPROM + eeprom_part = self.getPartDescInfoFromList(PART_TYPE_SIMU_EEPROM) + if eeprom_part and (eeprom_part.start_addr < app_part.start_addr): + fObj.seek(eeprom_part.start_addr, os.SEEK_SET) + fObj.write(self.__partbuf_eeprom) + except Exception as err: + print("Error: open file failed: {}!!!".format(str(err))) + return False + + return True + + def doAllWork(self) -> bool: + if not self.readPartCfg(): + return False + + if not self.genPartBufPartTab(): + return False + + if not self.checkFileSize(): + print("Error: file size check failed!!!") + return False + + if not self.genPartBufBootRam(): + print("Error: ram code wrong!!!") + return False + + if not self.genPartBufKV(): + print("Error: KV wrong!!!") + return False + + if not self.genPartBufEEPROM(): + print("Error: EEPROM wrong!!!") + return False + + if not self.genPartBufAPP(): + print("Error: process app content!!!") + return False + + if not self.writeOutputFile(): + print("Error: final store!!!") + return False + + return True + + @property + def boot_filepath(self): + return self.__boot_filepath + + @boot_filepath.setter + def boot_filepath(self, boot): + if isinstance(boot, str): + if os.path.exists(boot): + self.__boot_filepath = boot + else: + raise ValueError("Error: not exist: {} !!!".format(boot)) + else: + raise TypeError("Error: boot MUST be a str!!!") + + @property + def app_filepath(self): + return self.__app_filepath + + @app_filepath.setter + def app_filepath(self, app): + if isinstance(app, str): + if os.path.exists(app): + self.__app_filepath = app + else: + raise ValueError("Error: not exist: {} !!!".format(app)) + else: + raise TypeError("Error: app MUST be a str!!!") + + @property + def flashimage_filepath(self): + return self.__flashimage_filepath + + @flashimage_filepath.setter + def flashimage_filepath(self, flashimage): + if isinstance(flashimage, str): + dest_dir = os.path.dirname(flashimage) + if os.path.exists(dest_dir): + self.__flashimage_filepath = flashimage + else: + raise ValueError("Error: directory for {} NOT exist!!!".format(flashimage)) + else: + raise TypeError("Error: flashimage MUST be a str!!!") + + @property + def part_cfg_filepath(self): + return self.__part_cfg_filepath + + @part_cfg_filepath.setter + def part_cfg_filepath(self, part_cfg): + if isinstance(part_cfg, str): + if os.path.exists(part_cfg): + self.__part_cfg_filepath = part_cfg + else: + raise ValueError("Error: not exist: {}".format(part_cfg)) + else: + raise TypeError("Error: part_cfg MUST be a str!!!") + + @property + def ver_str(self): + return self.__ver_str + + @ver_str.setter + def ver_str(self, ver): + """ + `ver` is a str with format ".", such as "1.2" or "2.3". + """ + if isinstance(ver, str): + temp_list = ver.split(".") + if (len(temp_list) == 2) and temp_list[0].isnumeric() and temp_list[1].isnumeric(): + self.__ver_str = ver + self.__ver_major = int(temp_list[0]) + self.__ver_minor = int(temp_list[1]) + else: + raise ValueError("Error: ver MUST be like '1.2' (major.minor)") + else: + raise TypeError("Error: ver MUST be a str!!!") + + @property + def verbose(self): + return self.__verbose + + @verbose.setter + def verbose(self, verbose): + if isinstance(verbose, int): + self.__verbose = verbose % 3 + else: + raise TypeError("Error: verbose MUST be [0, 1, 2]") + + @property + def swd_crp(self) -> int: + return self.__swd_crp + + @swd_crp.setter + def swd_crp(self, crp): + if isinstance(crp, int): + if crp == 0: + self.__swd_crp = 0 + else: + self.__swd_crp = 1 + else: + raise TypeError("Error: crp MUST be one of [0, 1]!!!") + + def __str__(self): + output_str = ( "\n------ mkimage ------\n" \ + "2nd boot: {_boot}\n" \ + "app.bin : {_app}\n" \ + "output : {_flash}\n" \ + "part_cfg: {_part}\n" \ + "ver str : {_ver}\n" \ + .format(_boot=self.boot_filepath, _app=self.app_filepath, + _flash=self.flashimage_filepath, _part=self.part_cfg_filepath, _ver=self.ver_str)) + return output_str + + +def main(*argv): + """ + The following arguments are required: + --boot /path/to/boot_ln88xx.bin, that is ramcode; + --app /path/to/app.bin, that is compiler output; + --output /path/to/flashimage.bin, that is our final image file which can be downloaded to flash; + --part /path/to/flash_partition_cfg.json, that is configuration for flash partition; + --ver APP version, like "1.2", but is only used for LN SDK boot, not for user app version; + + The following arguments are optional: + --crp which change the SWD behavior, 0 -- SWD protect is disabled; 1 -- SWD protect is enabled; + + Usage + ===== + python3 makeimage.py -h + """ + prog = os.path.basename(__file__) + desc = "makeimage tool for LN88XX" + parser = argparse.ArgumentParser(prog=prog, description=desc) + parser.add_argument("--boot", help="/path/to/boot_ln88xx.bin", type=str) + parser.add_argument("--app", help="/path/to/app.bin", type=str) + parser.add_argument("--output", help="/path/to/flashimage.bin, that is output filepath", type=str) + parser.add_argument("--part", help="/path/to/flash_partition_cfg.json", type=str) + parser.add_argument("--ver", help="APP version (only used for LN SDK boot), such as 1.2", type=str) + parser.add_argument("--crp", help="SWD protect bit [0 -- disable, 1 -- enable]", type=int, choices=[0, 1]) + + args = parser.parse_args() + + if args.boot is None: + print("Error: /path/to/boot_ln88xx.bin has not been set!!!") + exit(-1) + + if args.app is None: + print("Error: /path/to/app.bin has not been set!!!") + exit(-2) + + if args.output is None: + print("Error: /path/to/flashimage.bin has not been set!!!") + exit(-3) + + if args.part is None: + print("Error: /path/to/flash_partition_cfg.json has not been set!!!") + exit(-4) + + if args.ver is None: + print("Error: LN SDK boot version has not been set!!!") + exit(-5) + + mkimage = MakeImageTool() + mkimage.boot_filepath = args.boot + mkimage.app_filepath = args.app + mkimage.flashimage_filepath = args.output + mkimage.part_cfg_filepath = args.part + mkimage.ver_str = args.ver + + if args.crp: + mkimage.swd_crp = args.crp + + if not mkimage.doAllWork(): + exit(-1) + + exit(0) + +@click.command( + help="makeimage tool from ln882x sdk", + context_settings=dict( + help_option_names=[], + ignore_unknown_options=True, + ), +) +@click.argument("args", nargs=-1) +def cli(args: List[str]): + main(*args) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/ltchiptool/soc/ln882x/util/ota_image_generator.py b/ltchiptool/soc/ln882x/util/ota_image_generator.py new file mode 100644 index 0000000..67c12d6 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/ota_image_generator.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# +# Copyright 2021 Shanghai Lightning Semiconductor Technology Co., LTD + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import lzma +import sys +import os +import struct +import zlib +import shutil +import argparse +from .part_desc_info import * +from .image_header import * +import click +from typing import List + + +class OTATOOL: + def __init__(self): + self.part_desc_info_list = [] + self.image_header = None + self.app_content = None + self.output_filepath = None + self.__input_filepath = None + + def readPartTab(self) -> bool: + try: + with open(self.input_filepath, "rb") as fInputObj: + fInputObj.seek(PARTITION_TAB_OFFSET, os.SEEK_SET) + ptable_buffer = fInputObj.read(PARTITION_TAB_SIZE) + + offset = 0 + while offset < PARTITION_TAB_SIZE: + part_desc_info_buffer = ptable_buffer[offset : (offset + PARTITION_DESC_INFO_SIZE)] + part_type, start_addr, part_size, part_crc32 = struct.unpack("= PART_TYPE_INVALID: + break + crc32_recalc = zlib.crc32(part_desc_info_buffer[0:(3*4)]) & 0xFFFFFFFF + if part_crc32 != crc32_recalc: + break + # print("type: {_pt:>12}, start_addr: 0x{_sa:08X}, part_size: 0x{_ps:08X}, part_crc32: 0x{_pc:08X}" + # .format(_pt=part_type, _sa=start_addr, _ps=part_size, _pc=part_crc32)) + + part_desc_info_obj = PartDescInfo(part_type, start_addr, part_size, part_crc32) + self.part_desc_info_list.append(part_desc_info_obj) + offset += PARTITION_DESC_INFO_SIZE + except Exception as err: + print("Error: open file failed: {}".format(str(err))) + return False + if len(self.part_desc_info_list) >= 3: + return True + else: + return False + + def readAPP(self): + if len(self.part_desc_info_list) < 3: + print("Please make sure that partition table has at least 3 items!!!") + return False + + app_desc_info = None + for desc_info in self.part_desc_info_list: + if isinstance(desc_info, PartDescInfo): + if desc_info.part_type == PART_TYPE_APP: + app_desc_info = desc_info + break + + if app_desc_info is None: + print("Please make sure that APP partition is in the partition table!!!") + return False + + try: + with open(self.input_filepath, "rb") as fInputObj: + fInputObj.seek(app_desc_info.start_addr, os.SEEK_SET) + app_image_header_buffer = fInputObj.read(256) + + # image header + self.image_header = ImageHeader(app_image_header_buffer) + + # app content + if self.image_header.image_type == IMAGE_TYPE_ORIGINAL: + fInputObj.seek(app_desc_info.start_addr + 256, os.SEEK_SET) + self.app_content = fInputObj.read(self.image_header.img_size_orig) + else: + print("Not supported image type, which is {_t}".format(_t=image_type_num2str(self.image_header.image_type))) + return False + except Exception as err: + print("Error: open file failed: {}".format(str(err))) + return False + + return True + + def processOTAImage(self): + if (self.image_header is None) or (self.app_content is None): + print("No valid app image header or app conent found!!!") + return False + + app_content_size_before_lzma = len(self.app_content) + my_filter = [ + { + "id": lzma.FILTER_LZMA1, + "dict_size": 4*1024, # 4KB, (32KB max) + "mode": lzma.MODE_NORMAL, + }, + ] + lzc = lzma.LZMACompressor(format=lzma.FORMAT_ALONE, filters=my_filter) + out1 = lzc.compress(self.app_content) + content_after_lzma = bytearray(b"".join([out1, lzc.flush()])) + + content_after_lzma[5] = get_num_at_byte(app_content_size_before_lzma, 0) + content_after_lzma[6] = get_num_at_byte(app_content_size_before_lzma, 1) + content_after_lzma[7] = get_num_at_byte(app_content_size_before_lzma, 2) + content_after_lzma[8] = get_num_at_byte(app_content_size_before_lzma, 3) + + content_after_lzma[9] = 0 + content_after_lzma[10] = 0 + content_after_lzma[11] = 0 + content_after_lzma[12] = 0 + + app_content_size_after_lzma = len(content_after_lzma) + + self.app_content = content_after_lzma + crc32_after_lzma = zlib.crc32(content_after_lzma) + + self.image_header.image_type = IMAGE_TYPE_ORIGINAL_XZ + ota_ver_major = self.image_header.getVerMajor() + ota_ver_minor = self.image_header.getVerMinor() + self.image_header.ver = ((ota_ver_major << 8) | ota_ver_minor) & 0xFFFF + self.image_header.img_size_orig_xz = app_content_size_after_lzma + self.image_header.img_crc32_orig_xz = crc32_after_lzma + self.image_header.reCalcCRC32() + + return True + + def writeOTAImage(self): + """ + OTA image, XZ format. + """ + ota_filename = "{_a}-ota-xz-v{_ma}.{_mi}.bin" \ + .format(_a= os.path.basename(self.input_filepath).split(".")[0], + _ma=self.image_header.getVerMajor(), _mi=self.image_header.getVerMinor()) + self.output_filepath = os.path.join(self.output_dir, ota_filename) + + if os.path.exists(self.output_filepath): + shutil.rmtree(self.output_filepath, ignore_errors=True) + + try: + with open(self.output_filepath, "wb") as fOutObj: + fOutObj.write(self.image_header.toBytes()) + fOutObj.write(self.app_content) + except Exception as err: + print("Error: write file failed: {}".format(str(err))) + return False + + if not os.path.exists(self.output_filepath): + print("Failed to build: {_ota}".format(_ota=self.output_filepath)) + return False + + return True + + def doAllWork(self) -> bool: + if not self.readPartTab(): + return False + if not self.readAPP(): + return False + if not self.processOTAImage(): + return False + if not self.writeOTAImage(): + return False + return True + + @property + def input_filepath(self): + return self.__input_filepath + + @input_filepath.setter + def input_filepath(self, filepath): + """ + Absolute filepath of flashimage.bin. + """ + if isinstance(filepath, str): + if os.path.exists(realpath(filepath)): + self.__input_filepath = realpath(filepath) + else: + raise ValueError("not exist: {_f}".format(_f=filepath)) + else: + raise TypeError("filepath MUST be a valid string") + + @property + def output_dir(self): + return self.__output_dir + + @output_dir.setter + def output_dir(self, filepath): + """ + Indicates the directory where to save ota.bin, normally it's the same + directory as flashimage.bin. + The output filename is `flashimage-ota-v{X}.{Y}.bin`, where X/Y is the + major/minor version of flashimage.bin. + """ + if isinstance(filepath, str): + if os.path.exists(filepath): + self.__output_dir = filepath + else: + raise ValueError("dir not exist: {_f}".format(_f=filepath)) + else: + raise TypeError("dir MUST be a valid string") + + +def main(*argv): + prog = os.path.basename(__file__) + usage = ("\nargv1: /path/to/flashimage.bin \n" + "Example: \n" + "python3 {_p} E:/ln_sdk/build/bin/flashimage.bin".format(_p=prog)) + + parser = argparse.ArgumentParser(prog=prog, usage=usage) + parser.add_argument("path_to_flashimage", help="absolute path of flashimage.bin") + + print(sys.argv) + args = parser.parse_args() + + flashimage_filepath = args.path_to_flashimage + ota_save_dir = os.path.dirname(flashimage_filepath) + + ota_tool = OTATOOL() + ota_tool.input_filepath = flashimage_filepath + ota_tool.output_dir = ota_save_dir + + if not ota_tool.doAllWork(): + exit(-1) + + print("Succeed to build: {}".format(ota_tool.output_filepath)) + + exit(0) + +@click.command( + help="ota_image_generator tool from ln882x sdk", + context_settings=dict( + help_option_names=[], + ignore_unknown_options=True, + ), +) +@click.argument("args", nargs=-1) +def cli(args: List[str]): + main(*args) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/ltchiptool/soc/ln882x/util/part_desc_info.py b/ltchiptool/soc/ln882x/util/part_desc_info.py new file mode 100644 index 0000000..1410709 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/part_desc_info.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# +# Copyright 2021 Shanghai Lightning Semiconductor Technology Co., LTD + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import zlib +from .ln_tools import * +from .boot_header import BootHeader + +# partition table start addr and size. +PARTITION_TAB_OFFSET = BootHeader.BOOT_START_ADDR + BootHeader.BOOT_SIZE_LIMIT +PARTITION_TAB_SIZE = 1024 * 4 + +PARTITION_DESC_INFO_SIZE = 4 + 4 + 4 + 4 + +PART_TYPE_APP = 0 +PART_TYPE_OTA = 1 +PART_TYPE_KV = 2 +PART_TYPE_NVDS = 3 +PART_TYPE_SIMU_EEPROM = 4 +PART_TYPE_USER = 5 +PART_TYPE_INVALID = 6 +PART_TYPE_BOOT = 7 +PART_TYPE_PART_TAB = 8 + +__PART_TYPE_DICT = { + PART_TYPE_APP : "APP", + PART_TYPE_OTA : "OTA", + PART_TYPE_KV : "KV", + PART_TYPE_NVDS : "NVDS", + PART_TYPE_SIMU_EEPROM : "SIMU_EEPROM", + PART_TYPE_USER : "USER", + PART_TYPE_INVALID : "INVALID", + PART_TYPE_BOOT : "BOOT", + PART_TYPE_PART_TAB : "PART_TAB" +} + + +def part_type_num2str(type_num=PART_TYPE_INVALID): + return __PART_TYPE_DICT.get(type_num, __PART_TYPE_DICT.get(PART_TYPE_INVALID)) + + +def part_type_str2num(type_str): + for k, v in __PART_TYPE_DICT.items(): + if v == type_str: + return k + return PART_TYPE_INVALID + + +class PartDescInfo(object): + def __init__(self, parttype=0, startaddr=0, partsize=0, partcrc32=0): + self.part_type = parttype + self.start_addr = startaddr + self.part_size = partsize + self.__part_crc32 = partcrc32 + + self.buffer = bytearray(4 * 4) + for i in range(0, 4 * 4): + self.buffer[i] = 0 + + self.toBytes() + + def toBytes(self) -> bytearray: + self.buffer[0] = get_num_at_byte(self.part_type, 0) + self.buffer[1] = get_num_at_byte(self.part_type, 1) + self.buffer[2] = get_num_at_byte(self.part_type, 2) + self.buffer[3] = get_num_at_byte(self.part_type, 3) + + self.buffer[4] = get_num_at_byte(self.start_addr, 0) + self.buffer[5] = get_num_at_byte(self.start_addr, 1) + self.buffer[6] = get_num_at_byte(self.start_addr, 2) + self.buffer[7] = get_num_at_byte(self.start_addr, 3) + + self.buffer[8] = get_num_at_byte(self.part_size, 0) + self.buffer[9] = get_num_at_byte(self.part_size, 1) + self.buffer[10] = get_num_at_byte(self.part_size, 2) + self.buffer[11] = get_num_at_byte(self.part_size, 3) + + self.reCalCRC32() + + return self.buffer + + def reCalCRC32(self): + self.__part_crc32 = zlib.crc32(self.buffer[0:12]) + + self.buffer[12] = get_num_at_byte(self.part_crc32, 0) + self.buffer[13] = get_num_at_byte(self.part_crc32, 1) + self.buffer[14] = get_num_at_byte(self.part_crc32, 2) + self.buffer[15] = get_num_at_byte(self.part_crc32, 3) + + @property + def part_type(self): + return self.__part_type + + @part_type.setter + def part_type(self, t): + if isinstance(t, int): + self.__part_type = t + else: + raise TypeError("part_type MUST be assigned to an int value (0~5)") + + @property + def start_addr(self): + return self.__start_addr + + @start_addr.setter + def start_addr(self, addr): + if isinstance(addr, int): + self.__start_addr = addr + else: + raise TypeError("start_addr MUST be assigned to an int value") + + @property + def part_size(self): + return self.__part_size + + @part_size.setter + def part_size(self, s): + if isinstance(s, int): + self.__part_size = s + else: + raise TypeError("part_size MUST be assigned to an int value") + + @property + def part_crc32(self): + return self.__part_crc32 + + # readonly + # @part_crc32.setter + # def part_crc32(self, crc32): + # if isinstance(crc32, int): + # self.__part_crc32 = crc32 + # else: + # raise TypeError("part_crc32 MUST be assigned to an int value") + + def __str__(self) -> str: + output = ("partition_type: {_p:>12}, start_addr: 0x{_sa:08X}, size_KB: 0x{_sz:08X}, crc32: 0x{_c:08X}" + .format(_p=part_type_num2str(self.part_type), _sa=self.start_addr, _sz=self.part_size, _c=self.part_crc32)) + return output From 26af2ea0a7ebde51767179ca2b66596f796d997d Mon Sep 17 00:00:00 2001 From: lamauny Date: Sat, 4 Jan 2025 12:10:23 +0100 Subject: [PATCH 02/12] removed ln882x sdk python scirpts from cli & cleanup --- ltchiptool/commands/soc.py | 2 -- ltchiptool/soc/ln882x/util/__init__.py | 2 ++ ltchiptool/soc/ln882x/util/makeimage.py | 19 +------------------ .../soc/ln882x/util/ota_image_generator.py | 19 +------------------ 4 files changed, 4 insertions(+), 38 deletions(-) diff --git a/ltchiptool/commands/soc.py b/ltchiptool/commands/soc.py index b3fd07a..5ef40d5 100644 --- a/ltchiptool/commands/soc.py +++ b/ltchiptool/commands/soc.py @@ -9,8 +9,6 @@ "rtltool": "ltchiptool/soc/ambz/util/rtltool.py", "ambztool": "ltchiptool/soc/ambz/util/ambztool.py", "ambz2tool": "ltchiptool/soc/ambz2/util/ambz2tool.py", - "ln-makeimage": "ltchiptool/soc/ln882x/util/makeimage.py", - "ln-otagen": "ltchiptool/soc/ln882x/util/ota_image_generator.py", } diff --git a/ltchiptool/soc/ln882x/util/__init__.py b/ltchiptool/soc/ln882x/util/__init__.py index fc57745..6b539b4 100644 --- a/ltchiptool/soc/ln882x/util/__init__.py +++ b/ltchiptool/soc/ln882x/util/__init__.py @@ -1,7 +1,9 @@ # Copyright (c) Etienne Le Cousin 2025-01-02. from .makeimage import MakeImageTool +from .ota_image_generator import OTATOOL __all__ = [ "MakeImageTool", + "OTATOOL", ] diff --git a/ltchiptool/soc/ln882x/util/makeimage.py b/ltchiptool/soc/ln882x/util/makeimage.py index 5d22340..31569ba 100644 --- a/ltchiptool/soc/ln882x/util/makeimage.py +++ b/ltchiptool/soc/ln882x/util/makeimage.py @@ -17,8 +17,6 @@ import argparse import json -import click -from typing import List from .boot_header import * from .image_header import * @@ -419,7 +417,7 @@ def __str__(self): return output_str -def main(*argv): +if __name__ == "__main__": """ The following arguments are required: --boot /path/to/boot_ln88xx.bin, that is ramcode; @@ -481,18 +479,3 @@ def main(*argv): exit(-1) exit(0) - -@click.command( - help="makeimage tool from ln882x sdk", - context_settings=dict( - help_option_names=[], - ignore_unknown_options=True, - ), -) -@click.argument("args", nargs=-1) -def cli(args: List[str]): - main(*args) - - -if __name__ == "__main__": - main(*sys.argv[1:]) diff --git a/ltchiptool/soc/ln882x/util/ota_image_generator.py b/ltchiptool/soc/ln882x/util/ota_image_generator.py index 67c12d6..8f1f08f 100644 --- a/ltchiptool/soc/ln882x/util/ota_image_generator.py +++ b/ltchiptool/soc/ln882x/util/ota_image_generator.py @@ -24,8 +24,6 @@ import argparse from .part_desc_info import * from .image_header import * -import click -from typing import List class OTATOOL: @@ -219,7 +217,7 @@ def output_dir(self, filepath): raise TypeError("dir MUST be a valid string") -def main(*argv): +if __name__ == "__main__": prog = os.path.basename(__file__) usage = ("\nargv1: /path/to/flashimage.bin \n" "Example: \n" @@ -244,18 +242,3 @@ def main(*argv): print("Succeed to build: {}".format(ota_tool.output_filepath)) exit(0) - -@click.command( - help="ota_image_generator tool from ln882x sdk", - context_settings=dict( - help_option_names=[], - ignore_unknown_options=True, - ), -) -@click.argument("args", nargs=-1) -def cli(args: List[str]): - main(*args) - - -if __name__ == "__main__": - main(*sys.argv[1:]) From e4053bafd574061f4fccb7e4e71db04aaa837768 Mon Sep 17 00:00:00 2001 From: lamauny Date: Sat, 4 Jan 2025 12:12:14 +0100 Subject: [PATCH 03/12] ln882x : added generation of partcfg json file & ota file --- README.md | 2 +- ltchiptool/soc/ln882x/binary.py | 65 +++++++++++++++++++++++++++------ tests/firmware.json | 16 ++++++++ tests/flash_partition_cfg.json | 40 ++++++++++++++++++++ 4 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 tests/firmware.json create mode 100644 tests/flash_partition_cfg.json diff --git a/README.md b/README.md index 11b73e4..53482f4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ltchiptool -Universal, easy-to-use GUI flashing/dumping tool for BK7231, RTL8710B and RTL8720C. Also contains some CLI utilities for binary firmware manipulation. +Universal, easy-to-use GUI flashing/dumping tool for BK7231, LN882H, RTL8710B and RTL8720C. Also contains some CLI utilities for binary firmware manipulation.
diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882x/binary.py index 1b5a414..8ef55f9 100644 --- a/ltchiptool/soc/ln882x/binary.py +++ b/ltchiptool/soc/ln882x/binary.py @@ -5,35 +5,37 @@ from logging import warning from os import stat from typing import IO, List, Optional, Union +import json from ltchiptool import SocInterface from ltchiptool.util.detection import Detection from ltchiptool.util.fileio import chext -from os.path import dirname, expanduser, isdir, isfile, join, realpath +from os.path import basename, dirname, expanduser, isdir, isfile, join, realpath from ltchiptool.util.fwbinary import FirmwareBinary from ltchiptool.util.lvm import LVM -from .util import MakeImageTool +from .util import MakeImageTool, OTATOOL class LN882xBinary(SocInterface, ABC): def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: toolchain = self.board.toolchain lvm = LVM.get() - print("lvm:", lvm.path()) - bootfile = self.board["build.bootfile"] - flashpart = self.board["build.flashpart"] + bootfile = join(lvm.path(), f"cores", self.family.name, f"misc", self.board["build.bootfile"]) + part_cfg = join(dirname(input), "flash_partition_cfg.json") + + self.gen_partcfg_json(part_cfg) # build output names - output = FirmwareBinary( + output_fw = FirmwareBinary( location=input, name=f"firmware", subname="", ext="bin", title="Flash Image", - description="Complete image with boot for flashing to ln882x memory at offset 0", + description="Complete image with boot for flashing at offset 0", public=True, ) @@ -41,16 +43,33 @@ def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: # objcopy ELF -> raw BIN toolchain.objcopy(input, fw_bin) + # Make firmware image mkimage = MakeImageTool() - mkimage.boot_filepath = join(lvm.path(), f"cores", self.family.name, f"misc", bootfile) + mkimage.boot_filepath = bootfile mkimage.app_filepath = fw_bin - mkimage.flashimage_filepath = output.path - mkimage.part_cfg_filepath = join(lvm.path(), f"cores", self.family.name, f"base/config", flashpart) + mkimage.flashimage_filepath = output_fw.path + mkimage.part_cfg_filepath = part_cfg mkimage.ver_str = "1.0" mkimage.swd_crp = 0 mkimage.doAllWork() - return output.group() + # Make ota image + ota_tool = OTATOOL() + ota_tool.input_filepath = output_fw.path + ota_tool.output_dir = dirname(input) + ota_tool.doAllWork() + + output_ota = FirmwareBinary.load( + location = ota_tool.output_filepath, + obj = { + "filename": basename(ota_tool.output_filepath), + "title": "Flash OTA Image", + "description": "Compressed App image for OTA flashing", + "public": True, + } + ) + + return output_fw.group() def detect_file_type( self, @@ -59,3 +78,27 @@ def detect_file_type( ) -> Optional[Detection]: return None + + def gen_partcfg_json(self, output: str): + flash_layout = self.board["flash"] + + # find all partitions + partitions = [] + for name, layout in flash_layout.items(): + part = {} + (offset, _, length) = layout.partition("+") + offset = int(offset, 16) + length = int(length, 16) + part["partition_type"] = name.upper() + part["start_addr"] = f"0x{offset:08X}" + part["size_KB"] = length // 1024 + partitions.append(part) + + partcfg: dict = { + "vendor_define": [], # boot and part_tab should be there but it's not needed + "user_define": partitions # so put all partitions in user define + } + # export file + with open(output, "w") as f: + json.dump(partcfg, f, indent="\t") + diff --git a/tests/firmware.json b/tests/firmware.json new file mode 100644 index 0000000..279e8ed --- /dev/null +++ b/tests/firmware.json @@ -0,0 +1,16 @@ +[ + { + "title": "Flash Image", + "description": "Complete image with boot for flashing at offset 0", + "filename": "image_firmware.bin", + "offset": null, + "public": true + }, + { + "title": "Flash OTA Image", + "description": "Compressed App image for OTA flashing", + "filename": "image_firmware-ota-xz-v1.0.bin", + "offset": null, + "public": true + } +] \ No newline at end of file diff --git a/tests/flash_partition_cfg.json b/tests/flash_partition_cfg.json new file mode 100644 index 0000000..228de61 --- /dev/null +++ b/tests/flash_partition_cfg.json @@ -0,0 +1,40 @@ +{ + "vendor_define": [], + "user_define": [ + { + "partition_type": "BOOT", + "start_addr": "0x00000000", + "size_KB": 24 + }, + { + "partition_type": "PART_TAB", + "start_addr": "0x00006000", + "size_KB": 4 + }, + { + "partition_type": "APP", + "start_addr": "0x00007000", + "size_KB": 1200 + }, + { + "partition_type": "OTA", + "start_addr": "0x00133000", + "size_KB": 680 + }, + { + "partition_type": "NVDS", + "start_addr": "0x001DD000", + "size_KB": 12 + }, + { + "partition_type": "KV", + "start_addr": "0x001E0000", + "size_KB": 16 + }, + { + "partition_type": "USER", + "start_addr": "0x001E4000", + "size_KB": 112 + } + ] +} \ No newline at end of file From 67301620e153b1958b31f93c501f15aa702850ab Mon Sep 17 00:00:00 2001 From: lamauny Date: Sat, 4 Jan 2025 12:13:56 +0100 Subject: [PATCH 04/12] removed test files, oops --- tests/firmware.json | 16 -------------- tests/flash_partition_cfg.json | 40 ---------------------------------- 2 files changed, 56 deletions(-) delete mode 100644 tests/firmware.json delete mode 100644 tests/flash_partition_cfg.json diff --git a/tests/firmware.json b/tests/firmware.json deleted file mode 100644 index 279e8ed..0000000 --- a/tests/firmware.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "title": "Flash Image", - "description": "Complete image with boot for flashing at offset 0", - "filename": "image_firmware.bin", - "offset": null, - "public": true - }, - { - "title": "Flash OTA Image", - "description": "Compressed App image for OTA flashing", - "filename": "image_firmware-ota-xz-v1.0.bin", - "offset": null, - "public": true - } -] \ No newline at end of file diff --git a/tests/flash_partition_cfg.json b/tests/flash_partition_cfg.json deleted file mode 100644 index 228de61..0000000 --- a/tests/flash_partition_cfg.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "vendor_define": [], - "user_define": [ - { - "partition_type": "BOOT", - "start_addr": "0x00000000", - "size_KB": 24 - }, - { - "partition_type": "PART_TAB", - "start_addr": "0x00006000", - "size_KB": 4 - }, - { - "partition_type": "APP", - "start_addr": "0x00007000", - "size_KB": 1200 - }, - { - "partition_type": "OTA", - "start_addr": "0x00133000", - "size_KB": 680 - }, - { - "partition_type": "NVDS", - "start_addr": "0x001DD000", - "size_KB": 12 - }, - { - "partition_type": "KV", - "start_addr": "0x001E0000", - "size_KB": 16 - }, - { - "partition_type": "USER", - "start_addr": "0x001E4000", - "size_KB": 112 - } - ] -} \ No newline at end of file From e2aa33c5da7acddb16d239589d13912c13c72b85 Mon Sep 17 00:00:00 2001 From: lamauny Date: Sat, 4 Jan 2025 14:50:36 +0100 Subject: [PATCH 05/12] ln882x: cleanup flash tools because not implemented --- ltchiptool/soc/ln882x/binary.py | 20 +-- ltchiptool/soc/ln882x/flash.py | 280 +------------------------------- 2 files changed, 12 insertions(+), 288 deletions(-) diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882x/binary.py index 8ef55f9..370c651 100644 --- a/ltchiptool/soc/ln882x/binary.py +++ b/ltchiptool/soc/ln882x/binary.py @@ -4,15 +4,13 @@ from datetime import datetime from logging import warning from os import stat +from os.path import basename, dirname, join, realpath from typing import IO, List, Optional, Union import json from ltchiptool import SocInterface -from ltchiptool.util.detection import Detection from ltchiptool.util.fileio import chext -from os.path import basename, dirname, expanduser, isdir, isfile, join, realpath from ltchiptool.util.fwbinary import FirmwareBinary - from ltchiptool.util.lvm import LVM from .util import MakeImageTool, OTATOOL @@ -21,9 +19,8 @@ class LN882xBinary(SocInterface, ABC): def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: toolchain = self.board.toolchain - lvm = LVM.get() - bootfile = join(lvm.path(), f"cores", self.family.name, f"misc", self.board["build.bootfile"]) + bootfile = join(LVM.get().path(), f"cores", self.family.name, f"misc", self.board["build.bootfile"]) part_cfg = join(dirname(input), "flash_partition_cfg.json") self.gen_partcfg_json(part_cfg) @@ -68,17 +65,14 @@ def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: "public": True, } ) + _, ota_size, _ = self.board.region("ota") + if stat(ota_tool.output_filepath) > ota_size: + warning( + f"OTA size too large: {ota_tool.output_filepath} > {ota_size} (0x{ota_size:X})" + ) return output_fw.group() - def detect_file_type( - self, - file: IO[bytes], - length: int, - ) -> Optional[Detection]: - - return None - def gen_partcfg_json(self, output: str): flash_layout = self.board["flash"] diff --git a/ltchiptool/soc/ln882x/flash.py b/ltchiptool/soc/ln882x/flash.py index 9189e8d..2040176 100644 --- a/ltchiptool/soc/ln882x/flash.py +++ b/ltchiptool/soc/ln882x/flash.py @@ -7,14 +7,6 @@ from logging import DEBUG, debug, warning from typing import IO, Generator, List, Optional, Tuple, Union -from bk7231tools.serial import BK7231Serial -from bk7231tools.serial.base import BkChipType -from bk7231tools.serial.base.packets import ( - BkFlashReg24ReadCmnd, - BkReadRegCmnd, - BkWriteRegCmnd, -) - from ltchiptool import SocInterface from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType from ltchiptool.util.intbin import gen2bytes, inttole32 @@ -27,8 +19,8 @@ "Connect UART1 of the LN882x to the USB-TTL adapter:", [ ("PC", "LN882x"), - ("RX", "TX1 (GPIO11 / P11)"), - ("TX", "RX1 (GPIO10 / P10)"), + ("RX", "TX1 (GPIOA2 / P2)"), + ("TX", "RX1 (GPIOA3 / P3)"), ("", ""), ("GND", "GND"), ], @@ -39,280 +31,18 @@ "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.", + "In order to do that, you need to bridge CEN/BOOT pin (GPIOA9) to GND with a wire.", ] -SCTRL_EFUSE_CTRL = 0x00800074 -SCTRL_EFUSE_OPTR = 0x00800078 - class LN882xFlash(SocInterface, ABC): - bk: Optional[BK7231Serial] = None info: List[Tuple[str, str]] = None def flash_get_features(self) -> FlashFeatures: return FlashFeatures() def flash_get_guide(self) -> List[Union[str, list]]: - return BK72XX_GUIDE + return LN882X_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() - self.conn = connection - self.conn.fill_baudrate(115200) - - def flash_build_protocol(self, force: bool = False) -> None: - if not force and self.bk: - return - self.flash_disconnect() - self.bk = BK7231Serial( - port=self.conn.port, - baudrate=self.conn.baudrate, - link_baudrate=self.conn.link_baudrate, - ) - loglevel = logging.getLogger().getEffectiveLevel() - if loglevel <= DEBUG: - self.bk.info = lambda *args: debug(" ".join(map(str, args))) - if loglevel <= VERBOSE: - self.bk.debug = lambda *args: verbose(" ".join(map(str, args))) - self.bk.warn = lambda *args: warning(" ".join(map(str, args))) - 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.bk.cmnd_timeout = timeout - self.conn.timeout = timeout - if link_timeout: - self.bk.link_timeout = link_timeout - self.conn.link_timeout = link_timeout - - def flash_hw_reset(self) -> None: - self.flash_build_protocol() - self.bk.hw_reset() - - def flash_connect(self) -> None: - if self.bk and self.conn.linked: - return - self.flash_build_protocol() - self.bk.connect() - self.conn.linked = True - - def flash_disconnect(self) -> None: - if self.bk: - # avoid printing retry warnings - self.bk.warn = lambda *_: None - self.bk.close() - self.bk = None - if self.conn: - self.conn.linked = False - - def flash_get_chip_info(self) -> List[Tuple[str, str]]: - if self.info: - return self.info - self.flash_connect() - - self.info = [ - ("Protocol Type", self.bk.protocol_type.name), - ( - "Chip Type", - self.bk.chip_type and self.bk.chip_type.name or "Unrecognized", - ), - ( - "Bootloader Type", - self.bk.bootloader - and f"{self.bk.bootloader.chip.name} {self.bk.bootloader.version or ''}" - or "Unrecognized", - ), - ( - "Chip ID", - self.bk.bk_chip_id and hex(self.bk.bk_chip_id) or "N/A", - ), - ( - "Boot Version String", - self.bk.bk_boot_version or "N/A", - ), - ] - - if self.bk.chip_type == BkChipType.BK7231N: - tlv = self.bk.flash_read_bytes(0x1D0000, 0x1000) - elif self.bk.chip_type == BkChipType.BK7231T: - tlv = self.bk.flash_read_bytes(0x1E0000, 0x1000) - 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"), - ] - - self.info += [ - ("", ""), - ] - if self.bk.check_protocol(BkFlashReg24ReadCmnd): - flash_id = self.bk.flash_read_id() - self.info += [ - ("Flash ID", flash_id["id"].hex(" ").upper()), - ("Flash Size (by ID)", sizeof(flash_id["size"])), - ] - if self.bk.bootloader and self.bk.bootloader.flash_size: - self.info += [ - ("Flash Size (by BL)", sizeof(self.bk.bootloader.flash_size)), - ] - if self.bk.flash_size_detected: - self.info += [ - ("Flash Size (detected)", sizeof(self.bk.flash_size)), - ] - else: - flash_size = self.bk.flash_detect_size() - self.info += [ - ("Flash Size (detected)", sizeof(flash_size)), - ] - - if self.bk.check_protocol(BkReadRegCmnd): - 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: - self.flash_connect() - if memory == FlashMemoryType.FLASH: - return self.bk.flash_size - if memory == FlashMemoryType.ROM: - if not self.bk.check_protocol(BkReadRegCmnd): - raise NotImplementedError("Only BK7231N has built-in ROM") - return 16 * 1024 - if memory == FlashMemoryType.EFUSE: - if not self.bk.check_protocol(BkWriteRegCmnd): - raise NotImplementedError("Only BK7231N can read eFuse via UART") - return 32 - raise NotImplementedError("Memory type not readable via UART") - - def flash_read_raw( - self, - offset: int, - length: int, - verify: bool = True, - memory: FlashMemoryType = FlashMemoryType.FLASH, - callback: ProgressCallback = ProgressCallback(), - ) -> Generator[bytes, None, None]: - self.flash_connect() - - 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): - reg = self.bk.register_read(address) - 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") - - crc_offset = offset - crc_length = 0 - crc_value = 0 - - for chunk in self.bk.flash_read(start=offset, length=length, crc_check=False): - if not verify: - yield chunk - continue - - crc_length += len(chunk) - crc_value = crc32(chunk, crc_value) - # check CRC every each 32 KiB, or by the end of file - if crc_length < 32 * 1024 and crc_offset + crc_length != offset + length: - yield chunk - callback.on_update(len(chunk)) - continue - - debug(f"Checking CRC @ 0x{crc_offset:X}..0x{crc_offset + crc_length:X}") - crc_expected = self.bk.read_flash_range_crc( - crc_offset, - crc_offset + crc_length, - ) - if crc_expected != crc_value: - raise ValueError( - f"Chip CRC value {crc_expected:X} does not match calculated " - f"CRC value {crc_value:X} (at 0x{crc_offset:X})" - ) - crc_offset += crc_length - crc_length = 0 - crc_value = 0 - yield chunk - callback.on_update(len(chunk)) - - def flash_write_raw( - self, - offset: int, - length: int, - data: IO[bytes], - verify: bool = True, - callback: ProgressCallback = ProgressCallback(), - ) -> None: - self.flash_connect() - gen = self.bk.program_flash( - io=data, - io_size=length, - start=offset, - crc_check=verify, - ) - callback.update_from(gen) - - def flash_write_uf2( - self, - ctx: UploadContext, - verify: bool = True, - callback: ProgressCallback = ProgressCallback(), - ) -> None: - # collect continuous blocks of data (before linking, as this takes time) - parts = ctx.collect_data(OTAScheme.FLASHER_SINGLE) - callback.on_total(sum(len(part.getvalue()) for part in parts.values())) - - # connect to chip - self.flash_connect() - - # write blocks to flash - for offset, data in parts.items(): - length = len(data.getvalue()) - data.seek(0) - callback.on_message(f"Writing (0x{offset:06X})") - gen = self.bk.program_flash( - io=data, - io_size=length, - start=offset, - crc_check=verify, - dry_run=False, - really_erase=True, - ) - callback.update_from(gen) - - callback.on_message("Booting firmware") - # reboot the chip - self.bk.reboot_chip() + return "https://docs.libretiny.eu/link/flashing-ln882x" From b83eca2571661db8628c6f325eddac8177923126 Mon Sep 17 00:00:00 2001 From: lamauny Date: Sat, 4 Jan 2025 16:50:05 +0100 Subject: [PATCH 06/12] ln882x: fix check ota size --- ltchiptool/soc/ln882x/binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882x/binary.py index 370c651..f85d005 100644 --- a/ltchiptool/soc/ln882x/binary.py +++ b/ltchiptool/soc/ln882x/binary.py @@ -66,7 +66,7 @@ def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: } ) _, ota_size, _ = self.board.region("ota") - if stat(ota_tool.output_filepath) > ota_size: + if stat(ota_tool.output_filepath).st_size > ota_size: warning( f"OTA size too large: {ota_tool.output_filepath} > {ota_size} (0x{ota_size:X})" ) From 6d024eb445be3e2a753ebdc2647b76792d206458 Mon Sep 17 00:00:00 2001 From: lamauny Date: Mon, 3 Mar 2025 19:39:54 +0100 Subject: [PATCH 07/12] [ln882x] update firmware binaries generation with sparate partition images --- ltchiptool/soc/ln882x/binary.py | 140 ++++++++++-------- ltchiptool/soc/ln882x/util/makeimage.py | 7 +- ltchiptool/soc/ln882x/util/models/__init__.py | 13 ++ .../ln882x/util/{ => models}/boot_header.py | 1 + .../ln882x/util/{ => models}/image_header.py | 1 + .../soc/ln882x/util/{ => models}/ln_tools.py | 1 + .../util/{ => models}/part_desc_info.py | 1 + .../soc/ln882x/util/ota_image_generator.py | 5 +- 8 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 ltchiptool/soc/ln882x/util/models/__init__.py rename ltchiptool/soc/ln882x/util/{ => models}/boot_header.py (99%) rename ltchiptool/soc/ln882x/util/{ => models}/image_header.py (99%) rename ltchiptool/soc/ln882x/util/{ => models}/ln_tools.py (99%) rename ltchiptool/soc/ln882x/util/{ => models}/part_desc_info.py (99%) diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882x/binary.py index f85d005..5c61d81 100644 --- a/ltchiptool/soc/ln882x/binary.py +++ b/ltchiptool/soc/ln882x/binary.py @@ -1,98 +1,118 @@ # Copyright (c) Etienne Le Cousin 2025-01-02. from abc import ABC -from datetime import datetime from logging import warning from os import stat -from os.path import basename, dirname, join, realpath -from typing import IO, List, Optional, Union -import json +from os.path import dirname, isfile +from shutil import copyfile +from typing import List from ltchiptool import SocInterface -from ltchiptool.util.fileio import chext +from ltchiptool.util.fileio import chext, chname from ltchiptool.util.fwbinary import FirmwareBinary -from ltchiptool.util.lvm import LVM from .util import MakeImageTool, OTATOOL +from .util.models import PartDescInfo, part_type_str2num class LN882xBinary(SocInterface, ABC): def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: toolchain = self.board.toolchain + flash_layout = self.board["flash"] - bootfile = join(LVM.get().path(), f"cores", self.family.name, f"misc", self.board["build.bootfile"]) - part_cfg = join(dirname(input), "flash_partition_cfg.json") - - self.gen_partcfg_json(part_cfg) + # find bootloader image + input_boot = chname(input, "boot.bin") + if not isfile(input_boot): + raise FileNotFoundError("Bootloader image not found") # build output names - output_fw = FirmwareBinary( + output = FirmwareBinary( location=input, - name=f"firmware", - subname="", - ext="bin", + name="firmware", + offset=0, title="Flash Image", description="Complete image with boot for flashing at offset 0", public=True, ) + out_boot = FirmwareBinary( + location=input, + name="boot", + offset=self.board.region("boot")[0], + title="Bootloader Image", + ) + out_ptab = FirmwareBinary( + location=input, + name="part_tab", + offset=self.board.region("part_tab")[0], + title="Partition Table", + ) + out_app = FirmwareBinary( + location=input, + name="app", + offset=self.board.region("app")[0], + title="Application Image", + description="Firmware partition image for direct flashing", + public=True, + ) + out_ota = FirmwareBinary( + location=input, + name="ota", + offset=self.board.region("ota")[0], + title="OTA Image", + description="Compressed App image for OTA flashing", + public=True, + ) + # print graph element + output.graph(1) - fw_bin = chext(input, "bin") + input_bin = chext(input, "bin") # objcopy ELF -> raw BIN - toolchain.objcopy(input, fw_bin) + toolchain.objcopy(input, input_bin) - # Make firmware image + # Make Image Tool + # fmt: off mkimage = MakeImageTool() - mkimage.boot_filepath = bootfile - mkimage.app_filepath = fw_bin - mkimage.flashimage_filepath = output_fw.path - mkimage.part_cfg_filepath = part_cfg + mkimage.boot_filepath = input_boot + mkimage.app_filepath = input_bin + mkimage.flashimage_filepath = output.path mkimage.ver_str = "1.0" mkimage.swd_crp = 0 - mkimage.doAllWork() + mkimage.readPartCfg = lambda : True + # fmt: off + + # find all partitions + for name, layout in flash_layout.items(): + (offset, _, length) = layout.partition("+") + part_info = PartDescInfo( + parttype = part_type_str2num(name.upper()), + startaddr = int(offset, 16), + partsize = int(length, 16) + ) + mkimage._MakeImageTool__part_desc_info_list.append(part_info) + + if not mkimage.doAllWork(): + raise RuntimeError("MakeImageTool: Fail to generate image") + + # write all parts to files + with out_boot.write() as f: + f.write(mkimage._MakeImageTool__partbuf_bootram) + with out_ptab.write() as f: + f.write(mkimage._MakeImageTool__partbuf_parttab) + with out_app.write() as f: + f.write(mkimage._MakeImageTool__partbuf_app) # Make ota image ota_tool = OTATOOL() - ota_tool.input_filepath = output_fw.path + ota_tool.input_filepath = output.path ota_tool.output_dir = dirname(input) - ota_tool.doAllWork() + if not ota_tool.doAllWork(): + raise RuntimeError("MakeImageTool: Fail to generate OTA image") - output_ota = FirmwareBinary.load( - location = ota_tool.output_filepath, - obj = { - "filename": basename(ota_tool.output_filepath), - "title": "Flash OTA Image", - "description": "Compressed App image for OTA flashing", - "public": True, - } - ) + copyfile(ota_tool.output_filepath, out_ota.path) _, ota_size, _ = self.board.region("ota") - if stat(ota_tool.output_filepath).st_size > ota_size: + if stat(out_ota.path).st_size > ota_size: warning( - f"OTA size too large: {ota_tool.output_filepath} > {ota_size} (0x{ota_size:X})" + f"OTA size too large: {out_ota.filename} > {ota_size} (0x{ota_size:X})" ) - return output_fw.group() - - def gen_partcfg_json(self, output: str): - flash_layout = self.board["flash"] - - # find all partitions - partitions = [] - for name, layout in flash_layout.items(): - part = {} - (offset, _, length) = layout.partition("+") - offset = int(offset, 16) - length = int(length, 16) - part["partition_type"] = name.upper() - part["start_addr"] = f"0x{offset:08X}" - part["size_KB"] = length // 1024 - partitions.append(part) - - partcfg: dict = { - "vendor_define": [], # boot and part_tab should be there but it's not needed - "user_define": partitions # so put all partitions in user define - } - # export file - with open(output, "w") as f: - json.dump(partcfg, f, indent="\t") - + return output.group() diff --git a/ltchiptool/soc/ln882x/util/makeimage.py b/ltchiptool/soc/ln882x/util/makeimage.py index 31569ba..2fd84fc 100644 --- a/ltchiptool/soc/ln882x/util/makeimage.py +++ b/ltchiptool/soc/ln882x/util/makeimage.py @@ -14,13 +14,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import argparse import json -from .boot_header import * -from .image_header import * -from .part_desc_info import * +from .models.boot_header import * +from .models.image_header import * +from .models.part_desc_info import * class MakeImageTool: diff --git a/ltchiptool/soc/ln882x/util/models/__init__.py b/ltchiptool/soc/ln882x/util/models/__init__.py new file mode 100644 index 0000000..4eb12d6 --- /dev/null +++ b/ltchiptool/soc/ln882x/util/models/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) Etienne Le Cousin 2025-03-02. + +from .boot_header import BootHeader +from .image_header import ImageHeader +from .part_desc_info import * + +__all__ = [ + "BootHeader", + "ImageHeader", + "PartDescInfo", + "part_type_str2num", + "part_type_num2str", +] diff --git a/ltchiptool/soc/ln882x/util/boot_header.py b/ltchiptool/soc/ln882x/util/models/boot_header.py similarity index 99% rename from ltchiptool/soc/ln882x/util/boot_header.py rename to ltchiptool/soc/ln882x/util/models/boot_header.py index 508a930..d192fa0 100644 --- a/ltchiptool/soc/ln882x/util/boot_header.py +++ b/ltchiptool/soc/ln882x/util/models/boot_header.py @@ -14,6 +14,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import zlib import struct diff --git a/ltchiptool/soc/ln882x/util/image_header.py b/ltchiptool/soc/ln882x/util/models/image_header.py similarity index 99% rename from ltchiptool/soc/ln882x/util/image_header.py rename to ltchiptool/soc/ln882x/util/models/image_header.py index 12b7e53..9d18a0b 100644 --- a/ltchiptool/soc/ln882x/util/image_header.py +++ b/ltchiptool/soc/ln882x/util/models/image_header.py @@ -14,6 +14,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import struct import zlib diff --git a/ltchiptool/soc/ln882x/util/ln_tools.py b/ltchiptool/soc/ln882x/util/models/ln_tools.py similarity index 99% rename from ltchiptool/soc/ln882x/util/ln_tools.py rename to ltchiptool/soc/ln882x/util/models/ln_tools.py index 1cbca2a..efbdf2b 100644 --- a/ltchiptool/soc/ln882x/util/ln_tools.py +++ b/ltchiptool/soc/ln882x/util/models/ln_tools.py @@ -14,6 +14,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import os import sys diff --git a/ltchiptool/soc/ln882x/util/part_desc_info.py b/ltchiptool/soc/ln882x/util/models/part_desc_info.py similarity index 99% rename from ltchiptool/soc/ln882x/util/part_desc_info.py rename to ltchiptool/soc/ln882x/util/models/part_desc_info.py index 1410709..8bef270 100644 --- a/ltchiptool/soc/ln882x/util/part_desc_info.py +++ b/ltchiptool/soc/ln882x/util/models/part_desc_info.py @@ -14,6 +14,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import zlib from .ln_tools import * diff --git a/ltchiptool/soc/ln882x/util/ota_image_generator.py b/ltchiptool/soc/ln882x/util/ota_image_generator.py index 8f1f08f..c50407c 100644 --- a/ltchiptool/soc/ln882x/util/ota_image_generator.py +++ b/ltchiptool/soc/ln882x/util/ota_image_generator.py @@ -14,6 +14,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# fmt: off import lzma import sys @@ -22,8 +23,8 @@ import zlib import shutil import argparse -from .part_desc_info import * -from .image_header import * +from .models.part_desc_info import * +from .models.image_header import * class OTATOOL: From 8b8482a144ed97cf687301f4c19171547d3edb99 Mon Sep 17 00:00:00 2001 From: lamauny Date: Sun, 16 Mar 2025 10:28:16 +0100 Subject: [PATCH 08/12] [ln882h] changed ln882x->ln882h; first flash tools release --- ltchiptool/commands/soc.py | 1 + ltchiptool/soc/interface.py | 8 +- ltchiptool/soc/{ln882x => ln882h}/__init__.py | 4 +- ltchiptool/soc/{ln882x => ln882h}/binary.py | 2 +- ltchiptool/soc/ln882h/flash.py | 204 ++++++++++++++ ltchiptool/soc/{ln882x => ln882h}/main.py | 12 +- .../soc/{ln882x => ln882h}/util/__init__.py | 0 ltchiptool/soc/ln882h/util/ln882htool.py | 262 ++++++++++++++++++ .../soc/{ln882x => ln882h}/util/makeimage.py | 0 .../util/models/__init__.py | 0 .../util/models/boot_header.py | 0 .../util/models/image_header.py | 0 .../util/models/ln_tools.py | 0 .../util/models/part_desc_info.py | 0 .../util/ota_image_generator.py | 0 ltchiptool/soc/ln882x/flash.py | 48 ---- pyproject.toml | 1 + 17 files changed, 481 insertions(+), 61 deletions(-) rename ltchiptool/soc/{ln882x => ln882h}/__init__.py (56%) rename ltchiptool/soc/{ln882x => ln882h}/binary.py (99%) create mode 100644 ltchiptool/soc/ln882h/flash.py rename ltchiptool/soc/{ln882x => ln882h}/main.py (79%) rename ltchiptool/soc/{ln882x => ln882h}/util/__init__.py (100%) create mode 100644 ltchiptool/soc/ln882h/util/ln882htool.py rename ltchiptool/soc/{ln882x => ln882h}/util/makeimage.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/models/__init__.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/models/boot_header.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/models/image_header.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/models/ln_tools.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/models/part_desc_info.py (100%) rename ltchiptool/soc/{ln882x => ln882h}/util/ota_image_generator.py (100%) delete mode 100644 ltchiptool/soc/ln882x/flash.py diff --git a/ltchiptool/commands/soc.py b/ltchiptool/commands/soc.py index 5ef40d5..cf5eff4 100644 --- a/ltchiptool/commands/soc.py +++ b/ltchiptool/commands/soc.py @@ -9,6 +9,7 @@ "rtltool": "ltchiptool/soc/ambz/util/rtltool.py", "ambztool": "ltchiptool/soc/ambz/util/ambztool.py", "ambz2tool": "ltchiptool/soc/ambz2/util/ambz2tool.py", + "ln882htool": "ltchiptool/soc/ln882h/util/ln882htool.py", } diff --git a/ltchiptool/soc/interface.py b/ltchiptool/soc/interface.py index 4af3055..ccef695 100644 --- a/ltchiptool/soc/interface.py +++ b/ltchiptool/soc/interface.py @@ -28,9 +28,9 @@ def get(cls, family: Family) -> "SocInterface": if family.is_child_of("realtek-ambz2"): from .ambz2 import AmebaZ2Main return AmebaZ2Main(family) - if family.is_child_of("lightning-ln882x"): - from .ln882x import LN882xMain - return LN882xMain(family) + if family.is_child_of("lightning-ln882h"): + from .ln882h import LN882hMain + return LN882hMain(family) # fmt: on raise NotImplementedError(f"Unsupported family - {family.name}") @@ -41,7 +41,7 @@ def get_family_names(cls) -> List[str]: "beken-72xx", "realtek-ambz", "realtek-ambz2", - "lightning-ln882x", + "lightning-ln882h", ] ######################### diff --git a/ltchiptool/soc/ln882x/__init__.py b/ltchiptool/soc/ln882h/__init__.py similarity index 56% rename from ltchiptool/soc/ln882x/__init__.py rename to ltchiptool/soc/ln882h/__init__.py index 852c182..2f2c3ed 100644 --- a/ltchiptool/soc/ln882x/__init__.py +++ b/ltchiptool/soc/ln882h/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) Etienne Le Cousin 2025-01-02. -from .main import LN882xMain +from .main import LN882hMain __all__ = [ - "LN882xMain", + "LN882hMain", ] diff --git a/ltchiptool/soc/ln882x/binary.py b/ltchiptool/soc/ln882h/binary.py similarity index 99% rename from ltchiptool/soc/ln882x/binary.py rename to ltchiptool/soc/ln882h/binary.py index 5c61d81..c225ee7 100644 --- a/ltchiptool/soc/ln882x/binary.py +++ b/ltchiptool/soc/ln882h/binary.py @@ -15,7 +15,7 @@ from .util.models import PartDescInfo, part_type_str2num -class LN882xBinary(SocInterface, ABC): +class LN882hBinary(SocInterface, ABC): def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: toolchain = self.board.toolchain flash_layout = self.board["flash"] diff --git a/ltchiptool/soc/ln882h/flash.py b/ltchiptool/soc/ln882h/flash.py new file mode 100644 index 0000000..7476fb8 --- /dev/null +++ b/ltchiptool/soc/ln882h/flash.py @@ -0,0 +1,204 @@ +# Copyright (c) Etienne Le Cousin 2025-01-02. + +import logging +import struct +from abc import ABC +from binascii import crc32 +from logging import DEBUG, debug, warning +from typing import IO, Generator, List, Optional, Tuple, Union + +from ltchiptool import SocInterface +from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType +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 + +from .util.ln882htool import LN882hTool + +LN882H_GUIDE = [ + "Connect UART1 of the LN882h to the USB-TTL adapter:", + [ + ("PC", "LN882h"), + ("RX", "TX1 (GPIOA2 / P2)"), + ("TX", "RX1 (GPIOA3 / P3)"), + ("", ""), + ("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.", + "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 BOOT pin (GPIOA9) to GND with a wire.", +] + + +class LN882hFlash(SocInterface, ABC): + ln882h: Optional[LN882hTool] = None + info: List[Tuple[str, str]] = None + + def flash_get_features(self) -> FlashFeatures: + return FlashFeatures() + + def flash_get_guide(self) -> List[Union[str, list]]: + return LN882H_GUIDE + + def flash_get_docs_url(self) -> Optional[str]: + return "https://docs.libretiny.eu/link/flashing-ln882h" + + def flash_set_connection(self, connection: FlashConnection) -> None: + if self.conn: + self.flash_disconnect() + self.conn = connection + self.conn.fill_baudrate(115200) + + def flash_build_protocol(self, force: bool = False) -> None: + if not force and self.ln882h: + return + self.flash_disconnect() + self.ln882h = LN882hTool( + port=self.conn.port, + baudrate=self.conn.link_baudrate, + ) + 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.ln882h.read_timeout = timeout + self.conn.timeout = timeout + if link_timeout: + self.ln882h.link_timeout = link_timeout + self.conn.link_timeout = link_timeout + + def flash_connect(self, callback: ProgressCallback = ProgressCallback()) -> None: + if self.ln882h and self.conn.linked: + return + self.flash_build_protocol() + assert self.ln882h + self.ln882h.link() + + def cb(i, n, t, sent): + callback.on_update(sent-cb.total_sent); + cb.total_sent = sent + cb.total_sent = 0 + + callback.on_message(f"Loading Ram Code") + self.ln882h.ram_boot(cb) + self.conn.linked = True + + def flash_disconnect(self) -> None: + if self.ln882h: + self.ln882h.close() + self.ln882h = None + if self.conn: + self.conn.linked = False + + def flash_get_chip_info(self) -> List[Tuple[str, str]]: + if self.info: + return self.info + self.flash_connect() + assert self.ln882h + + flash_info = self.ln882h.command("flash_info")[-1] + flash_info = dict(s.split(':') for s in flash_info.split(',')) + + self.info = [ + ("Flash ID", flash_info['id']), + ("Flash Size", flash_info['flash size']), + ("Flash UUID", self.ln882h.command("flash_uid")[-1][10:]), + ("OTP MAC", self.ln882h.command("get_mac_in_flash_otp")[-2]), + ] + return self.info + + def flash_get_chip_info_string(self) -> str: + self.flash_connect() + assert self.ln882h + return "LN882H" + + def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: + self.flash_connect() + assert self.ln882h + if memory == FlashMemoryType.EFUSE: + raise NotImplementedError("Memory type not readable via UART") + else: + # It appears that flash size is coded in the low byte of flash ID as 2^X + # Ex: LN882HKI id=0xEB6015 --> 0x15 = 21 --> flash_size = 2^21 = 2MB + flash_info = self.ln882h.command("flash_info")[-1] + flash_info = dict(s.split(':') for s in flash_info.split(',')) + flash_size = 1 << (int(flash_info['id'], 16) & 0xff) + return flash_size + + def flash_read_raw( + self, + offset: int, + length: int, + verify: bool = True, + memory: FlashMemoryType = FlashMemoryType.FLASH, + callback: ProgressCallback = ProgressCallback(), + ) -> Generator[bytes, None, None]: + self.flash_connect() + assert self.ln882h + if memory == FlashMemoryType.EFUSE: + raise NotImplementedError("Memory type not readable via UART") + else: + gen = self.ln882h.flash_read( + offset=offset, + length=length, + verify=verify, + ) + yield from callback.update_with(gen) + + def flash_write_raw( + self, + offset: int, + length: int, + data: IO[bytes], + verify: bool = True, + callback: ProgressCallback = ProgressCallback(), + ) -> None: + self.flash_connect() + assert self.ln882h + def cb(i, n, t, sent): + callback.on_update(sent-cb.total_sent); + cb.total_sent = sent + cb.total_sent = 0 + + self.ln882h.flash_write( + offset=offset, + stream=data, + callback=cb, + ) + + def flash_write_uf2( + self, + ctx: UploadContext, + verify: bool = True, + callback: ProgressCallback = ProgressCallback(), + ) -> None: + # collect continuous blocks of data (before linking, as this takes time) + parts = ctx.collect_data(OTAScheme.FLASHER_SINGLE) + callback.on_total(sum(len(part.getvalue()) for part in parts.values())) + + # connect to chip + self.flash_connect() + + # write blocks to flash + for offset, data in parts.items(): + length = len(data.getvalue()) + data.seek(0) + callback.on_message(f"Writing (0x{offset:06X})") + gen = self.flash_write_raw( + offset=offset, + data=data, + length=length, + callback=callback, + ) + + callback.on_message("Booting firmware") + # reboot the chip + self.ln882h.disconnect() diff --git a/ltchiptool/soc/ln882x/main.py b/ltchiptool/soc/ln882h/main.py similarity index 79% rename from ltchiptool/soc/ln882x/main.py rename to ltchiptool/soc/ln882h/main.py index cf207e9..4b9353d 100644 --- a/ltchiptool/soc/ln882x/main.py +++ b/ltchiptool/soc/ln882h/main.py @@ -8,13 +8,13 @@ from ltchiptool.models import OTAType from ltchiptool.soc import SocInterfaceCommon -from .binary import LN882xBinary -from .flash import LN882xFlash +from .binary import LN882hBinary +from .flash import LN882hFlash -class LN882xMain( - LN882xBinary, - LN882xFlash, +class LN882hMain( + LN882hBinary, + LN882hFlash, SocInterfaceCommon, ABC, ): @@ -23,7 +23,7 @@ def __init__(self, family: Family) -> None: self.family = family def hello(self): - info("Hello from LN882x") + info("Hello from LN882h") @property def ota_type(self) -> Optional[OTAType]: diff --git a/ltchiptool/soc/ln882x/util/__init__.py b/ltchiptool/soc/ln882h/util/__init__.py similarity index 100% rename from ltchiptool/soc/ln882x/util/__init__.py rename to ltchiptool/soc/ln882h/util/__init__.py diff --git a/ltchiptool/soc/ln882h/util/ln882htool.py b/ltchiptool/soc/ln882h/util/ln882htool.py new file mode 100644 index 0000000..b9f0c31 --- /dev/null +++ b/ltchiptool/soc/ln882h/util/ln882htool.py @@ -0,0 +1,262 @@ +# Copyright (c) Etienne Le Cousin 2025-02-23. + +import logging +from enum import IntEnum +from hashlib import sha256 +from io import BytesIO +from tempfile import NamedTemporaryFile +from os import path, stat +from logging import debug, info, warning +from math import ceil +from time import time, sleep +from typing import IO, Callable, Generator, List, Optional, Tuple + +import click +from hexdump import hexdump, restore +from serial import Serial +from ymodem.Socket import ModemSocket + +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_YmodemCB = Optional[Callable[[int, str, int, int], None]] + +LN882H_YM_BAUDRATE = 2000000 +LN882H_ROM_BAUDRATE = 115200 +LN882H_FLASH_ADDRESS = 0x0000000 +LN882H_RAM_ADDRESS = 0x20000000 +LN882H_BOOTRAM_FILE = 'ramcode.bin' + + +class LN882hTool(SerialToolBase): + ramcode = False + + def __init__( + self, + port: str, + baudrate: int, + link_timeout: float = 10.0, + read_timeout: float = 0.2, + retry_count: int = 10, + ): + super().__init__(port, baudrate, link_timeout, read_timeout, retry_count) + self.ym = ModemSocket( + read=lambda size, timeout=2: self.read(size) or None, + write=lambda data, timeout=2: self.write(data), + packet_size=128 # it seems that ramcode doesn't support 1k packets for filename... + ) + + ######################################### + # Private # + ######################################### + + # Redefinition of readlines because romloader omit the last '\n' on some cmds... + 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 + if response: # add the last received "line" if any + yield response + raise TimeoutError("Timeout in readlines() - no more data received") + + ######################################### + # Basic commands - public low-level API # + ######################################### + + def command(self, cmd: str, waitresp: bool = True) -> str: + debug(f"cmd: {cmd}") + self.flush() + self.write(cmd.encode() + b"\r\n") + # remove ramcode echo + if self.ramcode: + self.read(len(cmd)) + return waitresp and self.resp() or None + + def resp(self) -> str: + r = [] + try: + for l in self.readlines(): + r.append(l) + except TimeoutError: + pass + if not r: + raise TimeoutError("No response") + debug(f"resp: {r}") + return r + + def ping(self) -> None: + self.ramcode = False + resp = self.command("version")[-1] + if resp == "RAMCODE": + self.ramcode = True + elif len(resp) != 20 or resp[11] != '/': + raise RuntimeError(f"Incorrect ping response: {resp!r}") + + def disconnect(self) -> None: + self.sw_reset() + + def link(self) -> None: + end = time() + self.link_timeout + while time() < end: + try: + self.ping() + return + except (RuntimeError, TimeoutError): + pass + raise TimeoutError("Timeout while linking") + + def sw_reset(self) -> None: + self.command("reboot", waitresp=False) + + def change_baudrate(self, baudrate: int) -> None: + if self.s.baudrate == baudrate: + return + self.flush() + self.command(f"baudrate {baudrate}", waitresp=False) + self.flush() + self.set_baudrate(baudrate) + + + ############################################### + # Flash-related commands - for internal usage # + ############################################### + + def ram_boot( + self, + callback: _T_YmodemCB = None, + ) -> None: + if self.ramcode: + return + + info("Loading RAM Code...") + ramcode_file = path.join(path.dirname(__file__), LN882H_BOOTRAM_FILE) + ramcode_size = stat(ramcode_file).st_size + + self.command(f"download [rambin] [0x{LN882H_RAM_ADDRESS:X}] [{ramcode_size}]", waitresp=False) + + self.push_timeout(3) + debug(f"YMODEM: transmitting to 0x{LN882H_RAM_ADDRESS:X}") + if not self.ym.send([ramcode_file], callback=callback): + self.pop_timeout() + raise RuntimeError("YMODEM transmission failed") + info("RAM Code successfully loaded.") + self.pop_timeout() + + # wait for boot start + sleep(2) + self.link() + + if not self.ramcode: + raise RuntimeError("RAM boot failed") + + ####################################### + # Memory-related commands - public API # + ####################################### + + def flash_read( + self, + offset: int, + length: int, + verify: bool = True, + chunk_size: int = 256, # maximum supported chunk size + ) -> Generator[bytes, None, None]: + self.link() + if not self.ramcode: + self.ram_boot() + + if chunk_size > 256: + raise RuntimeError(f"Chunk size {chunk_size} exceeds the maximum allowed (256)") + + 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}") + + resp = self.command(f"flash_read 0x{start:X} 0x{count:X}")[-1] + data = bytearray.fromhex(resp.decode()) + + valid, data = self.ym._verify_recv_checksum(True, data) + if verify and not valid: + raise RuntimeError(f"Invalid checksum") + + yield data + + def flash_write( + self, + offset: int, + stream: IO[bytes], + callback: _T_YmodemCB = None, + ) -> None: + self.link() + prev_baudrate = self.s.baudrate + if not self.ramcode: + self.ram_boot() + + self.change_baudrate(LN882H_YM_BAUDRATE) + self.link() + + self.command(f"startaddr 0x{offset:X}") + + # Convert stream to temporary file before sending with YMODEM + tmp_file = NamedTemporaryFile() + with open(tmp_file.name, 'wb') as f: + f.write(stream.getbuffer()) + + self.command(f"upgrade", waitresp=False) + + self.push_timeout(3) + debug(f"YMODEM: transmitting to 0x{offset:X}") + if not self.ym.send([f.name], callback=callback): + self.change_baudrate(prev_baudrate) + self.pop_timeout() + raise RuntimeError("YMODEM transmission failed") + + self.link() + + self.change_baudrate(prev_baudrate) + self.pop_timeout() + info("Flash Successful.") + +@click.command( + help="LN882H flashing tool", +) +@click.option( + "-d", + "--device", + help="Target device port (default: auto detect)", + type=DevicePortParamType(), + default=(), +) +def cli(device: str): + ln882h = LN882HTool(port=device, baudrate=LN882H_ROM_BAUDRATE) + info("Linking...") + ln882h.link() + + info("Loading Ram code...") + ln882h.ram_boot() + + flash_info = ln882h.command("flash_info")[-1] + info(f"Received flash info: {flash_info}") + + info("Disconnecting...") + ln882h.disconnect() + + + +if __name__ == "__main__": + cli() diff --git a/ltchiptool/soc/ln882x/util/makeimage.py b/ltchiptool/soc/ln882h/util/makeimage.py similarity index 100% rename from ltchiptool/soc/ln882x/util/makeimage.py rename to ltchiptool/soc/ln882h/util/makeimage.py diff --git a/ltchiptool/soc/ln882x/util/models/__init__.py b/ltchiptool/soc/ln882h/util/models/__init__.py similarity index 100% rename from ltchiptool/soc/ln882x/util/models/__init__.py rename to ltchiptool/soc/ln882h/util/models/__init__.py diff --git a/ltchiptool/soc/ln882x/util/models/boot_header.py b/ltchiptool/soc/ln882h/util/models/boot_header.py similarity index 100% rename from ltchiptool/soc/ln882x/util/models/boot_header.py rename to ltchiptool/soc/ln882h/util/models/boot_header.py diff --git a/ltchiptool/soc/ln882x/util/models/image_header.py b/ltchiptool/soc/ln882h/util/models/image_header.py similarity index 100% rename from ltchiptool/soc/ln882x/util/models/image_header.py rename to ltchiptool/soc/ln882h/util/models/image_header.py diff --git a/ltchiptool/soc/ln882x/util/models/ln_tools.py b/ltchiptool/soc/ln882h/util/models/ln_tools.py similarity index 100% rename from ltchiptool/soc/ln882x/util/models/ln_tools.py rename to ltchiptool/soc/ln882h/util/models/ln_tools.py diff --git a/ltchiptool/soc/ln882x/util/models/part_desc_info.py b/ltchiptool/soc/ln882h/util/models/part_desc_info.py similarity index 100% rename from ltchiptool/soc/ln882x/util/models/part_desc_info.py rename to ltchiptool/soc/ln882h/util/models/part_desc_info.py diff --git a/ltchiptool/soc/ln882x/util/ota_image_generator.py b/ltchiptool/soc/ln882h/util/ota_image_generator.py similarity index 100% rename from ltchiptool/soc/ln882x/util/ota_image_generator.py rename to ltchiptool/soc/ln882h/util/ota_image_generator.py diff --git a/ltchiptool/soc/ln882x/flash.py b/ltchiptool/soc/ln882x/flash.py deleted file mode 100644 index 2040176..0000000 --- a/ltchiptool/soc/ln882x/flash.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) Etienne Le Cousin 2025-01-02. - -import logging -import struct -from abc import ABC -from binascii import crc32 -from logging import DEBUG, debug, warning -from typing import IO, Generator, List, Optional, Tuple, Union - -from ltchiptool import SocInterface -from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType -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 - -LN882x_GUIDE = [ - "Connect UART1 of the LN882x to the USB-TTL adapter:", - [ - ("PC", "LN882x"), - ("RX", "TX1 (GPIOA2 / P2)"), - ("TX", "RX1 (GPIOA3 / P3)"), - ("", ""), - ("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.", - "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/BOOT pin (GPIOA9) to GND with a wire.", -] - - -class LN882xFlash(SocInterface, ABC): - info: List[Tuple[str, str]] = None - - def flash_get_features(self) -> FlashFeatures: - return FlashFeatures() - - def flash_get_guide(self) -> List[Union[str, list]]: - return LN882X_GUIDE - - def flash_get_docs_url(self) -> Optional[str]: - return "https://docs.libretiny.eu/link/flashing-ln882x" diff --git a/pyproject.toml b/pyproject.toml index 79963ef..bc31434 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ importlib-metadata = "*" prettytable = "^3.3.0" bk7231tools = "^2.0.0" xmodem = "^0.4.6" +ymodem = "^1.5.1" wxPython = {version = "^4.2.0", optional = true} pywin32 = {version = "*", optional = true, markers = "sys_platform == 'win32'"} py-datastruct = "^1.0.0" From 9b100a04b09c2498a035cfae4e1494fde622103b Mon Sep 17 00:00:00 2001 From: lamauny Date: Sun, 16 Mar 2025 10:37:27 +0100 Subject: [PATCH 09/12] [ln882h] reformat --- ltchiptool/soc/ln882h/flash.py | 20 +++++++------- ltchiptool/soc/ln882h/util/ln882htool.py | 33 ++++++++++++------------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/ltchiptool/soc/ln882h/flash.py b/ltchiptool/soc/ln882h/flash.py index 7476fb8..8d437d7 100644 --- a/ltchiptool/soc/ln882h/flash.py +++ b/ltchiptool/soc/ln882h/flash.py @@ -1,10 +1,7 @@ # Copyright (c) Etienne Le Cousin 2025-01-02. -import logging -import struct from abc import ABC from binascii import crc32 -from logging import DEBUG, debug, warning from typing import IO, Generator, List, Optional, Tuple, Union from ltchiptool import SocInterface @@ -83,8 +80,9 @@ def flash_connect(self, callback: ProgressCallback = ProgressCallback()) -> None self.ln882h.link() def cb(i, n, t, sent): - callback.on_update(sent-cb.total_sent); + callback.on_update(sent - cb.total_sent) cb.total_sent = sent + cb.total_sent = 0 callback.on_message(f"Loading Ram Code") @@ -105,11 +103,11 @@ def flash_get_chip_info(self) -> List[Tuple[str, str]]: assert self.ln882h flash_info = self.ln882h.command("flash_info")[-1] - flash_info = dict(s.split(':') for s in flash_info.split(',')) + flash_info = dict(s.split(":") for s in flash_info.split(",")) self.info = [ - ("Flash ID", flash_info['id']), - ("Flash Size", flash_info['flash size']), + ("Flash ID", flash_info["id"]), + ("Flash Size", flash_info["flash size"]), ("Flash UUID", self.ln882h.command("flash_uid")[-1][10:]), ("OTP MAC", self.ln882h.command("get_mac_in_flash_otp")[-2]), ] @@ -129,8 +127,8 @@ def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int # It appears that flash size is coded in the low byte of flash ID as 2^X # Ex: LN882HKI id=0xEB6015 --> 0x15 = 21 --> flash_size = 2^21 = 2MB flash_info = self.ln882h.command("flash_info")[-1] - flash_info = dict(s.split(':') for s in flash_info.split(',')) - flash_size = 1 << (int(flash_info['id'], 16) & 0xff) + flash_info = dict(s.split(":") for s in flash_info.split(",")) + flash_size = 1 << (int(flash_info["id"], 16) & 0xFF) return flash_size def flash_read_raw( @@ -163,9 +161,11 @@ def flash_write_raw( ) -> None: self.flash_connect() assert self.ln882h + def cb(i, n, t, sent): - callback.on_update(sent-cb.total_sent); + callback.on_update(sent - cb.total_sent) cb.total_sent = sent + cb.total_sent = 0 self.ln882h.flash_write( diff --git a/ltchiptool/soc/ln882h/util/ln882htool.py b/ltchiptool/soc/ln882h/util/ln882htool.py index b9f0c31..925c2cf 100644 --- a/ltchiptool/soc/ln882h/util/ln882htool.py +++ b/ltchiptool/soc/ln882h/util/ln882htool.py @@ -1,15 +1,10 @@ # Copyright (c) Etienne Le Cousin 2025-02-23. -import logging -from enum import IntEnum -from hashlib import sha256 -from io import BytesIO from tempfile import NamedTemporaryFile from os import path, stat -from logging import debug, info, warning -from math import ceil +from logging import debug, info from time import time, sleep -from typing import IO, Callable, Generator, List, Optional, Tuple +from typing import IO, Callable, Generator, Optional import click from hexdump import hexdump, restore @@ -28,7 +23,7 @@ LN882H_ROM_BAUDRATE = 115200 LN882H_FLASH_ADDRESS = 0x0000000 LN882H_RAM_ADDRESS = 0x20000000 -LN882H_BOOTRAM_FILE = 'ramcode.bin' +LN882H_BOOTRAM_FILE = "ramcode.bin" class LN882hTool(SerialToolBase): @@ -46,7 +41,7 @@ def __init__( self.ym = ModemSocket( read=lambda size, timeout=2: self.read(size) or None, write=lambda data, timeout=2: self.write(data), - packet_size=128 # it seems that ramcode doesn't support 1k packets for filename... + packet_size=128, # it seems that ramcode doesn't support 1k packets for filename... ) ######################################### @@ -71,7 +66,7 @@ def readlines(self) -> Generator[str, None, None]: yield line response = b"" response += read - if response: # add the last received "line" if any + if response: # add the last received "line" if any yield response raise TimeoutError("Timeout in readlines() - no more data received") @@ -105,7 +100,7 @@ def ping(self) -> None: resp = self.command("version")[-1] if resp == "RAMCODE": self.ramcode = True - elif len(resp) != 20 or resp[11] != '/': + elif len(resp) != 20 or resp[11] != "/": raise RuntimeError(f"Incorrect ping response: {resp!r}") def disconnect(self) -> None: @@ -132,7 +127,6 @@ def change_baudrate(self, baudrate: int) -> None: self.flush() self.set_baudrate(baudrate) - ############################################### # Flash-related commands - for internal usage # ############################################### @@ -148,7 +142,10 @@ def ram_boot( ramcode_file = path.join(path.dirname(__file__), LN882H_BOOTRAM_FILE) ramcode_size = stat(ramcode_file).st_size - self.command(f"download [rambin] [0x{LN882H_RAM_ADDRESS:X}] [{ramcode_size}]", waitresp=False) + self.command( + f"download [rambin] [0x{LN882H_RAM_ADDRESS:X}] [{ramcode_size}]", + waitresp=False, + ) self.push_timeout(3) debug(f"YMODEM: transmitting to 0x{LN882H_RAM_ADDRESS:X}") @@ -174,14 +171,16 @@ def flash_read( offset: int, length: int, verify: bool = True, - chunk_size: int = 256, # maximum supported chunk size + chunk_size: int = 256, # maximum supported chunk size ) -> Generator[bytes, None, None]: self.link() if not self.ramcode: self.ram_boot() if chunk_size > 256: - raise RuntimeError(f"Chunk size {chunk_size} exceeds the maximum allowed (256)") + raise RuntimeError( + f"Chunk size {chunk_size} exceeds the maximum allowed (256)" + ) for start in range(offset, offset + length, chunk_size): count = min(start + chunk_size, offset + length) - start @@ -214,7 +213,7 @@ def flash_write( # Convert stream to temporary file before sending with YMODEM tmp_file = NamedTemporaryFile() - with open(tmp_file.name, 'wb') as f: + with open(tmp_file.name, "wb") as f: f.write(stream.getbuffer()) self.command(f"upgrade", waitresp=False) @@ -232,6 +231,7 @@ def flash_write( self.pop_timeout() info("Flash Successful.") + @click.command( help="LN882H flashing tool", ) @@ -257,6 +257,5 @@ def cli(device: str): ln882h.disconnect() - if __name__ == "__main__": cli() From 35f85bb47ab53599949d8fb11cfa118bf24fbe54 Mon Sep 17 00:00:00 2001 From: lamauny Date: Sun, 16 Mar 2025 10:45:13 +0100 Subject: [PATCH 10/12] [ln882h] reformat --- ltchiptool/soc/ln882h/binary.py | 2 +- ltchiptool/soc/ln882h/util/ln882htool.py | 6 +++--- ltchiptool/soc/ln882h/util/makeimage.py | 1 + ltchiptool/soc/ln882h/util/models/boot_header.py | 1 + ltchiptool/soc/ln882h/util/models/image_header.py | 1 + ltchiptool/soc/ln882h/util/models/ln_tools.py | 1 + ltchiptool/soc/ln882h/util/models/part_desc_info.py | 1 + ltchiptool/soc/ln882h/util/ota_image_generator.py | 1 + 8 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ltchiptool/soc/ln882h/binary.py b/ltchiptool/soc/ln882h/binary.py index c225ee7..637b789 100644 --- a/ltchiptool/soc/ln882h/binary.py +++ b/ltchiptool/soc/ln882h/binary.py @@ -11,7 +11,7 @@ from ltchiptool.util.fileio import chext, chname from ltchiptool.util.fwbinary import FirmwareBinary -from .util import MakeImageTool, OTATOOL +from .util import OTATOOL, MakeImageTool from .util.models import PartDescInfo, part_type_str2num diff --git a/ltchiptool/soc/ln882h/util/ln882htool.py b/ltchiptool/soc/ln882h/util/ln882htool.py index 925c2cf..85127a5 100644 --- a/ltchiptool/soc/ln882h/util/ln882htool.py +++ b/ltchiptool/soc/ln882h/util/ln882htool.py @@ -1,9 +1,9 @@ # Copyright (c) Etienne Le Cousin 2025-02-23. -from tempfile import NamedTemporaryFile -from os import path, stat from logging import debug, info -from time import time, sleep +from os import path, stat +from tempfile import NamedTemporaryFile +from time import sleep, time from typing import IO, Callable, Generator, Optional import click diff --git a/ltchiptool/soc/ln882h/util/makeimage.py b/ltchiptool/soc/ln882h/util/makeimage.py index 2fd84fc..fb2bb42 100644 --- a/ltchiptool/soc/ln882h/util/makeimage.py +++ b/ltchiptool/soc/ln882h/util/makeimage.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import argparse import json diff --git a/ltchiptool/soc/ln882h/util/models/boot_header.py b/ltchiptool/soc/ln882h/util/models/boot_header.py index d192fa0..0d5f9e3 100644 --- a/ltchiptool/soc/ln882h/util/models/boot_header.py +++ b/ltchiptool/soc/ln882h/util/models/boot_header.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import zlib import struct diff --git a/ltchiptool/soc/ln882h/util/models/image_header.py b/ltchiptool/soc/ln882h/util/models/image_header.py index 9d18a0b..03d88ce 100644 --- a/ltchiptool/soc/ln882h/util/models/image_header.py +++ b/ltchiptool/soc/ln882h/util/models/image_header.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import struct import zlib diff --git a/ltchiptool/soc/ln882h/util/models/ln_tools.py b/ltchiptool/soc/ln882h/util/models/ln_tools.py index efbdf2b..10c6aa9 100644 --- a/ltchiptool/soc/ln882h/util/models/ln_tools.py +++ b/ltchiptool/soc/ln882h/util/models/ln_tools.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import os import sys diff --git a/ltchiptool/soc/ln882h/util/models/part_desc_info.py b/ltchiptool/soc/ln882h/util/models/part_desc_info.py index 8bef270..ef4959e 100644 --- a/ltchiptool/soc/ln882h/util/models/part_desc_info.py +++ b/ltchiptool/soc/ln882h/util/models/part_desc_info.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import zlib from .ln_tools import * diff --git a/ltchiptool/soc/ln882h/util/ota_image_generator.py b/ltchiptool/soc/ln882h/util/ota_image_generator.py index c50407c..e54d663 100644 --- a/ltchiptool/soc/ln882h/util/ota_image_generator.py +++ b/ltchiptool/soc/ln882h/util/ota_image_generator.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # fmt: off +# isort:skip_file import lzma import sys From c183945c7688b31d4c63272fd51643488abafc3d Mon Sep 17 00:00:00 2001 From: lamauny Date: Sun, 16 Mar 2025 14:45:50 +0100 Subject: [PATCH 11/12] [ln822h] add ramcode binary --- ltchiptool/soc/ln882h/util/ramcode.bin | Bin 0 -> 37872 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ltchiptool/soc/ln882h/util/ramcode.bin diff --git a/ltchiptool/soc/ln882h/util/ramcode.bin b/ltchiptool/soc/ln882h/util/ramcode.bin new file mode 100644 index 0000000000000000000000000000000000000000..d80ee4413a6a2a01a1235499cc648e969ce5b76d GIT binary patch literal 37872 zcmd?Sdtg+>*#|u5?Cs>f34sJy!Z{%!%O$u$6a-s0n{culfdFEQ1gLuMt z(vyjJQz8+4DVi*X%Rt8y(SV2IOmGwMBz@+&1z|sn`%k_fMf(3+xB>9_zxjUcvI!Lv9gc(%6eY%8bWYl)dlPWDzW!= zBE6N*45dfZ+oRq9Y1knTrhMOLQXYepiS)YwF@GbT{x<`hT+lMBx+%`Vg#aPEHNgv- zhFXK+eWwGd|A40c*G@qWblOfP$s8mg=5l!niWK6nKJPG`)4_F()#Gg~9hq~qhvR!L zF=?!%(%LUSC)dkHdAn@S8>?95V!2GtMc$pEyX0@k9b@(to6n`o*$j76sBp}>ZceDc z7bg;ls1rk3-6UZ_v6%B>52yd&l2a~{C(9)=<%{NjhfL~7!ASWDIT8M+Wt)}rKHoAz zfeezkf;y|tyA+tYEs07>i@DI`BL(Js>c=ZWK2Z|$$;4(kRWXDGVh9_1xB{-*ERPR) z0|^>UIyN7E-CXCq`cLa zNPg3oxQK7CkKr2Z*7X;D(>H?H`#obx<9=CeArsDa>k?OYCna%>_sAVRc77qadnRa< zQ{;v6Ju-KQs6Ppsy9H8NO!9Vjr7zyq)wy|0iQP*Mt@;^vh?`u|Wm-J)5*b_B&2tqh zaq#$l_X=?kOYv1t`gC~;;yWGGH6tf`DUZQUjh6Y3Alb``#q6V3Nmg06jq*PlDV^7Ity^Z+aKQZVbh}(UC&I_hm)5S82INax{OQ9x<<`B*)16E?=~gxPTikc*0nv$(X}(< zzrsBCT6Ypx)lKwMQcAj#Z~kU>X}4wJ1$mL&3BNb`M(8WLl5Q^P%Dk(hOFx}ET+(IK zz1Wki=MRv>koc=aHN@piL=JLBeV!lBEbuc3B~q&fSqZlhox`zKlG9hkMb+d%kpLJuc!%6^pDDb!Ql1)2`Z^(i7& zKq5wY7wUf2SkRgb=Q75w?l$O*CG)ztWUb6-I7rv@PamX9GQ2yw^kiq3ffGs$Uh~+W z6dTWSxnzP-A-UkQgjXI=IsPv5m~JDP*DWkEcPEl!$Oq}oC+hZ7;B+x93wv+uW?CMH z96aNvw#?ppbIBpc(cYUK{H4VXqRu%*)XAryg>yFc5WW5)VlTBjs=7HYGiNg}hKm*| znUckh)!lmCL5I-Z;IOqB=b7dk6{Xs)a=HT_n@D4sdBzFHwR185A0pMraU&javi_nt zn}qltBG#7ev;VqB)QKTo-{_JCdu^HK3$3OSskRJmpO;eb`|Lu=XqoyOFaADDoLvlx zk}hSlQ33QBn%P!z);3F2MZS14ckXD zsvI%X-eW%1mFX=lq3ylA(j4;iiqjj=N4*%+L(AK{AkVHOj%rPzWP~?ezN?EPqrGRC z4v6`iVg@;t=>P}9JRYN^#EE)Egs#`h+)yHSDGyF9nOTz9xZ3bhViU9k$sIr|C|iyDfJIM6%r>TwY}{bnoB{-Ma`;ZwsQ#g(^#JPRH0LN7p4zpR_U~rw2R5t`ZCY)98tpZFQ z=B$;zN($9+R-Ml-o78y$qq2fw%%|ZbndB560MJjrh1r3}dQUjakE z$33eh&1umjJu(H@j{*kO4jQ|ZfUnB|Azlt-JS`1jfc5Gx!aBS!h6&ys?EQAw1o!}Z z?_}>y41bVOE)G|tbTI1S4I1Ul0^K0C zB96^)dTg-w`fEv=%Tpw{Q;|FR?Xe}fEcg~=`bBJmkVh2U2FL?g)l7I-GH8$*M?~SD zL@}Qn5ArcSl4vvtIS;QL=jVi5M@L;O>rbk^%0{7>9bou@o*u z&25mOi1<-{X|EdYG@sNR*O&8+L=6PZzgiK<3=`5ahlnqtoYP)?)h|XFR$cxvXfO0D?vrG$${XgYoPo59N`}+mPEt6e{*8cLroC|U)rYq6 zf1x_bpd?X!!gSIqbs4BJCt6^?c)!+Ec`xlh(EZ`(RVHtrOMgqc&W+i2ODIB0;;_7A97aC_W*ijzA^n!#}!i4afNpnsqOrO z-}eymK_^CnlD*uO?daAoL@hRFD2mDCoi5Lj?XnB~^fMukyhod=($hXP*Lr8nzb33ZPi+lOHo+>( zzf;6$Ddzu0Fy^<1%^0zfsfia8VY9{jwxI4g+w?JaSLMLM<9mJ3=7)aUldj7wO~)8P z=jC#Rk`!N}B9!R8uol(yr1XYOoj>eOGHl36QI^fsUxrk8Ll0-rE4m95{t;j@`4cZB zaS&2~B*@&YodkcGkiB{b#sK(JO9)Z!D7+G@c((Zv8yAxNPe07Je%nX#3glopC+Q9l zMPD*1z}+Fv;X*pq?C|?+b(5{>-Urm1bhf&t9vh!9dDMB`p;EK}zIz9+>vr(w(g!WN zhJSS~4d|!n_LIrF3kw~k{bY%*fl@tavDHnpj`AVav{F^CpKMK`vGFW6UBZ-v^QP|I zI=;I^_g40UmOdKG!!*o&)-qin60d~-r}vY zZuPFQX84}?u-3ZSD_mM$zfXwcw>v^{+%=!B@h-Wk zJHaH$v*av{J3S$5Dd|oyn3PgQ_%KBoi?OrD@#!8e!GHEHj3y~%uX3A`C?N-nk!BLv z$4@qT!AH{TS-Qyy-b7`BBJ`M`8BBcd$2`{{8x+#|dX|1N=d;5llZ+PD zujCr^?@!5nDxeoRXoJ6+IIo!N@7yPU9jTxXm9$in;t(x=p@8huDFW&>(r=)g@RJRU z2O{Yv3tr(f9Ox8Jt9~-^b+jTxLau4@`_t=cC!4&N1Ek|g)ttP%D=FFFO;zl1E(8Uw zN@w)iCV(s3Qvrj>=kAlqb30W*cUM=EF40F@9p_tRGQ4kNVPO9zY|EKmw{)`Jd)v|E z{mC!!`;(M8M}wWqWO69#Um8n(?z!dsp~X4;{t>lWo8=qz$AvMZM@({?`1kYrJu`^H z6|9`gU;Z!XF7%rs!T5_b=o($6_A$B!MQ41EmO<7b+Cv@>8cIolQQ?j)mOEBx^=g}? zV(ztziZ1#6>vYe8POsR+!MaBdI&(+^nRNA%pzw|S!Nr(?p*?~Y17=9LpY(7^J(re! zNYph5OIrf5ZtG8ZWpuNZjzsnnl3?YCt!VnqGj6$c=B(Q)SFNsEvzBUy>$qY^N$Fgf z;@l?kp0OZaBZ&GjKfwb_jm%rwl}tGA_nQ(F(ZId`y(R-RE9ZT-iBo>sJCBTLu+>e# z7;%j?87+7&T8eBPhnBa^H%D1(&GxRdKIFRMou$XS(Uz5Bi)${qya#QR z_GDZG%>myaVvNQ$I_L5jSEHwZ)%R53PQo{)^xSD`?jaTVUS3RYm?EY$z&5icU$8s) zhC+Oq(3<$hsDEIU`$0^GP{)P!Wzgx*V(@?XBKIVp`oElOL3nWxSrrU0Lkeyn3` z$2h)WL?hKt*H%VTw@Yyp*@H8P+WuFH*Ki?V#bc6()^!G1|@4q zLv66|$PJI&^j{jR`!D>X5cO{g9uW_3;5W6Ggp=sAG1B1` z&GzA4q_N%+IM$b{Nkb_ew6 zEo`+!?FyGlRypcl79pb5ZI`U$qW-%hrM*iy5!i(hQ36cF(x|^YlH#Ozsq~B+2nGq2 z32~gWau8QvX&9udtc5jscpoPo_TfqAMWQZ;9WN?kozXh>><1qcb&O6(GLIzc<6&MD zYcmw0<_6O6-VxU0I}y-UXj`!seTG`cXrKo%Zo7aqcITP@%ej3(&!`T>Hu zh``AhbS%mj(wmz@CUR0W&3D)+lWFrWNBwVxN%iySJMGJf`c=}@qWN!*P+V^HLW}4Y z$8BFCR@<$&UbIUhr1CqAE=BiE3MhzRO@wtytk%n*$PwKH6uH%)XTFJ;rKVIV)@&aQ zc`97nB8?Lv{SlBphgoYa#v?37s~l+4z7Mi*B3{G%3{gi13^JAd)a+?=hQ5+Y+=eoF zENpPuWFzK?&8NjWyG5*}`9-}JiAd{@%IXgt&yYNfW%9=;X-?8@$fRL6hdr`Gw#ia; z)W1FKK232oz=`-?6ZXvIeInK9+Y96kmYWLydTy5N?>vEVv-u>i22n%km=A5Dj9RF_ z?Mou!XLLJQPiIIY-BGi;-eZ*w}4I zaH0M1>ZOQ&h$bDPKbH?RtZahaN;q$r}bqzs$p{$ zRR8M9ywf#ul2fOCJJ5z2B>;Le^hg^wHNjUS8}YTC;dOw15$f%PanwmV&>#Jl*lGN! z@<>*5xds}ifi!IXDnPuG$9Er#kQDt2VJ#H6$uXVv74s2sD8cO zQoou~d)Aw+@147zhf6{GP}t)v?EegftzluE2uWW z^RLcHd&C-{6!?ndv+rHejX0A%d5@tdnTz^;0nJaW%u&Rej;o$B_-E8-&z3d+*8^hB ztWvS^lx*Ge*4X#17$$hL;luOY0yi64t0m9`|E5aPUfOFW#rKf$_ItKFvd)S%G_`_r z;+_s@#960_B+8tsA27 zJ^DDT`D0cRPc6Zm9H;&|R?#mM8=buReayt^fjJGmM)mSB=%yF(EG1@&(%ve1M)^WKsG=%fCOn8pekrQV}q$dXsGD_&}MAl#g{QxPw>tmF9$4y1C&dQ z$Mc{k`Xii_3%7m%yuxjOn*;Yf;4N@x42=5H**MakDRF4u1p)AHe>nhKADT@dX-++O zehRi;k~7a{VEQf*23;a@b_w&20$gW|%e6~XQ++3r&`gk5rmJ9-9u8{xorQIL%-%sK z-;UY0=Bq-59xD>JL2`I%Z$lgp#yYMFcG56|&dwdIckqAV#Od%Wa+12*Urvi-C!d`P zi!7nqJ_aMA#$ArIzf5r!lxI|L&xYl7Xu$u|SRSJkN^|L4apSBuN;T)aKqZ!r28}3x(#a`AywW)mIE@D_=1gb$u$XtmQsOZuv6y|Glq&UnlG8LS<_BVW z7BkOcZb!e-);YY~sXVA%L?o)?g@`2U;T{XqIa`deBIs4yASHY4No}?>3AU66bG$k= z6eiZ7cjF35-xS$k`H`dNoG4Sfg3@DdSDhI!w{bFNo5Z9#+Z2=U&bm6Z;XCV=PQeON z?jDO(p?Kk9Qr&G+440DXNSMF*hdnkjR!PK2yS6N;u9*4C%vaYH+^sSXWl~ ze>WEMuR^_U`#7+Zj)~~neXNXz-Ur_eVa=|ZhPh+SuAW|(bcEK;HzOp^gz+<3fxaE~ zV$8ojR%PLi4fy{YZF2OU$Jzt6xp%{|OI*8pc77FAnXrcfC3MbjB8sxWfrwSk3)yoA}q;GmunIBiKt0#j(<1fduP-jzjf6^ zjyd?V)Fs$eVvy(mtfFh=B$Ah^n7Z}iy(n?d#+ZM6SmFU~z89?>dvU^5mU(kOJ@;|z z(v~Z&8ePnv67Fu5c#WblOt~uncf~aKd#SX=pdEHC?OilUvr$$&zm!KR;oHCy%`g3? z`7J_zVcU{s@JL;vzy_D^Z#g!a_CX0}HCb$AvQPt>6-(3V3e3Vj8>9t$JPrFpV*WzR z@6sMDKRf2X3$}o@mU8$fEzEXiU!x9awUy*+wcyDCGBM_VFl3NFZXJmkYJMU{K@)Gr z&Ka%jYr?eV@~FA67@=ol{?sT|ACC?A9~#hV?qJ#1RM9eDI(6-Rm7A-@%{33LK|P)? zI<{_EgIaLCmC^Fo+8339RtJ7MFenqP4*VDTxRX~s4;Y|pIrUpC2Z5cMB=VRf)&ob! zwlx1wH)|4@0saR369!`bF`+kFC(FsK?FlRg!`t4=16tb~&2m6xREpSA*{C-qaWg8f z)&K|X{Fwh*kWv+ais~)orDD7MI9d{|gETK5l6+EJI`c~Iui9i8+y?43RYPr!Ez^RW6XmcGTFI??zvntC{jVOD^*w#=$Ctf4spsiMTCBo_%@{AlqZ$Lj@&+#84 zwES$WJZYVNE7H-*H4K&@QR%p;?c>OH?j;U$0x|zLAy1<20k)5o;h;P#*h)Op5=<*m zoj}{n4IV2-#f<8jzMBEL*bCzE$zt7gwKZZH=0{WG~dip9>(M>n> z5%)y2diq9~wlpd+6*_ycp2pkRCfe!-^{iU<@>OmH)jj)BPBSD&Z!)Pa9q?oP4?Pba zG4iMFocBR`qc{cnZWQ)`DtAl8y~+H&rRhuTSS|Ccympb~$NZ@8c@q>-r}=?)QOy5B zK-B48(%{EXlam$ray6w1%XKehEZ^tAdQd*;*3Z-_T1;%+zeT51SzhfPbq98Hw3EVH zeKgINK9^?HABcTE#WllHoc|xCc+2S#M>I)9{anzf%$75TzJthPf&yQfhZR`KMBf#M%coGv&v+JEER^u9@yu$wE(? zdThXd0lGM`A*ms`A*DfNeXlQ+h<>#PxfPVecLU9A@6VS3J&X9D7c?bCAS^LJQm{|oMgdT%cz^hXNoa#Y;H66ND2kyQRpq7CS zmcd}}5DRUv&^XqIsAWLw?DyzQN?f;ozRs#ApBa*s5#dThBb^Na$=nbS#oui6YZar*2#7(dm*AY4c1nMo0#>y<|YCy^eh`G>$?h8JhM|q&jC9CW5dk(^mni5@I9K4 zhUWv>wghlP!~HTw-zw%HC0PJ352KeNYBtgz$Td<5N-7Efo@p!xW@IKMX(=bJagxq`*He<;pzEY4pH zi*w!mgOPrg&-;erti2)5n^~OILvi*ojo&aV&blp?_pMxAS*yiNZKkK2wO&>0$L|QD zcg3Dt>_YjO8~dz^tvWAtD`BOO0<%J4M;x~bIIh5m(GXF;i8Ppw++A7iv`{N+z`rf{ zW~+Ic{xbDn0RK-ri_n)w{c@DfT2dQ*z~3LGb(tGx8$g)xQ#|FG-#N(4g7QTL&ewn>`9PL#kD|fA|!OI)#MUZRa}eJlCF_B*_J?(|Kq! zIvOP8*WfFPxr|g=3eYTAzk-H8&t&1Qj`%-~b+ppi<-vSK{C{T{!!XPxhA|GqoMRZ% zFwE~5Mi_>9onaCP)n)53W+LiSA)OM#3SN>hK&Qs@ z_3+h0;opyk?`JZ;I27_vUlQ_q%BSKX{b8E&`z+;O4uya1OTu4IxiKF8&n)E^hC&|v zl8_BUJU@((hMo!BnO08aV>ER-<|pbSLveijOX8>-isPPPaVSw5dc{!arSZ^%^^4a1 zF_@ck)(V?5r7R~`ndK_R`XZf`aAW+U&G$t7>!N0tP)SEL6w`$E@y%$m%UP*ZFJ%}7 zn7gBMTS=krVoD)*$>5rgu{Kt-gFI$FFQeB-sS#>(kl!7eGgM|G_JJa8P82p1Dbwi* zd}OZZ4{hB31sqI7diCHyOOJ9e?lbA>kHZG=H<;dFTAVlzX%Ug8wjDc{33i5h z0{V2CUPqwcJecQMLwWw^fZ6qWpq~s+^KS#!^O%4<3P|(*0V6ClJ;z}bLDc^W@S6Vr zUwNP$WP*c^z$fO>u8sgUu*PM6J^hyl(vkirNMFOzJpXVY890>;m;1qCJW;t*Ipi@p zR1V{T%6+)qyAA8oNs_K9>VE~LquIVGiHnQpmsFI_g^{fFDW$!)8R<%2b6IU0ug?D^ zUtQTZH-pDoJB9Q^5aB8A*VV7beV(2^Nb}YZ%@es4=RmkL-T`Pw_4YXRV?#74L*Wt_ zHG$2x8+M@isOfk5pO}7=4wr*VCSq+fV3Jc>^siv&&MPF(sMzAOaZ&$V%xBo-h`%M$ zpu#Xh999 zW!;@=Gw3GCb=6V-#Gt)(a2ye>&|_{6RzVVv{U9b?Q+3B!Sg?&)5Bb`X_)2Wle_wFX zogZR@V1OYEDbtyFwzcX^aWsv?#;Eo}BMuS)= z{q2l?7NfUCvRi2X()Mw5uL)~&O>U){;N*)~gELZ?g*PM>A|Fl2$FBnpSS}Qr7Mz55 zD3lePjI~CZZgRxT;<*4D$XeRurfbY%4feAo{H1V{>N<^hC~DD=<3Uuz`B z!JjBDgL3fuu(<_iX2?PGnj*Btvz;eipnExJIi<)GYl`IeuJC-+|HnX4ocmLO>GFp) zR8PGe_EM=8)3_I4jl@%|(Vk|ome(Ug)ZYsa%1qr(xe$6n3Ko6(ChNA<=?=L`%}Q;!S2OGp(in^4Qmz+fsY8?Tif<=><7l& zQ%@k>?($R zgkg6Ao0hi}*yRkngJJ7{HRsI-_UjB=&#)VSP0w=xyNF?*X4qB0X5`HTb{@kXVA!t% zJ0fo?urk9QV%T}WX68x27Bg%U!xjTOGH(>Ha~Sr=40|iEqwOYO6_dMaUD>;{=h_#dX`$C&qoT4&5*z4%;oSw~7qr z?w+NL1JoR7C7g7`USBc>``(Y>3o&rn<$QOs*(yc+M+fxXrQ8Tm=GtKE8xjjL6`DpG zO~bq}P(W(7PTtyGhy8t+S>Vk-sk^ssQe8q*#J?l9&60D|$LFxm8KK{?ad*?*Nl*NP z5PJUNI6Qv!VFrtdJN!%wU`cAAJ&VDTt*Q(XJPsyZ3{oKk0H z@R7QPaF1F5OrCQh-d5}q=20g4~1*nFj5nT zm;}#q$BmKJ0hbqU0ZcJDKPKo0z(hA_r zVQ~-UO3uZ z`u(GjyH0H3tR^py^9k5F%CN765*5wA1?NV1hC300&4-klzJUJ8A^P1z^q4zl^vV~| zKQToA*bx2pIK8-akk{puSLA+>*CiqMke?fVRDy$kOG8aVev{xwB{}FPg_I#b5q?yn zgMQf|X%CfyEo7CY8mx4{{jfUb9~HvBi+{ULYHfBDYZaOCR*?~J73O%WNR79O{8_5dMaY)SnLGa&iqm&;GYuF=0wB(G??%El$fKQKJXBY7X z#HChfe+z7Vobj*+m>rh2AgjVAYbOf1zGe?-?X$mLZ*qZ|-sQwFkNazmQ z-WXB;77!qR&922sC4=5u(eK+h1@hP%wBgI%`@?iAzUqUoT_qK_p8GKAZC%<}(^XRT zR-b7Woxdf}Y()K9V1wo5R-CU{GI#FPbL!ZRzCX_F=u0J>fp|FyH#gkM=6V^eJx)tX z+ZH#Yay&35Kf9X*@1gRl5r;0 zrc1;rRh$IT5vhfnWbpEn1e_(J{(5CGMquaTa=R<6XL9=*e>q0Msg7UsuIxECkH zu;Q(rh(3=wy^cN)v0zs#*0#|9*GJnvHrt8X@C$PC(Pjfe2!w{izNrcPOQU5y!Zk#> z2|BlOXoCFTcc2$q{5VIM4;?Wa&o|dSoVIAWn9h~q11NKo_PSSXB9&`eSSZL|4 zLoArUHC^>t+SHS;H|JnZHd!{wtyewU^nbDU{xRK*ay=a2ZW@hK1+->}R2o<~qiH}I z_Q_ah<&K5@V+IK3s3wi0v7V2{u%cCr5wAo1sPo^IJH6Y5-cdsDlR=#;8;_kO$?^`l z1^X`a=PLTc8^?gB#eqCEMw%R{Uc*?!RnNCxagejO;1u3utn@5I3vGaV`|iO`8CDawU)|qzN6DEuqi*mXqU`FXJr4=m`WL@rU4D;TAp?kMM-@RK;&(f!Pzbt$)qo#YO>8$g=nveP_i{BD&M|xC2%ExtoL)_nt69AX7jvDiK z#Y(&A9!c}b$C~x>os6D<2iiJ#JUTpjvjIGqvF=OMk0NwMRg}a2pNEa$gpgU#y(psx zXc>L8C<$dWCRWPg`E2>%gm6%6@hh;yWkGaS+rKMcs#j4z*JNQ8KrbJ2&@>+^qGJsu z+B91Gez?eh@AJ_)SmTxGp4eil{cHdG-%l&DG6ktvOGgdm@3Ersi2J4J8-1n%gBnXj zYf+sE$LRj6hqII~s#B1jhf41rtSS+8E_M%UZOM4m^I)6f@8{@w*L3gy7IF~_X^mj_ z$q)YhJjBW{YC6Cj;19(7??k(;I`804y_-=+(3n^+C+f42cZPjE9eHWk*X~FtAN(z( zw^pEDctrT|B&9$%5w)Wh`EwhX?%W)4!iNy5H*uOCG&=cSIZk8eq=n;%`Ucjc4dm_u zqHZGo#qcEn)7ohCiD!WFjs}qsJ)}61NZ6R zyv;PdkL1#ms+b24j&|;Lr21aP?iq4PkDWC?-8d4p{pJB~8t2O|8KwNFBoi|RyB+C1 zVXE%pt5?Z3jqcTrwAGIX9q*glwJV=f^6?%_X$?}MW8pK%2b*cdI`IFk9@8~yW?m3_ z_Ji)rO~KmVbI!*cwLaLIj7xj3F1-<{D-nC)T5swlp|BWdAi2VEUcPXmS6{fvH%&1Y zI=o_G&{wX|{ap9rmyVizfkhT*lCDSqxStQIrH z&Q@kAqvAfEq1fx;Q#cH(#ZIxg3Qmu)JW^^cc8VV!hZZ}<(w)#$9vwq5p8=MRk{(dz zqBQRF30OstMgu)cBxo{Lp;A_4d#Zx$)Nq#ny{Cr>Wu(&n; zXxxrrSdBZ1ojDAvaYwOJ<5;qOsI*p-c4-(kURuBo=8w|GOAFY+{84PYw1Cw{fwk=H z&wo;1u`e90?QqSDt)e`p1@{d21l$`{U!uiJecXL%`om}ywD+a7m#QgkE@<^$oYBVk zT`18Z1g8v!xr7;`H2ozv4P!frwZL{m^~=~Nih2Qeiaeu*n~D+fRKcs8nv8Soi6u{z zBq)!UB$XKPegd&20;Z!M`p;0ksxQr8Kx{PpYQS>=+rSNv7#nJE4qPS^^tgGc-T^() zZob#ks*~v$-3vcJ@S$reumZ#g=ECVYgNb@-fkpi{M+f&a&{1I2KLy`$iLhLLp+s2j z4zt`5LfZ8%7i9E~#FsCEl<52(PTbhRH|8nbeT37MH_rZbpwX=PH4gJjEz*@AhTq}1 z-{E0?Zy?;4`Q0gfk)Pi6_5<#|KNj)jzIDU-(D+OE9Df=%kv4#e|v0C}NjUwE}2E6u#x6QZZ`lCdkCevJ+b&V>PWd{v)`;GYI@?osT@VDOQ^+wW{yA7c+CMxv+g zrFWi$v4;}-+oF2WNcYwiZh=FMf5|DHB;-8^7eO(6Nvf=-rAY;8|8I)fP-%*dtVqxoiDJP8gZZ0z_EKf`s_F% z7xF%s(|?wzAK<>Du^4V{Ar5Q8J|D> zt3>Huu@;Au{?1CUyh!yX4rFuxWHT_@v;w?2B)@)*(vDVHrF@H(t zakJSxr5SwCdCFAqq0Lin#5``qKQfpMjYMZ6H(`z_;!g?YvH3h+J&gG?ZmKq$#jDST zplAOhUfBBE#F0!YnQ^IP#HC^$lFA>YRHXigz>QMzGM;|J&VFJBzJ|^P;bbuM8?>Ny zzUM#SNt@qY%j8oPm(QxWeB49w86kZW96gGAtgf$4jFj#H?%@DVs`q%d5!~)NO1c}E zO#z(di(|5+g}~GV#D`;Sl_Fc3hwsV&ZTrrk(`k2DOKmOLR>5ntCVHJ#voA@Zexrh# zJ2q%y-(pb5-25Qx3qCXRHGJ-sS1$xHpNTo*!O}V#oHmrsD5NtH={SA2DB{D^(%A#u zY~lbp_Cb>mu5%P~>G-;J7)=`PD!Gwn2cyXwMw317n=04 zxlJ76_6gMgu)idBGG4cy#Jp|TKPN_eY?ON7KWmfP+$X)C0n^af|C;3#Q6n)!u+rph zzDiam`%DVY zf)Zw<37j^HwGWOBJcY?Vs$uA;XnH6U8YTnl1@vo5C`GXsCJp)K4Cx>0n+5%&&4JUr z6SV-})CTg_T(@#yf56Jo<~vg4{4dBAhZ8n?W3+v7I4?rm7pESKeOddmZk5ndEE^@{ zL@gM0pZs{!3!0+e{Yh zyyrxW8-{)AdVOu@V3XIDVOFx;345`&Y`9!EVJ(2PZ)X&w zo$g5b4fY{vHWfXSL~=d(CfSsiE>Dw3-!vPyV3^c7DQW2=EF-f8s^t%S>+qUdrbDW=>4B`r5!!3kb`^Y|wC79`7OFv-%0&tOs|$oGa&X%dI9>GHhZ zEria{Ep*{7wC#es;+k4(E=d^oZh(03rE&@7uDM-CQEbZkUEI)x6mbY8U zen&te+BqWFMfPkwvn0-L!{asS#S;xgBzgFTP9^zrZ^Ob}4&&L1{)aZ|ah`~%NdZA2 z`E>r_p<38$Sey6}Zh@hr5?ZI|KdyVbel?dkZ&kH7yTM`j3+@%1S5>{s5k7}?z;?X` zpGWnbUUM5fb%37arlt#D1X{u@@ir zLWJo1KrrM_jB@HbkyWqJd(hW#fZLQ}~9muNcPe~KFLJ_qdIBibFX zhapqCS940?|NGw*Y@>Tb)c(pU=9SJ}u+mXcym-mV`PfGo$5ZDK(3ww0{CDDP^Wd3i z0osV}(|aGg-$@~NnXbWUx0Uz$a90aEH%jN3aQ|0+6Heo#u$zD){x;CxR64EnShqp= zG474i?kd3TM1^a;MNb#hqUsYcv)1O+-@Rbo1E;A7QX(Pdxi?BblxE`FA?>o-U29hZO+C;={g7>-&v6t{0E4J|IuP+TeH+nepZoRsLqz6wbyRkH$N( z`en2n+(9B{^n12l^Ae0OYN|LT?$OD`+t( zH=Fy(L7a0thIVnCW@97;;cO@?gFIDU>@;AOG~|yCEOe4O>uFA0fW0DgZDi3vHEyjs zh8+aNYWJ19%25;d3=qlXjQP_;;=>)MolWmu(Muuh{2?N}Jyp-OU_VNe8!d7ZtmH7? z^HtEki&f9t;rfI3!W!eO>wI$4k(!Wy_drE|ZRLDt(BB@*#*GSe=TFdo8nCuoDd_)M zY^uv;nTmAh56okhrOW4VS@As=d=${Qg9otR_T$r^i2OaO$lWss^?e!RhJJk_l6y3c zC`7rUJdH-Uo!}`8?#n?@p)qh)i;qm~N0^z2VfKI(>82u`^nnzmN*wUtAF8^wA8|B0 z=xW)HTAUHaz6qk>#u=(<*jZV+SE&QflcoG_tOweuJ(iNVY|EO>TMDaIty{5fRpEvQ zs@8~G>Q=74f6G_$tESf6zh%uPoUH-8fgNANnYPc<*RJ2Pa#P((IDxKd?9Lch(cj^wZppQJ zVs=-RpMhUhdFi!x;AV`oUrT0gaj&3#_m-K>?iJLZ-=*dDYm@C-=-+~BI`dspj#V!AU7%@oGr!*H2uZ_LAeNmBSJ5BQ!m)TLZ;L_W z#4g=>xb|_B+CHAXGsZcu#X##QH;7tnUu7|99DKF>NnpPc=Lhj1=ag!}wNDw<{A-sN z((ifK-m#nAGZ<%DtQ4y~Hss=2#F8tC>9m?nY_vf#T`YdaZsvz{_^js51y5h`R6FU|VYc@?4;X_Dv>jc+C9-+IJ)i z^Oe9$pdQ8GnE|UOi$T)^*&ZU=+`8{0E^&fPbv9aqxea_0^&ECtmLev>JPCUV61wex z_2yy^sLb0vZp&|5Gp0U&j@^i{w-ou;ROz=DsB$G?+BH|GMBx#Y@y)(hHEQI>>I9f)Qfjvc?#}C za31r&AN!gMg=N=puqt&+<1kL@!^K`K zX*b*#;8OgdFl^67MnKPo^`}?8Mkam`AlvcmBYF9B?Q#^8mg}NBVzA3cq;eTaNd#aveTsQKjCbhp8p-JW7i^=kY; zvY0E0k&wSLWOh+b{ua8gB}ExtZWlr&*9JCHzxP8_{w9~RmuLAV&kAn3-;354TF(aV z;0WcBJBeGLagQ4C4fMtW+{+N4dk11z|K*tU-U;)-Q>)_c04Jul(VNvs{xJQIutsGZ z>T_%o=dH24+iJHMt=BH*w`e^?%>R>c?oe-WGvdGvZY)*XP^z?_SUxOW0kx!orAvKA zvvjpEqlUuJ-or60438T9APd8zzmCNyuiB0Hv=7sRdJ3osa%S{#@t#c&%B~pQhn4+m zY_iZU_MXDj0YCFRca-T^l; z_h#=lxCyy8c?*@|Yq2dyyY1a%7H|CB3)bW7O zZpa-8$PGxJYsQ_qzry^7(9Y*3dVh7bqe5usat(mofJiR!rsuX?-3C~Y?)Da3YPnhq zST9w0(=U-p^Sl_}(GXG@AUhylasZ-Xb<$V7bCi~=b$|`hOkf`YWR#`@`Ys@oROp?e z;FRi?BjEf9I8TxCa9cCimCyzvjncC{XAw)^{C_NnAJ`$?$X2Ef#2_V2fv+DFj4esG@>yK^A5!O?53#i~43-KUZQyl0W5<59%m zgvA(v`)VXd)Juam+6Qo>y?`6-E4ni7yB5FGo)73cNP7-i(Ut&*5&owLH6n`kmv|28 zBsL`Ao-gc`$nQR)d;8rhMox3}9GQsK5S%t`L@nd2OMF64K>wq0N*ysCslyC`y^_8~ ziM|tISsN#5*sTmp-$~V5fu)fB3Zp$07 zzvpzC{D0x^S?Bj$XDYd#JR+<*;1sI39w%{DJ>zt)<$CTT^=tPzUk}t1LGB1VY2NE> z0IeVktM)qgVkC}Jf$pcB^?1$z7AnbjXXR3{zLJQbTIF0-zv|2?VYPF0{pvHTg(_!N zebt#NVNLy-Gi!vk&b9SxEBc$snY9SH&bdyI>(_n9c?Qqx0S}??UE~p#_9M=3BP~19 z62wh7#rGuRrlF}LaG*J{@%2C>Nx=Is5%6vz8=bYF-sp7VsmF5$k5GwkJoR|a;1O2g z8&5r+GkAp6_{LL@=geyS5|5LpzlnOJ`!PpL)c=lF;H=Yn;7$WF7u5iIHCpY$=@zv) zy2dHkbO-SZSkcwk$;t!zag<1Irx_uS09t{4yhJ@5UG7>2`o`!tT=&2|5MAm@822?m zd!u@33H`+m?2laHS`5r%(Yss=;qHhob}fMW-RKT!9^+(dw8G^A=3CMEIMrSTs5W}1 zYcAaTqMTF?w>s)_6~nEHCQEl<=P*%MMkl&%cTNTLjVLe8c18f*9eq)~nq$9HCqv+pu+ z_NX%%dHUVJfGgr`0Q3g5SP4Yv;=<0s)NaO**H*;xyk zodfo3Q_?Ruw*dYQV)1PJv-3&7-yZn0>pcc-8hGDzmO&c^-gEttLDdK~IsF|5uNwG+ z>vs%VF_0|1g?-}m9+Tg>-f%Vmx@+KZsl)kO@U;MGb~q0J=K|Empi)4;WY8UePBCaU zpw}35%Ya_`8G~$qS{O8CAWwRQK@$gFluj^60(6`~mH}t)i`FqMgo10RJIEr0vco zKyL%ug&5xe<@}91Se)$<`?Uk<+tA9<{v*ZLtd)<@9qa1PK)aQ-+gCvC+4vx9@y8gv znZZW^Z&_5!;KLCsv`HSqeLu3nwVt)i{gL&q8rCwOirnj3%lN8~RJ*F2GeEZ!@h@X- z?Yn^PVbE5@_)sOZ2J*TY*t=NV_eZE~?{?OrEUOv3n8B+UT*2Vw44%i}uSJrjQdS!l zMao=d&MgQx4|7@^ZgRj%Q9B)N1pTo{rXu6?*<1Xg>eBLzz)TYKUyamRcDEK|XTwZl zJ$(o6oOp}atI27iT%j{H1$6up^#2thZ;OYV84r1$g}gZ)5;t1j5E8wA!dRsczxyswXdCSv1e42urHzv1Pi7utLFfX)O->+~pr`*F&`+88$-zAOTP~uQ zIq~kh$sZpWo6G4;X}^We+R}La_o6h8s^T0arJZIRjZ=O)#L?jIV*TU`IC920DvjsN zJ`~UYT-3^AW`yQ2RXI%KGao_jr$r9p*Rdcm{5n?9Ul0$WOZ(@=h0+1k;#J{iT>DvF zS%LADy=X71!(R`Zq$gP$SsY#{Jr24B;XN*#7Db(Mh3j39GOE&WnuN2W(D`?O$^)u7 zVUO#(jOy0#cU|V9hZ&tMoP}0E>8610LC_V1A9QVDbh+UzF0lx`D{8YPJYIT$(Pe?| zKG2N_-{-oQ(WQp(b=iv6F*+e!Agy6^I?$~GT`aW97PRjT=Q6bXG8N`?jjeXdna^< zg#HwCZ-K59biWCex*Uw|ze5gJZP6W!?o_Bu`U<1_8R%#)`P0yB*DOZ&QfL-_f9n=T z_hP65_u10;eh@NINPRBEq3=Zd4^4O3;64+w!2*~D=;=@)dh;oO_JpRml8Yt*+7+@w z#uEW;3r%!o7UcnYC^W&9T_geeR!G7xdyNNF8_I!he$~WSjx$>!A$Sj3P6j#i2CUtRmd$s?HCky099fy5J@=jZ_+9w=wP| zWXB8I@1Pw<8+gk6&0m{|4pT#zUqX8^B*QX9A778YmiFrwx_=I%Kkol&>}kNFx~_cl z0~vnQ2ZIW4P_G7)8G>{;0wKoefJpGCT}ZUmOS;1V149A~xgelxD@Kz*qOGl_OH52i zVj7#ZS#5Mz+qk7B+f7=s#h*>{qq}UIiix(Xi7tbZu;<)2GYmiJw}WqH-nr-8bM86k zo;%)q-?_UiK4n)&2hAiQPoJ{)qz7kam~lJpJ9ec~hL|V?XW#pkOD=OPW{;f*#!H1p z<=Lci#B=RwM?7|4>S@AbuGD1vrynUe`$==a>QStxD1KFn59jktDX!R6am$u{il>0B zO2Jts!(7*#p~1B`%yVa8pD4x8nfUp2+?y3oYeveADAS7ZYYD0g*Bo#y!9;5g!LXyyoO>r$?@yOIwpUES9e+JEp|OTnCCwj!njr!LzUvLE5P z$E+2pD-%2NG1D{pcnA8#3QN1CNa?WK({KlyrQLw10V+PqOk&4#;{t=RBl>6bNG#^% zi^q{CjmdX4zI$jefZ6e8XMBF98#4w3@QF(OGaa9W+&7><7kvP8?3_e@E5@~eYT9xr1NnZi616P{I?L_fcR6x z_#sHF#J9-2Di&O6q6{m;N;G~$tKph1z1!)3`3j9)FJj+*2`}0kpv=cL*fv8-T(C}a z>6Qh`2V3UrV|pNfJ2KFA*KkFgYH`(^u(L! z)uWiN@aC5|zSB(D>@R6Q3O!qfb5JKtN0q&}j|iVvbETT>!ym<;*n-lUuf|6yw_yZ$ zn6d{p;W-nHv(1MZPY{GM4L^tw%GhyjU?0vi@532lOW_DUUF*MMR6ZT>Uui6$9Z7`e z@pTj!`v~_x2g-d>g;WD&T3!*X;miO*d^l6)3POH^dC={6pEpFgG)&6L0hC7Z_-+mw zFxxE6g5eyWoJGJ)yvWg@93JT2l!qr_pe!nJBPm{ylJyI;02y;IMhUK|5#=d2!J2n)O|C4}% z=X%j>W3s)G_TuAtL`K|?X0|_g>ZSgCyRXlKSu)MF>9~W`f|ngvE(51Ju)X!EN`HN-y^Y}xQ+v^k!Fz5L zVjfIOsb&4m!7ql34=1B!X9D!z7ogd}_Sw%2MC08%0h0OG0c>a7J^kIA@GQym_&m4W za;krm^EhD~llThThPK&Sh_x!p`r_qrr(fuh-^=jSA;qGxt>*r<{SWn*_jh-&t(5y- zSwFYafcUCnz!~!|2lrqekYo4G?d&Ls=_5IINNGA{?OMO7pWz9Wtb96ZL;q_@P0F-< zK$xu(6X&W>ZrUMrS716q>jH$g1Z8%mb_;@3A|#~yJ5culse7~i+9@4}q$ri`{YGFO z&I0OQHX~p^tlJ3l4k8DVfVeJ}T+srv*Fn5`@w$vx{5`$B8F;P6s}8Rncpb#67q82B zr7Y=sI%7$1ui^^5^63@28U2hhj(xH4D+u2uU+N?;9ic*EYBj#2a#mbjbS8r>B{A|I@E`gv1AvE+9H)%wV3XD&cKvlAP+@!dvfe_PVLSiTH~` z|472GrfZ6%3y~o0XuH0OpM*Q#j3 zC$$gRv_68bbeRcyARS3Hki90#AuAa#2h}vjmuea&bEcYxvEoYsh;DNI601fL$zPWTjr~6 zWWrnF_cu#`UN0F_FW2=d+E|2rN!4!b2Dk--jGluo$ebvKltCT?kSX7>qo5O0iJ}w+ z8_E3D)!4VN@^JGc3+kEZ&RJNO#4nPWjJ?9s>beaB*Rr^J~rjvjZ`_t7{rmriUmwPo{XtfaejnkVo21tP=}{tY!gJ?u=@#Wpn72nc`u{3NM8fhP5sJ~`! zgf~-gqyo1rz_v=PFi`|16=3;t+3QJs8sjA5WP<5Eng)PK*&?B+|E6X?#<%KBQH|{V zjaa*)+>iZ(w8<(w8>$2fi(OyTHq?Y8ab$D~<78#!0M0{GopN*{-*8uXTWTxiPlzBq z^49_3QsW~=vLG!s6th9$4-a`CF~TaNfm2ar%VjXYdgwhmqYe1owxoqf<_07)h{dWlZ>y?HDc5W%|l8icvH`B2S%$9Kqc*dM|Au0o14EZw18FhxHH* z`782~gLgO>2JgdzdAK|n{KDm-d&*lz`6t&!<5?He^zQ^qN>G&TF}Oc#w+>sJX;j|8 zaznxl%yPz_T*yq;&fAh$-=j9xrGi1bu3CFLS2ObtX8o`ENi6TNGG<}clm$twr^Ck1 zF)&(BrP)}wOS3bFs@xm%W7(>9ooQ{V_I8P* z+&XK%jcqUIB`)o0Rdbc_er7%KHEhcxW$XcF9dahIU0cgoM~U_w)%x)^8{4lsoVKU4 zcBMEUJm%6CmY^KlRa;heT?$*3x3k0=b*GK3YuDDPVAeWSEw6_kaN4FrU5ZodQXP%9 zqYEBLDbeawhfC|k7Bg;lS!dL}N| zSFBa2z_*>6n_1%@FJn8L+8kBuR(Z2iOK@r#PJYCx{i)w}?y;oxPVHq?t8(%rr}adp zjrF&4Y|h+;HrCe83z?R~5>iSQ;ew3zDD9$$#~q8oG6-m$zyTn$<9)}aQLAls29^Ly`FPw!Aa6l)_ z=1&|HPl_FU=MnfGxZwLdeKY(E`~WV%4{u}azK^U=ybzp`P9%T{H;ue&{g{Daov1FJuK4mqZ zTE(X&3l$O|nJ3zX)v+dDOoth;6K3*XWkVLE!0kd4Hb{l4Ry%X-iM}a7wK>Zj6ep8v_NE5e67srv^(B{ zivTbc7Qzjl-6(P(7mDCch~jy}{4NVDLP785+Bt^>?g0y5av7HLf((bnu^%rp+y~3R z#0&FaIiI%K(arDe;>8y9f$6yO^+~?!eUvW=zK$|~162Mu#c1*UywvB|3>EzTH((8H zBKPFqY=&=vov&kjJp&HiPzG)Mfn)G($Q6Gp+`PO8JbcP#_@$^6RovSss(DR0_#hi< zQM?D?B!9>Pb!hc^c#St$zz@gJAI<#XauC?OCiLZI=z$Gz5L#e_w^|(A^I;=Owh6BB zzgs6Zqp!_DwW_cM;;}a{zBL)Uk%ezdhV7`CN1^z9wA{o99#e7!)yTvcDTNq$B%!fTN$e-%Qe(?hKixJ+HirvD>_jd7Tdf{1g^?mRM z@tk2eD|f?cRu|1FZ**ZT)i3fa&DBWrTsXQ-I25Om3Heb&h!IU zQVj5ud0Wz_4nrFJ1Kf&DN)SJV*I+9D$BX=s$`7mjXDWYP@a z5q^&S`xo#{cmtBsPaHa|8I5Y}7pBimpO`M0ViJoIpGy27@mg~1pH0_H*U^3ARww2s ZdJ?~zxI1yLYDrdqKGU4?t)Kl-`9GqWajO6T literal 0 HcmV?d00001 From f25d661322cf22d655ae64ac928f040be078fc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 25 Mar 2025 16:36:05 +0100 Subject: [PATCH 12/12] Reformat Python files --- ltchiptool/soc/amb/system.py | 2 +- ltchiptool/soc/ambz/binary.py | 4 ++-- ltchiptool/soc/ambz/flash.py | 4 ++-- ltchiptool/soc/ambz/util/ambzcode.py | 2 +- ltchiptool/soc/ambz/util/ambztool.py | 2 +- ltchiptool/soc/ambz/util/rtltool.py | 4 ++-- ltchiptool/soc/ambz2/binary.py | 12 ++++++------ ltchiptool/soc/ambz2/util/models/images.py | 2 +- ltchiptool/soc/ambz2/util/models/utils.py | 6 +++--- ltchiptool/soc/bk72xx/binary.py | 8 ++++---- ltchiptool/soc/bk72xx/util/binary.py | 4 ++-- ltchiptool/soc/ln882h/flash.py | 4 ---- ltchiptool/soc/ln882h/util/ln882htool.py | 5 ----- ltchiptool/soc/ln882h/util/models/boot_header.py | 1 - ltchiptool/util/detection.py | 12 ++++++------ uf2tool/models/block.py | 4 ++-- 16 files changed, 33 insertions(+), 43 deletions(-) diff --git a/ltchiptool/soc/amb/system.py b/ltchiptool/soc/amb/system.py index db17167..a5ad5ae 100644 --- a/ltchiptool/soc/amb/system.py +++ b/ltchiptool/soc/amb/system.py @@ -6,7 +6,7 @@ from datastruct import DataStruct from datastruct.fields import adapter, alignto, bitfield, field -FF_16 = b"\xFF" * 16 +FF_16 = b"\xff" * 16 class FlashSpeed(IntEnum): diff --git a/ltchiptool/soc/ambz/binary.py b/ltchiptool/soc/ambz/binary.py index 7a5d214..aa8e0d5 100644 --- a/ltchiptool/soc/ambz/binary.py +++ b/ltchiptool/soc/ambz/binary.py @@ -24,14 +24,14 @@ def check_xip_binary( ) -> Optional[Tuple[int, int, bytes]]: if data[0:8] != header: return None - if data[16:32] != b"\xFF" * 16: + if data[16:32] != b"\xff" * 16: return None length, start = unpack(" Optional[Tuple[int, int, bytes]]: - return check_xip_binary(data, header=b"\x99\x99\x96\x96\x3F\xCC\x66\xFC") + return check_xip_binary(data, header=b"\x99\x99\x96\x96\x3f\xcc\x66\xfc") class AmebaZBinary(SocInterface, ABC): diff --git a/ltchiptool/soc/ambz/flash.py b/ltchiptool/soc/ambz/flash.py index b2c64e8..cff27f5 100644 --- a/ltchiptool/soc/ambz/flash.py +++ b/ltchiptool/soc/ambz/flash.py @@ -105,7 +105,7 @@ def flash_sw_reset(self) -> None: port.baudrate = 115200 sleep(0.1) # try software reset by writing the family ID, preceded by 55AA - magic_word = b"\x55\xAA" + self.family.id.to_bytes(length=4, byteorder="big") + magic_word = b"\x55\xaa" + self.family.id.to_bytes(length=4, byteorder="big") port.write(magic_word) sleep(0.5) port.baudrate = prev_baudrate @@ -178,7 +178,7 @@ def flash_get_chip_info(self) -> List[Tuple[str, str]]: 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" + 4096, b"\xff" ) system = SystemData.unpack(system_data) diff --git a/ltchiptool/soc/ambz/util/ambzcode.py b/ltchiptool/soc/ambz/util/ambzcode.py index 72d2220..c0fdc4a 100644 --- a/ltchiptool/soc/ambz/util/ambzcode.py +++ b/ltchiptool/soc/ambz/util/ambzcode.py @@ -204,7 +204,7 @@ def read_efuse_otp(offset: int = 0) -> bytes: return ( b"\x02\x48\x01\x4b" b"\x98\x47\x03\xe0" - b"\x21\x3C\x00\x00" # EFUSE_OTP_Read32B() + b"\x21\x3c\x00\x00" # EFUSE_OTP_Read32B() ) + inttole32(AMBZ_DATA_ADDRESS + offset) @staticmethod diff --git a/ltchiptool/soc/ambz/util/ambztool.py b/ltchiptool/soc/ambz/util/ambztool.py index dc88e59..d3d7575 100644 --- a/ltchiptool/soc/ambz/util/ambztool.py +++ b/ltchiptool/soc/ambz/util/ambztool.py @@ -108,7 +108,7 @@ def read(self, io: IO[bytes], n: int) -> bytes: # increment saved address self.address += n # add padding to force sending N+4 packet size - data = data.ljust(n + 4, b"\xFF") + data = data.ljust(n + 4, b"\xff") return data diff --git a/ltchiptool/soc/ambz/util/rtltool.py b/ltchiptool/soc/ambz/util/rtltool.py index 0b3077e..8214c1b 100644 --- a/ltchiptool/soc/ambz/util/rtltool.py +++ b/ltchiptool/soc/ambz/util/rtltool.py @@ -29,7 +29,7 @@ CMD_XMD = b"\x07" # Go xmodem mode (write RAM/Flash mode) CMD_EFS = b"\x17" # Erase Flash Sectors CMD_RBF = b"\x19" # Read Block Flash -CMD_ABRT = b"\x1B" # End xmodem mode (write RAM/Flash mode) +CMD_ABRT = b"\x1b" # End xmodem mode (write RAM/Flash mode) CMD_GFS = b"\x21" # FLASH Get Status CMD_SFS = b"\x26" # FLASH Set Status @@ -283,7 +283,7 @@ def send_xmodem(self, stream, offset, size, retry=3): if not data: # end of stream print("send: at EOF") return False - data = data.ljust(packet_size, b"\xFF") + data = data.ljust(packet_size, b"\xff") pkt = ( struct.pack(" List[FirmwareBinary]: nmap_ota1 = self.board.toolchain.nm(input) # build the partition table - ptable = PartitionTable(user_data=b"\xFF" * 256) + ptable = PartitionTable(user_data=b"\xff" * 256) for region, type in config.ptable.items(): offset, length, _ = self.board.region(region) hash_key = config.keys.hash_keys[region] @@ -205,11 +205,11 @@ def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: with output.write() as f: f.write(data) with out_ptab.write() as f: - ptab = data[ptab_offset:ptab_end].rstrip(b"\xFF") + ptab = data[ptab_offset:ptab_end].rstrip(b"\xff") ptab = pad_data(ptab, 0x20, 0xFF) f.write(ptab) with out_boot.write() as f: - boot = data[boot_offset:boot_end].rstrip(b"\xFF") + boot = data[boot_offset:boot_end].rstrip(b"\xff") boot = pad_data(boot, 0x20, 0xFF) f.write(boot) with out_ota1.write() as f: @@ -230,15 +230,15 @@ def detect_file_type( return Detection.make("Realtek AmebaZ2 Flash Image", offset=0) if ( - data[0x40:0x44] != b"\xFF\xFF\xFF\xFF" + data[0x40:0x44] != b"\xff\xff\xff\xff" and data[0x48] == ImageType.BOOT.value ): return Detection.make("Realtek AmebaZ2 Bootloader", offset=0x4000) if ( - data[0xE0:0xE8].strip(b"\xFF") + data[0xE0:0xE8].strip(b"\xff") and data[0xE8] == ImageType.FWHS_S.value - and data[0x1A0:0x1A8].strip(b"\xFF") + and data[0x1A0:0x1A8].strip(b"\xff") and data[0x1A8] == SectionType.SRAM.value ): return Detection.make("Realtek AmebaZ2 Firmware", offset=None) diff --git a/ltchiptool/soc/ambz2/util/models/images.py b/ltchiptool/soc/ambz2/util/models/images.py index da6804a..7b55797 100644 --- a/ltchiptool/soc/ambz2/util/models/images.py +++ b/ltchiptool/soc/ambz2/util/models/images.py @@ -27,7 +27,7 @@ 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" +FLASH_CALIBRATION = b"\x99\x99\x96\x96\x3f\xcc\x66\xfc\xc0\x33\xcc\x03\xe5\xdc\x31\x62" @dataclass diff --git a/ltchiptool/soc/ambz2/util/models/utils.py b/ltchiptool/soc/ambz2/util/models/utils.py index c577a14..08d97eb 100644 --- a/ltchiptool/soc/ambz2/util/models/utils.py +++ b/ltchiptool/soc/ambz2/util/models/utils.py @@ -4,9 +4,9 @@ from datastruct import Adapter, Context -FF_48 = b"\xFF" * 48 -FF_32 = b"\xFF" * 32 -FF_16 = b"\xFF" * 16 +FF_48 = b"\xff" * 48 +FF_32 = b"\xff" * 32 +FF_16 = b"\xff" * 16 T = TypeVar("T") diff --git a/ltchiptool/soc/bk72xx/binary.py b/ltchiptool/soc/bk72xx/binary.py index 9bffc84..2e0e42f 100644 --- a/ltchiptool/soc/bk72xx/binary.py +++ b/ltchiptool/soc/bk72xx/binary.py @@ -29,7 +29,7 @@ def to_address(offs: int) -> int: def check_app_code_crc(data: bytes) -> Union[bool, None]: # b #0x40 # ldr pc, [pc, #0x14] - if data[0:8] == b"\x2F\x07\xB5\x94\x35\xFF\x2A\x9B": + if data[0:8] == b"\x2f\x07\xb5\x94\x35\xff\x2a\x9b": crc = CRC16.CMS.calc(data[0:32]) crc_found = betoint(data[32:34]) if crc == crc_found: @@ -183,13 +183,13 @@ def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: with out_ug.write() as ug: hdr = BytesIO() ota_bin = ota_data.getvalue() - hdr.write(b"\x55\xAA\x55\xAA") + hdr.write(b"\x55\xaa\x55\xaa") hdr.write(pad_data(version.encode(), 12, 0x00)) hdr.write(inttobe32(len(ota_bin))) hdr.write(inttobe32(sum(ota_bin))) ug.write(hdr.getvalue()) ug.write(inttobe32(sum(hdr.getvalue()))) - ug.write(b"\xAA\x55\xAA\x55") + ug.write(b"\xaa\x55\xaa\x55") ug.write(ota_bin) # close all files @@ -218,7 +218,7 @@ def detect_file_type( return Detection.make_unsupported("Beken Encrypted App") # raw firmware binary - if data[0:8] == b"\x0E\x00\x00\xEA\x14\xF0\x9F\xE5": + if data[0:8] == b"\x0e\x00\x00\xea\x14\xf0\x9f\xe5": return Detection.make_unsupported("Raw ARM Binary") # RBL file for OTA - 'download' partition diff --git a/ltchiptool/soc/bk72xx/util/binary.py b/ltchiptool/soc/bk72xx/util/binary.py index cb49eb7..26ef545 100644 --- a/ltchiptool/soc/bk72xx/util/binary.py +++ b/ltchiptool/soc/bk72xx/util/binary.py @@ -44,7 +44,7 @@ def __init__(self, coeffs: Union[bytes, str] = None) -> None: def crc(self, data: ByteSource, type: DataType = None) -> DataGenerator: for block in geniter(data, 32): if len(block) < 32: - block += b"\xFF" * (32 - len(block)) + block += b"\xff" * (32 - len(block)) crc = CRC16.CMS.calc(block) block += inttobe16(crc) if type: @@ -54,7 +54,7 @@ def crc(self, data: ByteSource, type: DataType = None) -> DataGenerator: def uncrc(self, data: ByteSource, check: bool = True) -> ByteGenerator: for block in geniter(data, 34): - if check and block != b"\xFF" * 34: + if check and block != b"\xff" * 34: crc = CRC16.CMS.calc(block[0:32]) crc_found = betoint(block[32:34]) if crc != crc_found: diff --git a/ltchiptool/soc/ln882h/flash.py b/ltchiptool/soc/ln882h/flash.py index 8d437d7..95cb3be 100644 --- a/ltchiptool/soc/ln882h/flash.py +++ b/ltchiptool/soc/ln882h/flash.py @@ -1,14 +1,10 @@ # Copyright (c) Etienne Le Cousin 2025-01-02. from abc import ABC -from binascii import crc32 from typing import IO, Generator, List, Optional, Tuple, Union from ltchiptool import SocInterface from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType -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 diff --git a/ltchiptool/soc/ln882h/util/ln882htool.py b/ltchiptool/soc/ln882h/util/ln882htool.py index 85127a5..777c32f 100644 --- a/ltchiptool/soc/ln882h/util/ln882htool.py +++ b/ltchiptool/soc/ln882h/util/ln882htool.py @@ -7,14 +7,9 @@ from typing import IO, Callable, Generator, Optional import click -from hexdump import hexdump, restore -from serial import Serial from ymodem.Socket import ModemSocket 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_YmodemCB = Optional[Callable[[int, str, int, int], None]] diff --git a/ltchiptool/soc/ln882h/util/models/boot_header.py b/ltchiptool/soc/ln882h/util/models/boot_header.py index 0d5f9e3..9d8e59b 100644 --- a/ltchiptool/soc/ln882h/util/models/boot_header.py +++ b/ltchiptool/soc/ln882h/util/models/boot_header.py @@ -19,7 +19,6 @@ import zlib import struct -from .ln_tools import * class BootHeader: diff --git a/ltchiptool/util/detection.py b/ltchiptool/util/detection.py index 0e11e65..0c67f72 100644 --- a/ltchiptool/util/detection.py +++ b/ltchiptool/util/detection.py @@ -13,16 +13,16 @@ FILE_TYPES = { "UF2": [ - (0x000, b"UF2\x0A"), - (0x004, b"\x57\x51\x5D\x9E"), - (0x1FC, b"\x30\x6F\xB1\x0A"), + (0x000, b"UF2\x0a"), + (0x004, b"\x57\x51\x5d\x9e"), + (0x1FC, b"\x30\x6f\xb1\x0a"), ], "ELF": [ - (0x00, b"\x7FELF"), + (0x00, b"\x7fELF"), ], "Tuya UG": [ - (0x00, b"\x55\xAA\x55\xAA"), - (0x1C, b"\xAA\x55\xAA\x55"), + (0x00, b"\x55\xaa\x55\xaa"), + (0x1C, b"\xaa\x55\xaa\x55"), ], } diff --git a/uf2tool/models/block.py b/uf2tool/models/block.py index 5f3196d..d5b4c43 100644 --- a/uf2tool/models/block.py +++ b/uf2tool/models/block.py @@ -37,7 +37,7 @@ def encode(self) -> bytes: self.flags.has_tags = not not self.tags self.length = self.data and len(self.data) or 0 # UF2 magic 1 and 2 - data = b"\x55\x46\x32\x0A\x57\x51\x5D\x9E" + data = b"\x55\x46\x32\x0a\x57\x51\x5d\x9e" # encode integer variables data += inttole32(self.flags.encode()) data += inttole32(self.address) @@ -71,7 +71,7 @@ def encode(self) -> bytes: raise ValueError("Padding too long") data += self.padding data += b"\x00" * (512 - 4 - len(data)) - data += b"\x30\x6F\xB1\x0A" # magic 3 + data += b"\x30\x6f\xb1\x0a" # magic 3 return data def decode(self, data: bytes):