From 00606f1ba1f410002d53dc7fb6192c6d3acae104 Mon Sep 17 00:00:00 2001 From: lamauny Date: Fri, 3 Jan 2025 18:05:11 +0100 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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})" )