From 81897e634cc879dee6ed38680f95ed5e143fe749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 1 Jun 2022 21:41:04 +0200 Subject: [PATCH] [realtek-ambz] Export both OTA images after linking --- builder/frameworks/realtek-ambz-sdk.py | 118 +------------ tools/link2bin.py | 227 +++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 114 deletions(-) create mode 100644 tools/link2bin.py diff --git a/builder/frameworks/realtek-ambz-sdk.py b/builder/frameworks/realtek-ambz-sdk.py index 6ac2633..a20e8ca 100644 --- a/builder/frameworks/realtek-ambz-sdk.py +++ b/builder/frameworks/realtek-ambz-sdk.py @@ -21,6 +21,7 @@ ota2_offset = hex(int(flash_addr, 16) + int(flash_ota2_offset, 16)) env.Replace( IMG_FW="image2_all_ota1.bin", IMG_OTA="ota_all.bin", + LINK="python ${LT_DIR}/tools/link2bin.py AMBZ xip1 xip2", ) # Tools @@ -278,65 +279,6 @@ env.Replace( SIZEPRINTCMD="$SIZETOOL -B -d $SOURCES", ) -# Image conversion -def pick_tool(target, source, env): - sections = [ - "__ram_image2_text_start__", - "__ram_image2_text_end__", - "__xip_image2_start__", - ] - addrs = [None] * len(sections) - with open(env.subst("${BUILD_DIR}/${PROGNAME}.nmap")) as f: - for line in f: - for i, section in enumerate(sections): - if section not in line: - continue - addrs[i] = line.split()[0] - files = [ - join("$BUILD_DIR", "ram_2.r.bin"), # RAM image with padding - join("$BUILD_DIR", "ram_2.bin"), # RAM image, stripped - join("$BUILD_DIR", "ram_2.p.bin"), # RAM image, stripped, with header - join("$BUILD_DIR", "xip_image2.bin"), # raw firmware image - join("$BUILD_DIR", "xip_image2.p.bin"), # firmware with header - ] - commands = [ - f"$PICK 0x{addrs[0]} 0x{addrs[1]} {files[0]} {files[1]} raw", - f"$PICK 0x{addrs[0]} 0x{addrs[1]} {files[1]} {files[2]}", - f"$PICK 0x{addrs[2]} 0x{addrs[2]} {files[3]} {files[4]}", - ] - for command in commands: - status = env.Execute("@" + command + " > " + join("$BUILD_DIR", "pick.txt")) - if status: - return status - - -def concat_xip_ram(target, source, env): - with open(env.subst("${BUILD_DIR}/xip_image2.p.bin"), "rb") as f: - xip = f.read() - with open(env.subst("${BUILD_DIR}/ram_2.p.bin"), "rb") as f: - ram = f.read() - with open(env.subst("${BUILD_DIR}/${IMG_FW}"), "wb") as f: - f.write(xip) - f.write(ram) - - -def checksum_img(target, source, env): - source = join("$BUILD_DIR", "$IMG_FW") - status = env.Execute(f"@$CHECKSUM {source}") - if status: - return status - - -def package_ota(target, source, env): - source = join("$BUILD_DIR", "$IMG_FW") - target = join("$BUILD_DIR", "$IMG_OTA") - status = env.Execute( - f"@$OTA {source} {ota1_offset} {source} {ota2_offset} 0x20170111 {target}" - ) - if status: - return status - - env.Append( BUILDERS=dict( BinToObj=Builder( @@ -353,63 +295,11 @@ env.Append( ) ), ) -commands = [ - ( - "${PROGNAME}.nmap", - [ - "$NM", - "$SOURCE", - "> $BIN", - ], - ), - ( - "ram_2.r.bin", - [ - "$OBJCOPY", - "-j .ram_image2.entry", - "-j .ram_image2.data", - "-j .ram_image2.bss", - "-j .ram_image2.skb.bss", - "-j .ram_heap.data", - "-O binary", - "$SOURCE", - "$BIN", - ], - ), - ( - "xip_image2.bin", - [ - "$OBJCOPY", - "-j .xip_image2.text", - "-O binary", - "$SOURCE", - "$BIN", - ], - ), - ( - "rdp.bin", - [ - "$OBJCOPY", - "-j .ram_rdp.text", - "-O binary", - "$SOURCE", - "$BIN", - ], - ), -] actions = [ - env.VerboseAction( - " ".join(command).replace("$BIN", join("$BUILD_DIR", target)), - f"Generating {target}", - ) - for target, command in commands + # env.VerboseAction(package_ota, "Packaging OTA image - $IMG_OTA"), + env.VerboseAction("true", f"- OTA1 flash offset: $FLASH_OTA1_OFFSET"), + env.VerboseAction("true", f"- OTA2 flash offset: $FLASH_OTA2_OFFSET"), ] -actions.append(env.VerboseAction(pick_tool, "Wrapping binary images")) -actions.append(env.VerboseAction(concat_xip_ram, "Packaging firmware image - $IMG_FW")) -# actions.append(env.VerboseAction(checksum_img, "Generating checksum")) -actions.append(env.VerboseAction(package_ota, "Packaging OTA image - $IMG_OTA")) -actions.append(env.VerboseAction("true", f"- OTA1 flash offset: $FLASH_OTA1_OFFSET")) -actions.append(env.VerboseAction("true", f"- OTA2 flash offset: $FLASH_OTA2_OFFSET")) # Uploader upload_protocol = env.subst("$UPLOAD_PROTOCOL") diff --git a/tools/link2bin.py b/tools/link2bin.py new file mode 100644 index 0000000..b731a2c --- /dev/null +++ b/tools/link2bin.py @@ -0,0 +1,227 @@ +# Copyright (c) Kuba SzczodrzyƄski 2022-05-31. + +from argparse import ArgumentParser +from enum import Enum +from os import stat, unlink +from os.path import basename, dirname, getmtime, isfile, join +from shutil import copyfile +from subprocess import PIPE, Popen +from typing import IO, Dict, List, Tuple + + +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) + return process.stdout + + @property + def dual_ota(self): + return self.value[2] + + +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) + + +# ____ _ _ _ _ +# | _ \(_) | | (_) | +# | |_) |_ _ __ _ _| |_ _| |___ +# | _ <| | '_ \| | | | __| | / __| +# | |_) | | | | | |_| | |_| | \__ \ +# |____/|_|_| |_|\__,_|\__|_|_|___/ +def nm(input: str) -> Dict[str, int]: + out = {} + stdout = soc.cmd(f"gcc-nm {input}") + for line in stdout.readlines(): + line = line.decode().strip().split(" ") + if len(line) != 3: + continue + out[line[2]] = int(line[0], 16) + return out + + +def objcopy( + input: str, + output: str, + sections: List[str], + fmt: str = "binary", +) -> str: + # print graph element + print(f"| | |-- {basename(output)}") + if isnewer(input, output): + sections = " ".join(f"-j {section}" for section in sections) + soc.cmd(f"objcopy {sections} -O {fmt} {input} {output}").read() + return output + + +# ______ _ ______ _ ____ _____ _ _ +# | ____| | | ____| | | | _ \_ _| \ | | +# | |__ | | | |__ | |_ ___ | |_) || | | \| | +# | __| | | | __| | __/ _ \ | _ < | | | . ` | +# | |____| |____| | | || (_) | | |_) || |_| |\ | +# |______|______|_| \__\___/ |____/_____|_| \_| +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(b"\xff" * 16) + + sections_ram = [ + ".ram_image2.entry", + ".ram_image2.data", + ".ram_image2.bss", + ".ram_image2.skb.bss", + ".ram_heap.data", + ] + sections_xip = [".xip_image2.text"] + sections_rdp = [".ram_rdp.text"] + nmap = nm(input) + ram_start = nmap["__ram_image2_text_start__"] + ram_end = nmap["__ram_image2_text_end__"] + xip_start = nmap["__flash_text_start__"] - 0x8000020 + # build output name + output = chname(input, f"image_0x{xip_start:06X}.ota{ota_idx}.bin") + out_ram = chname(input, f"ota{ota_idx}.ram_2.r.bin") + out_xip = chname(input, f"ota{ota_idx}.xip_image2.bin") + out_rdp = chname(input, f"ota{ota_idx}.rdp.bin") + # print graph element + print(f"| |-- {basename(output)}") + # objcopy required images + ram = objcopy(input, out_ram, sections_ram) + xip = objcopy(input, out_xip, sections_xip) + objcopy(input, out_rdp, sections_rdp) + # return if images are up to date + if not isnewer(ram, output) and not isnewer(xip, output): + return (xip_start, output) + + # read and trim RAM image + ram = readbin(ram).rstrip(b"\x00") + # read XIP image + xip = readbin(xip) + # align images to 4 bytes + ram += b"\x00" * (((((len(ram) - 1) // 4) + 1) * 4) - len(ram)) + xip += b"\x00" * (((((len(xip) - 1) // 4) + 1) * 4) - len(xip)) + # write output file + with open(output, "wb") as f: + # write XIP header + write_header(f, 0, len(xip)) + # write XIP image + f.write(xip) + # write RAM header + write_header(f, ram_start, ram_end) + # write RAM image + f.write(ram) + return (xip_start, output) + + +def elf2bin(input: str, ota_idx: int = 1) -> Tuple[int, str]: + checkfile(input) + if soc == SocType.AMBZ: + return elf2bin_ambz(input, ota_idx) + raise NotImplementedError(f"SoC ELF->BIN not implemented: {soc}") + + +def ldargs_parse( + args: List[str], + ld_ota1: str, + ld_ota2: str, +) -> List[Tuple[str, List[str]]]: + args1 = list(args) + args2 = list(args) + elf1 = elf2 = None + for i, arg in enumerate(args): + arg = arg.strip('"').strip("'") + if ".elf" in arg: + if not ld_ota1: + # single-OTA chip, return the output name + return [(arg, args)] + # append OTA index in filename + elf1 = chext(arg, "ota1.elf") + elf2 = chext(arg, "ota2.elf") + args1[i] = '"' + elf1 + '"' + args2[i] = '"' + elf2 + '"' + if arg.endswith(".ld") and ld_ota1: + # use OTA2 linker script + args2[i] = arg.replace(ld_ota1, ld_ota2) + return [(elf1, args1), (elf2, args2)] + + +def link2bin( + args: List[str], + ld_ota1: str = None, + ld_ota2: str = None, +) -> List[str]: + elfs = [] + if soc.dual_ota: + # process linker arguments for dual-OTA chips + elfs = ldargs_parse(args, ld_ota1, ld_ota2) + else: + # just get .elf output name for single-OTA chips + elfs = ldargs_parse(args, None, None) + + ota_idx = 1 + for elf, ldargs in elfs: + # print graph element + print(f"|-- Image {ota_idx}: {basename(elf)}") + if isfile(elf): + unlink(elf) + ldargs = " ".join(ldargs) + soc.cmd(f"gcc {ldargs}").read() + checkfile(elf) + elf2bin(elf, ota_idx) + ota_idx += 1 + + if soc.dual_ota: + # copy OTA1 file as firmware.elf to make PIO understand it + elf, _ = ldargs_parse(args, None, None)[0] + copyfile(elfs[0][0], elf) + + +if __name__ == "__main__": + parser = ArgumentParser( + prog="link2bin", + description="Link to BIN format", + prefix_chars="@", + ) + parser.add_argument("soc", type=str, help="SoC name/platform short name") + parser.add_argument("ota1", type=str, help=".LD file OTA1 pattern") + 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) + link2bin(args.args, args.ota1, args.ota2)