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:
parent
0f18520690
commit
4208707088
@ -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__":
|
||||||
|
21
core/tools/trezor_core_tools/common.py
Normal file
21
core/tools/trezor_core_tools/common.py
Normal 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"
|
@ -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__":
|
||||||
|
Loading…
Reference in New Issue
Block a user