1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-23 15:08:19 +00:00

feat(core/prodtest): add nrf update command & script

[no changelog]
This commit is contained in:
tychovrahe 2025-06-26 20:09:19 +02:00 committed by TychoVrahe
parent 351c6da4be
commit d5d921d3d4
3 changed files with 188 additions and 0 deletions

View File

@ -322,6 +322,10 @@ nrf-version
OK 0.1.2.3
```
### nrf-update
Updates the nRF firmware.
### touch-draw
Starts a drawing canvas, where user can draw with finger on pen. Canvas is exited by sending CTRL+C command.
```

View File

@ -69,6 +69,107 @@ static void prodtest_nrf_version(cli_t* cli) {
info.version_patch, info.version_tweak);
}
#define NRF_UPDATE_MAXSIZE 0x50000
__attribute__((section(".buf"))) static uint8_t nrf_buffer[NRF_UPDATE_MAXSIZE];
static void prodtest_nrf_update(cli_t* cli) {
static size_t nrf_len = 0;
static bool nrf_update_in_progress = false;
if (cli_arg_count(cli) < 1) {
cli_error_arg_count(cli);
return;
}
const char* phase = cli_arg(cli, "phase");
if (phase == NULL) {
cli_error_arg(cli, "Expecting phase (begin|chunk|end).");
return;
}
if (0 == strcmp(phase, "begin")) {
if (cli_arg_count(cli) != 1) {
cli_error_arg_count(cli);
goto cleanup;
}
// Reset our state
nrf_len = 0;
nrf_update_in_progress = true;
cli_trace(cli, "begin");
cli_ok(cli, "");
} else if (0 == strcmp(phase, "chunk")) {
if (cli_arg_count(cli) < 2) {
cli_error_arg_count(cli);
goto cleanup;
}
if (!nrf_update_in_progress) {
cli_error(cli, CLI_ERROR, "Update not started. Use 'begin' first.");
goto cleanup;
}
// Receive next piece of the image
size_t chunk_len = 0;
uint8_t chunk_buf[512]; // tune this if you like
if (!cli_arg_hex(cli, "hex-data", chunk_buf, sizeof(chunk_buf),
&chunk_len)) {
cli_error_arg(cli, "Expecting hex-data for chunk.");
goto cleanup;
}
if (nrf_len + chunk_len > NRF_UPDATE_MAXSIZE) {
cli_error(cli, CLI_ERROR, "Buffer overflow (have %u, need %u)",
(unsigned)nrf_len, (unsigned)chunk_len);
goto cleanup;
}
memcpy(&nrf_buffer[nrf_len], chunk_buf, chunk_len);
nrf_len += chunk_len;
cli_ok(cli, "%u %u", (unsigned)chunk_len, (unsigned)nrf_len);
} else if (0 == strcmp(phase, "end")) {
if (cli_arg_count(cli) != 1) {
cli_error_arg_count(cli);
goto cleanup;
}
if (!nrf_update_in_progress) {
cli_error(cli, CLI_ERROR, "Update not started. Use 'begin' first.");
goto cleanup;
}
if (nrf_len == 0) {
cli_error(cli, CLI_ERROR, "No data received");
goto cleanup;
}
// Hand off to your firmware update routine
if (!nrf_update(nrf_buffer, nrf_len)) {
cli_error(cli, CLI_ERROR, "Update failed");
goto cleanup;
}
// Clear state so next begin is required
nrf_len = 0;
nrf_update_in_progress = false;
cli_trace(cli, "Update successful");
cli_ok(cli, "");
} else {
cli_error(cli, CLI_ERROR, "Unknown phase '%s' (begin|chunk|end)", phase);
}
return;
cleanup:
nrf_update_in_progress = false;
}
// clang-format off
PRODTEST_CLI_CMD(
@ -85,4 +186,11 @@ PRODTEST_CLI_CMD(
.args = ""
);
PRODTEST_CLI_CMD(
.name = "nrf-update",
.func = prodtest_nrf_update,
.info = "Update nRF firmware",
.args = "<phase> <hex-data>"
);
#endif

76
core/tools/nrf_update.py Normal file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
import time
from pathlib import Path
import sys
import click
import serial
def send_cmd(ser, cmd, expect_ok=True):
"""Send a line, read response, and abort on non-OK."""
ser.write((cmd + "\r\n").encode())
# Give the device a moment to process
time.sleep(0.05)
resp = ser.readline().decode(errors="ignore").strip()
# Skip comments or empty lines
while resp.startswith("#") or len(resp) == 0:
resp = ser.readline().decode(errors="ignore").strip()
click.echo(f"> {cmd}")
click.echo(f"< {resp}")
if expect_ok and not resp.startswith("OK"):
click.echo("Error from device, aborting.", err=True)
sys.exit(1)
return resp
def upload_nrf(port, bin_path, chunk_size):
# Read binary file
data = Path(bin_path).read_bytes()
total = len(data)
click.echo(f"Read {total} bytes from {bin_path!r}")
# Open USB-VCP port
ser = serial.Serial(port, timeout=2)
time.sleep(0.1)
ser.reset_input_buffer()
ser.reset_output_buffer()
# 1) Begin transfer
send_cmd(ser, "nrf-update begin")
# 2) Stream chunks
offset = 0
while offset < total:
chunk = data[offset:offset + chunk_size]
hexstr = chunk.hex()
send_cmd(ser, f"nrf-update chunk {hexstr}")
offset += len(chunk)
pct = offset * 100 // total
click.echo(f" Uploaded {offset}/{total} bytes ({pct}%)")
# 3) Finish transfer
send_cmd(ser, "nrf-update end")
click.echo("nRF update complete.")
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.argument("port", metavar="<serial-port>")
@click.argument("binary", metavar="<nrf-binary>", type=click.Path(exists=True, dir_okay=False))
@click.option("--chunk-size", "-c", default=512, show_default=True,
help="Max bytes per chunk")
def main(port, binary, chunk_size):
"""
Upload an nRF firmware image via USB-VCP CLI.
<serial-port> e.g. /dev/ttyUSB0 or COM3
<nrf-binary> path to the .bin file
"""
try:
upload_nrf(port, binary, chunk_size)
except serial.SerialException as e:
click.echo(f"Serial error: {e}", err=True)
sys.exit(1)
except KeyboardInterrupt:
click.echo("Interrupted by user.", err=True)
sys.exit(1)
if __name__ == "__main__":
main()