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

feat(core/prodtest): add command for bootloader update, along with script for sending the data

This commit is contained in:
tychovrahe 2025-06-18 23:11:47 +02:00 committed by TychoVrahe
parent 9f2dec6169
commit a3c1f197ce
9 changed files with 199 additions and 6 deletions

View File

@ -67,6 +67,7 @@ SOURCE_MOD_CRYPTO += [
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/blake256.c',
'vendor/trezor-crypto/blake2b.c',
'vendor/trezor-crypto/blake2s.c',
'vendor/trezor-crypto/buffer.c',
'vendor/trezor-crypto/chacha_drbg.c',
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
@ -124,6 +125,7 @@ SOURCE_MOD += [
'embed/gfx/gfx_draw.c',
'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
'embed/util/bl_check/bl_check.c',
'embed/util/image/image.c',
'embed/util/rsod/rsod.c',
'embed/util/scm_revision/scm_revision.c',
@ -206,6 +208,7 @@ ALLPATHS = [
'embed/models',
'embed/gfx/inc',
'embed/sys/bsp/inc',
'embed/util/bl_check/inc',
'embed/util/image/inc',
'embed/util/rsod/inc',
'embed/util/scm_revision/inc',

View File

@ -58,6 +58,7 @@ SOURCE_MOD_CRYPTO += [
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/blake256.c',
'vendor/trezor-crypto/blake2b.c',
'vendor/trezor-crypto/blake2s.c',
'vendor/trezor-crypto/buffer.c',
'vendor/trezor-crypto/chacha_drbg.c',
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
@ -115,6 +116,7 @@ SOURCE_MOD += [
'embed/gfx/gfx_draw.c',
'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
'embed/util/bl_check/bl_check.c',
'embed/util/image/image.c',
'embed/util/rsod/rsod.c',
'embed/util/scm_revision/scm_revision.c',
@ -194,6 +196,7 @@ ALLPATHS = ['embed/rust',
'embed/models',
'embed/projects/unix',
'embed/gfx/inc',
'embed/util/bl_check/inc',
'embed/util/image/inc',
'embed/util/rsod/inc',
'embed/util/scm_revision/inc',

View File

@ -0,0 +1 @@
Added command for updating bootloader.

View File

@ -115,7 +115,7 @@ OK
### reboot-to-bootloader
This command initiates device reboot to bootlaoder.
This command initiates device reboot to bootloader.
Example:
```
@ -134,7 +134,7 @@ OK 0.2.6
```
### bootloader-version
Retrives the version of the bootlaoder. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
Retrieves the version of the bootloader. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
Example:
```
@ -142,6 +142,10 @@ bootloader-version
OK 2.1.7
```
### bootloader-update
Updates the bootloader to the supplied binary file.
### ble-adv-start
Starts BLE advertising. Accepts one parameter, advertising name. The command returns `OK` if the operation is successful.

View File

@ -28,9 +28,6 @@ static void prodtest_boardloader_version(cli_t* cli) {
return;
}
cli_trace(cli, "Parsing boardloader capabilities...");
parse_boardloader_capabilities();
boardloader_version_t v;
get_boardloader_version(&v);
cli_ok(cli, "%d.%d.%d", v.version_major, v.version_minor, v.version_patch);

View File

@ -17,10 +17,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <trezor_model.h>
#include <trezor_rtl.h>
#include <rtl/cli.h>
#include <sys/mpu.h>
#include <util/bl_check.h>
#include <util/board_capabilities.h>
#include <util/image.h>
static void prodtest_bootloader_version(cli_t *cli) {
@ -51,6 +54,87 @@ static void prodtest_bootloader_version(cli_t *cli) {
cli_ok(cli, "%d.%d.%d", v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF);
}
#ifndef TREZOR_MODEL_T2T1
__attribute__((
section(".buf"))) static uint8_t bootloader_buffer[BOOTLOADER_MAXSIZE];
static size_t bootloader_len = 0;
static void prodtest_bootloader_update(cli_t *cli) {
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).");
}
if (0 == strcmp(phase, "begin")) {
if (cli_arg_count(cli) != 1) {
cli_error_arg_count(cli);
return;
}
// Reset our state
bootloader_len = 0;
cli_trace(cli, "Begin");
cli_ok(cli, "");
} else if (0 == strcmp(phase, "chunk")) {
if (cli_arg_count(cli) < 2) {
cli_error_arg_count(cli);
return;
}
// Receive next piece of the image
size_t chunk_len = 0;
// Temporary buffer for this chunk; tweak max if you like
uint8_t chunk_buf[1024];
if (!cli_arg_hex(cli, "hex-data", chunk_buf, sizeof(chunk_buf),
&chunk_len)) {
cli_error_arg(cli, "Expecting hex data for chunk.");
return;
}
if (bootloader_len + chunk_len > BOOTLOADER_MAXSIZE) {
cli_error(cli, CLI_ERROR, "Buffer overflow (have %u, %u more)",
(unsigned)bootloader_len, (unsigned)chunk_len);
return;
}
memcpy(&bootloader_buffer[bootloader_len], chunk_buf, chunk_len);
bootloader_len += chunk_len;
cli_ok(cli, "%u %u", (unsigned)chunk_len, (unsigned)bootloader_len);
} else if (0 == strcmp(phase, "end")) {
if (cli_arg_count(cli) != 1) {
cli_error_arg_count(cli);
return;
}
if (bootloader_len == 0) {
cli_error(cli, CLI_ERROR, "No data received");
return;
}
bl_check_replace(bootloader_buffer, bootloader_len);
// Reset state so next begin must come before chunks
bootloader_len = 0;
cli_trace(cli, "Update successful (%u bytes)", (unsigned)bootloader_len);
cli_ok(cli, "");
} else {
cli_error(cli, CLI_ERROR, "Unknown phase '%s' (begin|chunk|end)", phase);
}
}
#endif
// clang-format off
PRODTEST_CLI_CMD(
@ -59,3 +143,13 @@ PRODTEST_CLI_CMD(
.info = "Retrieve the bootloader version",
.args = ""
);
#ifndef TREZOR_MODEL_T2T1
PRODTEST_CLI_CMD(
.name = "bootloader-update",
.func = prodtest_bootloader_update,
.info = "Update bootloader",
.args = "<phase> <hex-data>"
);
#endif

View File

@ -27,6 +27,7 @@
#include <rtl/cli.h>
#include <sys/system.h>
#include <sys/systick.h>
#include <util/board_capabilities.h>
#include <util/flash_otp.h>
#include <util/rsod.h>
#include <util/unit_properties.h>
@ -200,6 +201,7 @@ static bool g_rgbled_control_disabled = false;
void prodtest_disable_rgbled_control(void) { g_rgbled_control_disabled = true; }
static void drivers_init(void) {
parse_boardloader_capabilities();
#ifdef USE_RTC
rtc_init();
#endif

View File

@ -5,6 +5,7 @@ MEMORY {
MAIN_RAM (wal) : ORIGIN = MAIN_RAM_START, LENGTH = MAIN_RAM_SIZE
AUX1_RAM (wal) : ORIGIN = AUX1_RAM_START, LENGTH = AUX1_RAM_SIZE
AUX2_RAM (wal) : ORIGIN = AUX2_RAM_START, LENGTH = AUX2_RAM_SIZE
BOOT_ARGS (wal) : ORIGIN = BOOTARGS_START, LENGTH = BOOTARGS_SIZE
FB1_RAM (wal) : ORIGIN = FB1_RAM_START, LENGTH = FB1_RAM_SIZE
FB2_RAM (wal) : ORIGIN = FB2_RAM_START, LENGTH = FB2_RAM_SIZE
@ -77,7 +78,7 @@ SECTIONS {
. = ALIGN(4);
*(.no_dma_buffers*);
. = ALIGN(4);
} >AUX1_RAM
} >AUX2_RAM
.stack : ALIGN(8) {
. = 16K; /* Overflow causes UsageFault */

88
core/tools/bld_update.py Normal file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
import time
from pathlib import Path
import sys
import zlib
import click
import serial
def _compress(data: bytes) -> bytes:
"""
Compress data with zlib at max compression, raw deflate.
"""
compressor = zlib.compressobj(level=9, wbits=-10)
return compressor.compress(data) + compressor.flush()
def send_cmd(ser, cmd, expect_ok=True):
"""Send a line, read response, and abort on CLI_ERROR."""
ser.write((cmd + "\r\n").encode())
# Give the device a moment to process
time.sleep(0.05)
resp = ser.readline().decode(errors='ignore').strip()
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_bootloader(port, bin_path, chunk_size):
# Read binary file
data = Path(bin_path).read_bytes()
orig_size = len(data)
click.echo(f"Read {orig_size} bytes from {bin_path!r}")
# Compress the data
comp_data = _compress(data)
comp_size = len(comp_data)
click.echo(f"Compressed to {comp_size} bytes ({comp_size*100//orig_size}% of original)")
# 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, "bootloader-update begin")
# 2) Stream compressed chunks
offset = 0
while offset < comp_size:
chunk = comp_data[offset:offset+chunk_size]
hexstr = chunk.hex()
send_cmd(ser, f"bootloader-update chunk {hexstr}")
offset += len(chunk)
pct = offset * 100 // comp_size
click.echo(f" Uploaded {offset}/{comp_size} bytes ({pct}%)")
# 3) Finish transfer
send_cmd(ser, "bootloader-update end")
click.echo("Bootloader upload complete.")
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.argument("port", metavar="<serial-port>")
@click.argument("binary", metavar="<bootloader-binary>", type=click.Path(exists=True, dir_okay=False))
@click.option("--chunk-size", "-c", default=512, show_default=True,
help="Max bytes per chunk (in compressed form)")
def main(port, binary, chunk_size):
"""
Upload a (compressed) bootloader image via USB-VCP CLI.
<serial-port> e.g. /dev/ttyUSB0 or COM3
<bootloader-binary> path to the .bin file
"""
try:
upload_bootloader(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()