1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-03 03:50:58 +00:00

refactor(core/tools): make combine_firmware nicer

This commit is contained in:
matejcik 2024-07-31 17:37:29 +02:00 committed by matejcik
parent 0f18520690
commit 4208707088
3 changed files with 104 additions and 57 deletions

View File

@ -3,24 +3,16 @@ from __future__ import annotations
import datetime import datetime
import io import io
import sys
from pathlib import Path from pathlib import Path
import os
import subprocess
import click import click
def get_layout_params(layout: Path, name: str) -> int: from .layout_parser import find_all_values
directory = os.path.dirname(os.path.realpath(__file__))
with subprocess.Popen(args=["python", Path(directory, "layout_parser.py"), str(layout), name],
stdout=subprocess.PIPE) as script:
return int(script.stdout.read().decode().strip())
@click.command() @click.command()
@click.argument( @click.argument("model")
"layout",
type=click.Path(dir_okay=False, writable=True, path_type=Path),
required=True,
)
@click.argument( @click.argument(
"outfile", "outfile",
type=click.Path(dir_okay=False, writable=True, path_type=Path), type=click.Path(dir_okay=False, writable=True, path_type=Path),
@ -32,37 +24,59 @@ def get_layout_params(layout: Path, name: str) -> int:
type=(str, click.Path(exists=True, dir_okay=False, readable=True, path_type=Path)), type=(str, click.Path(exists=True, dir_okay=False, readable=True, path_type=Path)),
multiple=True, multiple=True,
) )
def main( def main(model: str, bin: list[tuple[str, Path]], outfile: Path | None) -> None:
layout: Path, """Create a combined.bin file from components.
bin: List[Tuple[Path, str]],
outfile: Path | None,
) -> None: MODEL is the internal model name (e.g. T1B1, T2T1, T2B1).
BIN is a list of tuples with the type and path to the binary file. The usual types
are:
\b
* BOARDLOADER
* BOOTLOADER
* FIRMWARE
For example:
\b
$ combine_firmware T3T1 -b boardloader build/boardloader.bin -b bootloader build/bootloader.bin -b firmware build/firmware.bin
"""
if outfile is None: if outfile is None:
today = datetime.date.today().strftime(r"%Y-%m-%d") today = datetime.date.today().strftime(r"%Y-%m-%d")
outfile = Path(f"combined-{today}.bin") outfile = Path(f"combined-{today}.bin")
first_bin = bin[0] offset = 0
(name, bin_path) = first_bin out_buf = io.BytesIO()
start_offset = get_layout_params(layout, name+ "_START") all_values = find_all_values(model)
offset = start_offset def find_value(name: str) -> int:
out_bytes = io.BytesIO() value_name = f"{name.upper()}_START"
if value_name not in all_values:
click.echo(f"ERROR: component {name} not found in layout for model {model}")
click.echo("Try one of: boardloader, bootloader, firmware")
sys.exit(1)
return all_values[value_name]
for (name, bin_path) in bin: for name, bin_path in bin:
bin_start = get_layout_params(layout, name + "_START") bin_start = find_value(name)
# zero-pad until next section:
offset += out_bytes.write(b"\x00" * (bin_start - offset)) if not offset:
# initialize offset
offset = bin_start
else:
# pad until next section
offset += out_buf.write(b"\x00" * (bin_start - offset))
assert offset == bin_start assert offset == bin_start
# write binary # write binary
offset += out_bytes.write(bin_path.read_bytes()) offset += out_buf.write(bin_path.read_bytes())
# write out contents # write out contents
click.echo(f"Writing {outfile} ({offset - start_offset} bytes)") out_bytes = out_buf.getvalue()
outfile.write_bytes(out_bytes.getvalue()) click.echo(f"Writing {outfile} ({len(out_bytes)} bytes)")
outfile.write_bytes(out_bytes)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -0,0 +1,21 @@
from __future__ import annotations
from pathlib import Path
HERE = Path(__file__).parent.resolve()
CORE = HERE.parent.parent
MODELS_DIR = CORE / "embed" / "models"
MODELS_DICT = {
"1": "T1B1",
"T": "T2T1",
"R": "T2B1",
"DISC1": "D001",
"DISC2": "D002",
}
def get_layout_for_model(model: str) -> Path:
model = MODELS_DICT.get(model, model)
return MODELS_DIR / model / f"model_{model}.h"

View File

@ -1,40 +1,52 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import re
from pathlib import Path
import click import click
import ast import ast
from .common import get_layout_for_model
# the following regular expression matches a thing looking like those examples:
# #define HEADER_START 0x123456
# #define HEADER_START (1 * 2 * 3) // comment
# and returns two groups: the name and the value. Comment is ignored.
SEARCH_PATTERN = r"#define (\w+) (.+?)(?:\s*//.*)?$"
def find_all_values(model: str) -> dict[str, int]:
layout = get_layout_for_model(model)
values = {}
for line in open(layout):
match = re.match(SEARCH_PATTERN, line)
if match is not None:
name, value = match.groups()
try:
node = ast.parse(value, mode="eval")
parsed_value = eval(compile(node, "<string>", "eval"))
values[name] = int(parsed_value)
except Exception:
pass
return values
def find_value(model: str, name: str) -> int:
all_values = find_all_values(model)
if name not in all_values:
raise ValueError(f"Value {name} not found in layout for model {model}")
return all_values[name]
@click.command() @click.command()
@click.argument( @click.argument("model")
"layout", @click.argument("name")
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path), def main(model: str, name: str) -> None:
) try:
@click.argument( print(find_value(model, name))
"name", except ValueError as e:
type=click.STRING, raise click.ClickException(str(e))
)
def main(
layout: Path,
name: str,
) -> None:
search = f'#define {name} '
for line in layout.read_text().splitlines():
if line.startswith(search):
line = line.split(search)[1]
if line.startswith("("):
line = line.split("(")[1].split(")")[-2]
if "//" in line:
line = line.split("//")[0]
line = line.strip()
node = ast.parse(line, mode='eval')
result = eval(compile(node, '<string>', 'eval'))
print(result)
if __name__ == "__main__": if __name__ == "__main__":