[realtek-ambz] Export both OTA images after linking
This commit is contained in:
@@ -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
227
tools/link2bin.py
Normal 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)
|
||||
Reference in New Issue
Block a user