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:
parent
9f2dec6169
commit
a3c1f197ce
@ -67,6 +67,7 @@ SOURCE_MOD_CRYPTO += [
|
|||||||
'vendor/trezor-crypto/bignum.c',
|
'vendor/trezor-crypto/bignum.c',
|
||||||
'vendor/trezor-crypto/blake256.c',
|
'vendor/trezor-crypto/blake256.c',
|
||||||
'vendor/trezor-crypto/blake2b.c',
|
'vendor/trezor-crypto/blake2b.c',
|
||||||
|
'vendor/trezor-crypto/blake2s.c',
|
||||||
'vendor/trezor-crypto/buffer.c',
|
'vendor/trezor-crypto/buffer.c',
|
||||||
'vendor/trezor-crypto/chacha_drbg.c',
|
'vendor/trezor-crypto/chacha_drbg.c',
|
||||||
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
|
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
|
||||||
@ -124,6 +125,7 @@ SOURCE_MOD += [
|
|||||||
'embed/gfx/gfx_draw.c',
|
'embed/gfx/gfx_draw.c',
|
||||||
'embed/gfx/terminal.c',
|
'embed/gfx/terminal.c',
|
||||||
'embed/io/display/display_utils.c',
|
'embed/io/display/display_utils.c',
|
||||||
|
'embed/util/bl_check/bl_check.c',
|
||||||
'embed/util/image/image.c',
|
'embed/util/image/image.c',
|
||||||
'embed/util/rsod/rsod.c',
|
'embed/util/rsod/rsod.c',
|
||||||
'embed/util/scm_revision/scm_revision.c',
|
'embed/util/scm_revision/scm_revision.c',
|
||||||
@ -206,6 +208,7 @@ ALLPATHS = [
|
|||||||
'embed/models',
|
'embed/models',
|
||||||
'embed/gfx/inc',
|
'embed/gfx/inc',
|
||||||
'embed/sys/bsp/inc',
|
'embed/sys/bsp/inc',
|
||||||
|
'embed/util/bl_check/inc',
|
||||||
'embed/util/image/inc',
|
'embed/util/image/inc',
|
||||||
'embed/util/rsod/inc',
|
'embed/util/rsod/inc',
|
||||||
'embed/util/scm_revision/inc',
|
'embed/util/scm_revision/inc',
|
||||||
|
@ -58,6 +58,7 @@ SOURCE_MOD_CRYPTO += [
|
|||||||
'vendor/trezor-crypto/bignum.c',
|
'vendor/trezor-crypto/bignum.c',
|
||||||
'vendor/trezor-crypto/blake256.c',
|
'vendor/trezor-crypto/blake256.c',
|
||||||
'vendor/trezor-crypto/blake2b.c',
|
'vendor/trezor-crypto/blake2b.c',
|
||||||
|
'vendor/trezor-crypto/blake2s.c',
|
||||||
'vendor/trezor-crypto/buffer.c',
|
'vendor/trezor-crypto/buffer.c',
|
||||||
'vendor/trezor-crypto/chacha_drbg.c',
|
'vendor/trezor-crypto/chacha_drbg.c',
|
||||||
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
|
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
|
||||||
@ -115,6 +116,7 @@ SOURCE_MOD += [
|
|||||||
'embed/gfx/gfx_draw.c',
|
'embed/gfx/gfx_draw.c',
|
||||||
'embed/gfx/terminal.c',
|
'embed/gfx/terminal.c',
|
||||||
'embed/io/display/display_utils.c',
|
'embed/io/display/display_utils.c',
|
||||||
|
'embed/util/bl_check/bl_check.c',
|
||||||
'embed/util/image/image.c',
|
'embed/util/image/image.c',
|
||||||
'embed/util/rsod/rsod.c',
|
'embed/util/rsod/rsod.c',
|
||||||
'embed/util/scm_revision/scm_revision.c',
|
'embed/util/scm_revision/scm_revision.c',
|
||||||
@ -194,6 +196,7 @@ ALLPATHS = ['embed/rust',
|
|||||||
'embed/models',
|
'embed/models',
|
||||||
'embed/projects/unix',
|
'embed/projects/unix',
|
||||||
'embed/gfx/inc',
|
'embed/gfx/inc',
|
||||||
|
'embed/util/bl_check/inc',
|
||||||
'embed/util/image/inc',
|
'embed/util/image/inc',
|
||||||
'embed/util/rsod/inc',
|
'embed/util/rsod/inc',
|
||||||
'embed/util/scm_revision/inc',
|
'embed/util/scm_revision/inc',
|
||||||
|
1
core/embed/projects/prodtest/.changelog.d/5227.added
Normal file
1
core/embed/projects/prodtest/.changelog.d/5227.added
Normal file
@ -0,0 +1 @@
|
|||||||
|
Added command for updating bootloader.
|
@ -115,7 +115,7 @@ OK
|
|||||||
|
|
||||||
|
|
||||||
### reboot-to-bootloader
|
### reboot-to-bootloader
|
||||||
This command initiates device reboot to bootlaoder.
|
This command initiates device reboot to bootloader.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
@ -134,7 +134,7 @@ OK 0.2.6
|
|||||||
```
|
```
|
||||||
|
|
||||||
### bootloader-version
|
### 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:
|
Example:
|
||||||
```
|
```
|
||||||
@ -142,6 +142,10 @@ bootloader-version
|
|||||||
OK 2.1.7
|
OK 2.1.7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### bootloader-update
|
||||||
|
Updates the bootloader to the supplied binary file.
|
||||||
|
|
||||||
|
|
||||||
### ble-adv-start
|
### ble-adv-start
|
||||||
Starts BLE advertising. Accepts one parameter, advertising name. The command returns `OK` if the operation is successful.
|
Starts BLE advertising. Accepts one parameter, advertising name. The command returns `OK` if the operation is successful.
|
||||||
|
|
||||||
|
@ -28,9 +28,6 @@ static void prodtest_boardloader_version(cli_t* cli) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cli_trace(cli, "Parsing boardloader capabilities...");
|
|
||||||
parse_boardloader_capabilities();
|
|
||||||
|
|
||||||
boardloader_version_t v;
|
boardloader_version_t v;
|
||||||
get_boardloader_version(&v);
|
get_boardloader_version(&v);
|
||||||
cli_ok(cli, "%d.%d.%d", v.version_major, v.version_minor, v.version_patch);
|
cli_ok(cli, "%d.%d.%d", v.version_major, v.version_minor, v.version_patch);
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <trezor_model.h>
|
||||||
#include <trezor_rtl.h>
|
#include <trezor_rtl.h>
|
||||||
|
|
||||||
#include <rtl/cli.h>
|
#include <rtl/cli.h>
|
||||||
#include <sys/mpu.h>
|
#include <sys/mpu.h>
|
||||||
|
#include <util/bl_check.h>
|
||||||
|
#include <util/board_capabilities.h>
|
||||||
#include <util/image.h>
|
#include <util/image.h>
|
||||||
|
|
||||||
static void prodtest_bootloader_version(cli_t *cli) {
|
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);
|
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
|
// clang-format off
|
||||||
|
|
||||||
PRODTEST_CLI_CMD(
|
PRODTEST_CLI_CMD(
|
||||||
@ -59,3 +143,13 @@ PRODTEST_CLI_CMD(
|
|||||||
.info = "Retrieve the bootloader version",
|
.info = "Retrieve the bootloader version",
|
||||||
.args = ""
|
.args = ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#ifndef TREZOR_MODEL_T2T1
|
||||||
|
PRODTEST_CLI_CMD(
|
||||||
|
.name = "bootloader-update",
|
||||||
|
.func = prodtest_bootloader_update,
|
||||||
|
.info = "Update bootloader",
|
||||||
|
.args = "<phase> <hex-data>"
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <rtl/cli.h>
|
#include <rtl/cli.h>
|
||||||
#include <sys/system.h>
|
#include <sys/system.h>
|
||||||
#include <sys/systick.h>
|
#include <sys/systick.h>
|
||||||
|
#include <util/board_capabilities.h>
|
||||||
#include <util/flash_otp.h>
|
#include <util/flash_otp.h>
|
||||||
#include <util/rsod.h>
|
#include <util/rsod.h>
|
||||||
#include <util/unit_properties.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; }
|
void prodtest_disable_rgbled_control(void) { g_rgbled_control_disabled = true; }
|
||||||
|
|
||||||
static void drivers_init(void) {
|
static void drivers_init(void) {
|
||||||
|
parse_boardloader_capabilities();
|
||||||
#ifdef USE_RTC
|
#ifdef USE_RTC
|
||||||
rtc_init();
|
rtc_init();
|
||||||
#endif
|
#endif
|
||||||
|
@ -5,6 +5,7 @@ MEMORY {
|
|||||||
|
|
||||||
MAIN_RAM (wal) : ORIGIN = MAIN_RAM_START, LENGTH = MAIN_RAM_SIZE
|
MAIN_RAM (wal) : ORIGIN = MAIN_RAM_START, LENGTH = MAIN_RAM_SIZE
|
||||||
AUX1_RAM (wal) : ORIGIN = AUX1_RAM_START, LENGTH = AUX1_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
|
BOOT_ARGS (wal) : ORIGIN = BOOTARGS_START, LENGTH = BOOTARGS_SIZE
|
||||||
FB1_RAM (wal) : ORIGIN = FB1_RAM_START, LENGTH = FB1_RAM_SIZE
|
FB1_RAM (wal) : ORIGIN = FB1_RAM_START, LENGTH = FB1_RAM_SIZE
|
||||||
FB2_RAM (wal) : ORIGIN = FB2_RAM_START, LENGTH = FB2_RAM_SIZE
|
FB2_RAM (wal) : ORIGIN = FB2_RAM_START, LENGTH = FB2_RAM_SIZE
|
||||||
@ -77,7 +78,7 @@ SECTIONS {
|
|||||||
. = ALIGN(4);
|
. = ALIGN(4);
|
||||||
*(.no_dma_buffers*);
|
*(.no_dma_buffers*);
|
||||||
. = ALIGN(4);
|
. = ALIGN(4);
|
||||||
} >AUX1_RAM
|
} >AUX2_RAM
|
||||||
|
|
||||||
.stack : ALIGN(8) {
|
.stack : ALIGN(8) {
|
||||||
. = 16K; /* Overflow causes UsageFault */
|
. = 16K; /* Overflow causes UsageFault */
|
||||||
|
88
core/tools/bld_update.py
Normal file
88
core/tools/bld_update.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user