Files
libretiny/tools/link2bin.py
2022-07-04 22:37:25 +02:00

242 lines
6.9 KiB
Python

# Copyright (c) Kuba Szczodrzyński 2022-05-31.
import sys
from os.path import dirname, join
sys.path.append(join(dirname(__file__), ".."))
import shlex
from argparse import ArgumentParser
from enum import Enum
from os import stat, unlink
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, isnewer, readtext
from tools.util.models import Family
from tools.util.obj import get
from tools.util.platform import get_board_manifest, get_family
class SocType(Enum):
UNSET = ()
# (index, toolchain prefix, has dual-OTA, argument count)
AMBZ = (1, "arm-none-eabi-", True, 0)
BK72XX = (2, "arm-none-eabi-", False, 0)
def cmd(self, program: str, args: List[str] = []) -> IO[bytes]:
program = self.prefix + program
cmd = [program] + args
try:
process = Popen(cmd, stdout=PIPE)
except FileNotFoundError:
if isinstance(cmd, list):
cmd = " ".join(cmd)
print(f"Toolchain not found while running: '{cmd}'")
exit(1)
return process.stdout
@property
def prefix(self) -> str:
return self.value[1]
@property
def dual_ota(self) -> bool:
return self.value[2]
@property
def soc_argc(self) -> int:
return self.value[3]
def nm(self, input: str) -> Dict[str, int]:
out = {}
stdout = self.cmd("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(
self,
input: str,
output: str,
sections: List[str] = [],
fmt: str = "binary",
) -> str:
# print graph element
print(f"| | |-- {basename(output)}")
if isnewer(input, output):
args = []
for section in sections:
args += ["-j", section]
args += ["-O", fmt]
args += [input, output]
self.cmd("objcopy", args).read()
return output
# _ _ _ _ _ _ _ _
# | | | | | (_) (_) | (_)
# | | | | |_ _| |_| |_ _ ___ ___
# | | | | __| | | | __| |/ _ \/ __|
# | |__| | |_| | | | |_| | __/\__ \
# \____/ \__|_|_|_|\__|_|\___||___/
def checkfile(path: str):
if not isfile(path) or stat(path).st_size == 0:
print(f"Generated file not found: {path}")
exit(1)
# ______ _ ______ _ ____ _____ _ _
# | ____| | | ____| | | | _ \_ _| \ | |
# | |__ | | | |__ | |_ ___ | |_) || | | \| |
# | __| | | | __| | __/ _ \ | _ < | | | . ` |
# | |____| |____| | | || (_) | | |_) || |_| |\ |
# |______|______|_| \__\___/ |____/_____|_| \_|
def elf2bin(
soc: SocType,
family: Family,
board: dict,
input: str,
ota_idx: int = 1,
args: List[str] = [],
) -> Tuple[int, str]:
checkfile(input)
func = None
if soc == SocType.AMBZ:
from tools.soc.link2bin_ambz import elf2bin_ambz
func = elf2bin_ambz
elif soc == SocType.BK72XX:
from tools.soc.link2bin_bk72xx import elf2bin_bk72xx
func = elf2bin_bk72xx
if func:
return func(soc, family, board, input, ota_idx, *args)
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):
if ".elf" in arg:
if not ld_ota1:
# single-OTA chip, return the output name
return [(arg, args)]
# append OTA index in filename
args1[i] = elf1 = chext(arg, "ota1.elf")
args2[i] = elf2 = chext(arg, "ota2.elf")
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)]
def link2bin(
soc: SocType,
family: Family,
board: dict,
ld_args: List[str],
ld_ota1: str = None,
ld_ota2: str = None,
soc_args: List[str] = [],
) -> List[str]:
elfs = []
if soc.dual_ota:
# process linker arguments for dual-OTA chips
elfs = ldargs_parse(ld_args, ld_ota1, ld_ota2)
else:
# just get .elf output name for single-OTA chips
elfs = ldargs_parse(ld_args, None, None)
if not elfs:
return None
ota_idx = 1
for elf, ldargs in elfs:
# print graph element
print(f"|-- Image {ota_idx}: {basename(elf)}")
if isfile(elf):
unlink(elf)
soc.cmd(f"gcc", args=ldargs).read()
checkfile(elf)
# generate a set of binaries for the SoC
elf2bin(soc, family, board, elf, ota_idx, soc_args)
ota_idx += 1
if soc.dual_ota:
# copy OTA1 file as firmware.elf to make PIO understand it
elf, _ = ldargs_parse(ld_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("board", type=str, help="Target board 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="SoC+linker arguments")
args = parser.parse_args()
try:
board = get_board_manifest(args.board)
except FileNotFoundError:
print(f"Board not found: {args.board}")
exit(1)
family = get_family(short_name=get(board, "build.family"))
soc_types = {soc.name.lower(): soc for soc in SocType}
soc = soc_types.get(family.code, soc_types.get(family.parent_code, None))
if not soc:
print(f"SoC type not found. Tried {family.code}, {family.parent_code}")
exit(1)
if not args.args:
print(f"Linker arguments must not be empty")
exit(1)
try:
while True:
i = next(i for i, a in enumerate(args.args) if a.startswith("@"))
arg = args.args.pop(i)
argv = readtext(arg[1:])
argv = shlex.split(argv)
args.args = args.args[0:i] + argv + args.args[i:]
except StopIteration:
pass
link2bin(
soc,
family,
board,
args.args[soc.soc_argc :],
args.ota1,
args.ota2,
args.args[: soc.soc_argc],
)