228 lines
6.8 KiB
Python
228 lines
6.8 KiB
Python
# 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)
|