[realtek-ambz] Export both OTA images after linking

This commit is contained in:
Kuba Szczodrzyński
2022-06-01 21:41:04 +02:00
parent 3e11da4dd4
commit 81897e634c
2 changed files with 231 additions and 114 deletions

View File

@@ -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")

227
tools/link2bin.py Normal file
View File

@@ -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)