diff --git a/tools/dumptool.py b/tools/dumptool.py index 380b123..5d3387d 100644 --- a/tools/dumptool.py +++ b/tools/dumptool.py @@ -10,7 +10,7 @@ from binascii import crc32 from os import makedirs from os.path import basename, dirname, join -from tools.util.crypto import crc16 +from tools.util.crc16 import CRC16 from tools.util.platform import get_board_manifest if __name__ == "__main__": @@ -61,7 +61,7 @@ if __name__ == "__main__": cs = crc32(part) cs = cs.to_bytes(length=4, byteorder="big") else: - cs = crc16(part) + cs = CRC16.ARC.calc(part) cs = cs.to_bytes(length=2, byteorder="big") filename = f"{offset}_{name}_{cs.hex().upper()}.bin" print(f"Writing {filename}") diff --git a/tools/link2bin.py b/tools/link2bin.py index b3bb28a..8af38ca 100644 --- a/tools/link2bin.py +++ b/tools/link2bin.py @@ -1,20 +1,32 @@ # Copyright (c) Kuba Szczodrzyński 2022-05-31. +import sys +from os.path import dirname, join + +sys.path.append(join(dirname(__file__), "..")) + from argparse import ArgumentParser from enum import Enum from os import stat, unlink -from os.path import basename, dirname, getmtime, isfile, join +from os.path import basename, dirname, isfile, join from shutil import copyfile from subprocess import PIPE, Popen from typing import IO, Dict, List, Tuple +from tools.util.fileio import chext, chname, isnewer, readbin +from tools.util.intbin import inttole32 + class SocType(Enum): UNSET = () AMBZ = (1, "arm-none-eabi-", True) def cmd(self, cmd: str) -> IO[bytes]: - process = Popen(self.value[1] + cmd, stdout=PIPE) + try: + process = Popen(self.value[1] + cmd, stdout=PIPE) + except FileNotFoundError: + print(f"Toolchain not found while running: '{self.value[1] + cmd}'") + exit(1) return process.stdout @property @@ -30,28 +42,6 @@ soc: "SocType" = SocType.UNSET # | | | | __| | | | __| |/ _ \/ __| # | |__| | |_| | | | |_| | __/\__ \ # \____/ \__|_|_|_|\__|_|\___||___/ -def chname(path: str, name: str) -> str: - return join(dirname(path), name) - - -def chext(path: str, ext: str) -> str: - return path.rpartition(".")[0] + "." + ext - - -def isnewer(what: str, than: str) -> bool: - if not isfile(than): - return True - if not isfile(what): - return False - return getmtime(what) > getmtime(than) - - -def readbin(file: str) -> bytes: - with open(file, "rb") as f: - data = f.read() - return data - - def checkfile(path: str): if not isfile(path) or stat(path).st_size == 0: exit(1) @@ -97,8 +87,8 @@ def objcopy( def elf2bin_ambz(input: str, ota_idx: int = 1) -> Tuple[int, str]: def write_header(f: IO[bytes], start: int, end: int): f.write(b"81958711") - f.write((end - start).to_bytes(length=4, byteorder="little")) - f.write(start.to_bytes(length=4, byteorder="little")) + f.write(inttole32(end - start)) + f.write(inttole32(start)) f.write(b"\xff" * 16) sections_ram = [ @@ -156,6 +146,12 @@ def elf2bin(input: str, ota_idx: int = 1) -> Tuple[int, str]: raise NotImplementedError(f"SoC ELF->BIN not implemented: {soc}") +# _ _ _ +# | | (_) | | +# | | _ _ __ | | _____ _ __ +# | | | | '_ \| |/ / _ \ '__| +# | |____| | | | | < __/ | +# |______|_|_| |_|_|\_\___|_| def ldargs_parse( args: List[str], ld_ota1: str, @@ -178,6 +174,9 @@ def ldargs_parse( if arg.endswith(".ld") and ld_ota1: # use OTA2 linker script args2[i] = arg.replace(ld_ota1, ld_ota2) + if not elf1 or not elf2: + print("Linker output .elf not found in arguments") + return None return [(elf1, args1), (elf2, args2)] @@ -194,6 +193,9 @@ def link2bin( # just get .elf output name for single-OTA chips elfs = ldargs_parse(args, None, None) + if not elfs: + return None + ota_idx = 1 for elf, ldargs in elfs: # print graph element @@ -203,6 +205,7 @@ def link2bin( ldargs = " ".join(ldargs) soc.cmd(f"gcc {ldargs}").read() checkfile(elf) + # generate a set of binaries for the SoC elf2bin(elf, ota_idx) ota_idx += 1 @@ -223,5 +226,12 @@ if __name__ == "__main__": parser.add_argument("ota2", type=str, help=".LD file OTA2 pattern") parser.add_argument("args", type=str, nargs="*", help="Linker arguments") args = parser.parse_args() - soc = next(soc for soc in SocType if soc.name == args.soc) + try: + soc = next(soc for soc in SocType if soc.name == args.soc) + except StopIteration: + print(f"Not a valid SoC: {args.soc}") + exit(1) + if not args.args: + print(f"Linker arguments must not be empty") + exit(1) link2bin(args.args, args.ota1, args.ota2) diff --git a/tools/util/crc16.py b/tools/util/crc16.py new file mode 100644 index 0000000..f9a2841 --- /dev/null +++ b/tools/util/crc16.py @@ -0,0 +1,133 @@ +# Copyright (c) Kuba Szczodrzyński 2022-06-02. + +from enum import Enum +from typing import List + + +class CRC16(Enum): + # based on https://crccalc.com/ and https://reveng.sourceforge.io/crc-catalogue/16.htm + ANSI = dict(poly=0x8005, init=0x0000, ref=False, out=0x0000) + ARC = dict(poly=0x8005, init=0x0000, ref=True, out=0x0000) + AUG_CCITT = dict(poly=0x1021, init=0x1D0F, ref=False, out=0x0000) + AUTOSAR = dict(poly=0x1021, init=0xFFFF, ref=False, out=0x0000) + BUYPASS = dict(poly=0x8005, init=0x0000, ref=False, out=0x0000) + CCITT = dict(poly=0x1021, init=0x0000, ref=True, out=0x0000) + CCITT_FALSE = dict(poly=0x1021, init=0xFFFF, ref=False, out=0x0000) + CCITT_TRUE = dict(poly=0x1021, init=0x0000, ref=True, out=0x0000) + CDMA2000 = dict(poly=0xC867, init=0xFFFF, ref=False, out=0x0000) + CMS = dict(poly=0x8005, init=0xFFFF, ref=False, out=0x0000) + CRC_A = dict(poly=0x1021, init=0xC6C6, ref=True, out=0x0000) + CRC_B = dict(poly=0x1021, init=0xFFFF, ref=True, out=0xFFFF) + DARC = dict(poly=0x1021, init=0xFFFF, ref=False, out=0xFFFF) + DDS_110 = dict(poly=0x8005, init=0x800D, ref=False, out=0x0000) + DECT_R = dict(poly=0x0589, init=0x0000, ref=False, out=0x0001) + DECT_X = dict(poly=0x0589, init=0x0000, ref=False, out=0x0000) + DNP = dict(poly=0x3D65, init=0x0000, ref=True, out=0xFFFF) + EN_13757 = dict(poly=0x3D65, init=0x0000, ref=False, out=0xFFFF) + EPC = dict(poly=0x1021, init=0xFFFF, ref=False, out=0xFFFF) + EPC_C1G2 = dict(poly=0x1021, init=0xFFFF, ref=False, out=0xFFFF) + GENIBUS = dict(poly=0x1021, init=0xFFFF, ref=False, out=0xFFFF) + GSM = dict(poly=0x1021, init=0x0000, ref=False, out=0xFFFF) + I_CODE = dict(poly=0x1021, init=0xFFFF, ref=False, out=0xFFFF) + IBM = dict(poly=0x8005, init=0x0000, ref=False, out=0x0000) + IBM_3740 = dict(poly=0x1021, init=0xFFFF, ref=False, out=0x0000) + IBM_SDLC = dict(poly=0x1021, init=0xFFFF, ref=True, out=0xFFFF) + IEC_61158_2 = dict(poly=0x1DCF, init=0xFFFF, ref=False, out=0xFFFF) + ISO_14443_3_A = dict(poly=0x1021, init=0xC6C6, ref=True, out=0x0000) + ISO_14443_3_B = dict(poly=0x1021, init=0xFFFF, ref=True, out=0xFFFF) + ISO_HDLC = dict(poly=0x1021, init=0xFFFF, ref=True, out=0xFFFF) + KERMIT = dict(poly=0x1021, init=0x0000, ref=True, out=0x0000) + LHA = dict(poly=0x8005, init=0x0000, ref=True, out=0x0000) + LJ1200 = dict(poly=0x6F63, init=0x0000, ref=False, out=0x0000) + M17 = dict(poly=0x5935, init=0xFFFF, ref=False, out=0x0000) + MAXIM = dict(poly=0x8005, init=0x0000, ref=True, out=0xFFFF) + MCRF4XX = dict(poly=0x1021, init=0xFFFF, ref=True, out=0x0000) + MODBUS = dict(poly=0x8005, init=0xFFFF, ref=True, out=0x0000) + NRSC_5 = dict(poly=0x080B, init=0xFFFF, ref=True, out=0x0000) + OPENSAFETY_A = dict(poly=0x5935, init=0x0000, ref=False, out=0x0000) + OPENSAFETY_B = dict(poly=0x755B, init=0x0000, ref=False, out=0x0000) + PROFIBUS = dict(poly=0x1DCF, init=0xFFFF, ref=False, out=0xFFFF) + RIELLO = dict(poly=0x1021, init=0xB2AA, ref=True, out=0x0000) + SPI_FUJITSU = dict(poly=0x1021, init=0x1D0F, ref=False, out=0x0000) + T10_DIF = dict(poly=0x8BB7, init=0x0000, ref=False, out=0x0000) + TELEDISK = dict(poly=0xA097, init=0x0000, ref=False, out=0x0000) + TMS37157 = dict(poly=0x1021, init=0x89EC, ref=True, out=0x0000) + UMTS = dict(poly=0x8005, init=0x0000, ref=False, out=0x0000) + USB = dict(poly=0x8005, init=0xFFFF, ref=True, out=0xFFFF) + V_41_LSB = dict(poly=0x1021, init=0x0000, ref=True, out=0x0000) + VERIFONE = dict(poly=0x8005, init=0x0000, ref=False, out=0x0000) + X_25 = dict(poly=0x1021, init=0xFFFF, ref=True, out=0xFFFF) + XMODEM = dict(poly=0x1021, init=0x0000, ref=False, out=0x0000) + + poly: int + init: int + ref: bool + out: int + table: List[int] + + def __init__(self, params: dict) -> None: + super().__init__() + self.poly = params["poly"] + self.init = params["init"] + self.ref = params["ref"] + self.out = params["out"] + self.table = None + if self.ref: + self.poly = self.reverse16(self.poly) + self.init = self.reverse16(self.init) + + @staticmethod + def reverse16(num: int) -> int: + out = 0 + for i in range(16): + out |= ((num & (1 << i)) >> i) << (15 - i) + return out + + def calc(self, data: bytes) -> int: + if self.ref: + self._init_ref() + return self._calc_ref(data) + self._init_std() + return self._calc_std(data) + + def _init_std(self): + if self.table: + return + self.table = [] + for b in range(256): + crc = b << 8 + for _ in range(8): + if crc & 0x8000: + crc <<= 1 + crc ^= self.poly + else: + crc <<= 1 + self.table.append(crc & 0xFFFF) + + def _init_ref(self): + if self.table: + return + self.table = [] + for b in range(256): + crc = b + for _ in range(8): + if crc & 0x0001: + crc >>= 1 + crc ^= self.poly + else: + crc >>= 1 + self.table.append(crc) + + def _calc_std(self, data: bytes) -> int: + crc = self.init + for b in data: + b ^= crc // 256 + crc = self.table[b] ^ (crc * 256 % 0x10000) + return crc ^ self.out + + def _calc_ref(self, data: bytes) -> int: + crc = self.init + for b in data: + b ^= crc % 256 + crc = self.table[b] ^ (crc // 256) + return crc ^ self.out diff --git a/tools/util/crypto.py b/tools/util/crypto.py deleted file mode 100644 index c344144..0000000 --- a/tools/util/crypto.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) Kuba Szczodrzyński 2022-06-02. - - -def crc16(data): - # https://gist.github.com/pintoXD/a90e398bba5a1b6c121de4e1265d9a29 - crc = 0x0000 - for b in data: - crc ^= b - for j in range(0, 8): - if (crc & 0x0001) > 0: - crc = (crc >> 1) ^ 0xA001 - else: - crc = crc >> 1 - return crc diff --git a/tools/util/fileio.py b/tools/util/fileio.py new file mode 100644 index 0000000..328179a --- /dev/null +++ b/tools/util/fileio.py @@ -0,0 +1,53 @@ +# Copyright (c) Kuba Szczodrzyński 2022-06-10. + +import json +from io import BytesIO +from os.path import dirname, getmtime, isfile, join +from typing import Union + + +def chname(path: str, name: str) -> str: + """Change the basename of 'path' to 'name'.""" + return join(dirname(path), name) + + +def chext(path: str, ext: str) -> str: + """Change the file extension of 'path' to 'ext' (without the dot).""" + return path.rpartition(".")[0] + "." + ext + + +def isnewer(what: str, than: str) -> bool: + """Check if 'what' is newer than 'than'. + + Returns False if 'what' is not a file. + + Returns True if 'than' is not a file. + """ + if not isfile(what): + return False + if not isfile(than): + return True + return getmtime(what) > getmtime(than) + + +def readbin(file: str) -> bytes: + """Read a binary file into a bytes object.""" + with open(file, "rb") as f: + data = f.read() + return data + + +def writebin(file: str, data: Union[bytes, BytesIO]): + """Write data into a binary file.""" + with open(file, "wb") as f: + if isinstance(data, BytesIO): + f.write(data.getvalue()) + else: + f.write(data) + + +# same as load_json +def readjson(file: str) -> Union[dict, list]: + """Read a JSON file into a dict or list.""" + with open(file, "r", encoding="utf-8") as f: + return json.load(f) diff --git a/tools/util/intbin.py b/tools/util/intbin.py index c7221df..9b0b2f3 100644 --- a/tools/util/intbin.py +++ b/tools/util/intbin.py @@ -2,60 +2,138 @@ def bswap(data: bytes) -> bytes: + """Reverse the byte array (big-endian <-> little-endian).""" return bytes(reversed(data)) def betoint(data: bytes) -> int: + """Convert bytes to big-endian unsigned integer.""" return int.from_bytes(data, byteorder="big") def letoint(data: bytes) -> int: + """Convert bytes to little-endian unsigned integer.""" return int.from_bytes(data, byteorder="little") def betosint(data: bytes) -> int: + """Convert bytes to big-endian signed integer.""" return int.from_bytes(data, byteorder="big", signed=True) def letosint(data: bytes) -> int: + """Convert bytes to little-endian signed integer.""" return int.from_bytes(data, byteorder="little", signed=True) +def inttobe32(data: int) -> bytes: + """Convert unsigned integer to 32 bits, big-endian.""" + return data.to_bytes(length=4, byteorder="big") + + def inttole32(data: int) -> bytes: + """Convert unsigned integer to 32 bits, little-endian.""" return data.to_bytes(length=4, byteorder="little") +def inttobe24(data: int) -> bytes: + """Convert unsigned integer to 24 bits, big-endian.""" + return data.to_bytes(length=3, byteorder="big") + + def inttole24(data: int) -> bytes: + """Convert unsigned integer to 24 bits, little-endian.""" return data.to_bytes(length=3, byteorder="little") +def inttobe16(data: int) -> bytes: + """Convert unsigned integer to 16 bits, big-endian.""" + return data.to_bytes(length=2, byteorder="big") + + def inttole16(data: int) -> bytes: + """Convert unsigned integer to 16 bits, little-endian.""" return data.to_bytes(length=2, byteorder="little") def intto8(data: int) -> bytes: + """Convert unsigned integer to 8 bits.""" return data.to_bytes(length=1, byteorder="big") +def sinttobe32(data: int) -> bytes: + """Convert signed integer to 32 bits, big-endian.""" + return data.to_bytes(length=4, byteorder="big", signed=True) + + def sinttole32(data: int) -> bytes: + """Convert signed integer to 32 bits, little-endian.""" return data.to_bytes(length=4, byteorder="little", signed=True) +def sinttobe24(data: int) -> bytes: + """Convert signed integer to 24 bits, big-endian.""" + return data.to_bytes(length=3, byteorder="big", signed=True) + + def sinttole24(data: int) -> bytes: + """Convert signed integer to 24 bits, little-endian.""" return data.to_bytes(length=3, byteorder="little", signed=True) +def sinttobe16(data: int) -> bytes: + """Convert signed integer to 16 bits, big-endian.""" + return data.to_bytes(length=2, byteorder="big", signed=True) + + def sinttole16(data: int) -> bytes: + """Convert signed integer to 16 bits, little-endian.""" return data.to_bytes(length=2, byteorder="little", signed=True) def sintto8(data: int) -> bytes: + """Convert signed integer to 8 bits.""" return data.to_bytes(length=1, byteorder="little", signed=True) def align_up(x: int, n: int) -> int: + """Return x aligned up to block size of n.""" return int((x - 1) // n + 1) * n def align_down(x: int, n: int) -> int: + """Return 'x' aligned down to block size of 'n'.""" return int(x // n) * n + + +def pad_up(x: int, n: int) -> int: + """Return how many bytes of padding is needed to align 'x' + up to block size of 'n'.""" + return n - (x % n) + + +def pad_data(data: bytes, n: int, char: int) -> bytes: + """Add 'char'-filled padding to 'data' to align to a 'n'-sized block.""" + if len(data) % n == 0: + return data + return data + (bytes([char]) * pad_up(len(data), n)) + + +def uint8(val): + """Get only the least-significant 8 bits of the value.""" + return val & 0xFF + + +def uint16(val): + """Get only the least-significant 16 bits of the value.""" + return val & 0xFFFF + + +def uint32(val): + """Get only the least-significant 32 bits of the value.""" + return val & 0xFFFFFFFF + + +def uintmax(bits: int) -> int: + """Get maximum integer size for given bit width.""" + return (2**bits) - 1