Skip to content

Commit 936d0ff

Browse files
committed
feat(core/prodtest): add command for bootloader update, along with script for sending the data
1 parent 444361c commit 936d0ff

File tree

7 files changed

+196
-3
lines changed

7 files changed

+196
-3
lines changed

core/SConscript.prodtest

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ SOURCE_MOD_CRYPTO += [
6666
'vendor/trezor-crypto/bignum.c',
6767
'vendor/trezor-crypto/blake256.c',
6868
'vendor/trezor-crypto/blake2b.c',
69+
'vendor/trezor-crypto/blake2s.c',
6970
'vendor/trezor-crypto/buffer.c',
7071
'vendor/trezor-crypto/chacha_drbg.c',
7172
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
@@ -123,6 +124,7 @@ SOURCE_MOD += [
123124
'embed/gfx/gfx_draw.c',
124125
'embed/gfx/terminal.c',
125126
'embed/io/display/display_utils.c',
127+
'embed/util/bl_check/bl_check.c',
126128
'embed/util/image/image.c',
127129
'embed/util/rsod/rsod.c',
128130
'embed/util/scm_revision/scm_revision.c',
@@ -205,6 +207,7 @@ ALLPATHS = [
205207
'embed/models',
206208
'embed/gfx/inc',
207209
'embed/sys/bsp/inc',
210+
'embed/util/bl_check/inc',
208211
'embed/util/image/inc',
209212
'embed/util/rsod/inc',
210213
'embed/util/scm_revision/inc',

core/SConscript.prodtest_emu

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ SOURCE_MOD_CRYPTO += [
5858
'vendor/trezor-crypto/bignum.c',
5959
'vendor/trezor-crypto/blake256.c',
6060
'vendor/trezor-crypto/blake2b.c',
61+
'vendor/trezor-crypto/blake2s.c',
6162
'vendor/trezor-crypto/buffer.c',
6263
'vendor/trezor-crypto/chacha_drbg.c',
6364
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
@@ -115,6 +116,7 @@ SOURCE_MOD += [
115116
'embed/gfx/gfx_draw.c',
116117
'embed/gfx/terminal.c',
117118
'embed/io/display/display_utils.c',
119+
'embed/util/bl_check/bl_check.c',
118120
'embed/util/image/image.c',
119121
'embed/util/rsod/rsod.c',
120122
'embed/util/scm_revision/scm_revision.c',
@@ -194,6 +196,7 @@ ALLPATHS = ['embed/rust',
194196
'embed/models',
195197
'embed/projects/unix',
196198
'embed/gfx/inc',
199+
'embed/util/bl_check/inc',
197200
'embed/util/image/inc',
198201
'embed/util/rsod/inc',
199202
'embed/util/scm_revision/inc',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added command for updating bootloader.

core/embed/projects/prodtest/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ OK
115115

116116

117117
### reboot-to-bootloader
118-
This command initiates device reboot to bootlaoder.
118+
This command initiates device reboot to bootloader.
119119

120120
Example:
121121
```
@@ -134,14 +134,18 @@ OK 0.2.6
134134
```
135135

136136
### bootloader-version
137-
Retrives the version of the bootlaoder. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
137+
Retrieves the version of the bootloader. The command returns `OK` followed by the version in the format `<major>.<minor>.<patch>`.
138138

139139
Example:
140140
```
141141
bootloader-version
142142
OK 2.1.7
143143
```
144144

145+
### bootloader-update
146+
Updates the bootloader to the supplied binary file.
147+
148+
145149
### ble-adv-start
146150
Starts BLE advertising. Accepts one parameter, advertising name. The command returns `OK` if the operation is successful.
147151

core/embed/projects/prodtest/cmd/prodtest_bootloader.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
*/
1919

2020
#include <trezor_rtl.h>
21+
#include <trezor_model.h>
2122

2223
#include <rtl/cli.h>
2324
#include <sys/mpu.h>
25+
#include <util/bl_check.h>
26+
#include <util/board_capabilities.h>
2427
#include <util/image.h>
2528

2629
static void prodtest_bootloader_version(cli_t *cli) {
@@ -51,6 +54,88 @@ static void prodtest_bootloader_version(cli_t *cli) {
5154
cli_ok(cli, "%d.%d.%d", v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF);
5255
}
5356

57+
#ifndef TREZOR_MODEL_T2T1
58+
__attribute__((
59+
section(".buf"))) static uint8_t bootloader_buffer[BOOTLOADER_MAXSIZE];
60+
static size_t bootloader_len = 0;
61+
62+
static void prodtest_bootloader_update(cli_t *cli) {
63+
if (cli_arg_count(cli) < 1) {
64+
cli_error_arg_count(cli);
65+
return;
66+
}
67+
68+
const char *phase = cli_arg(cli, "phase");
69+
70+
if (phase == NULL) {
71+
cli_error_arg(cli, "Expecting phase (begin|chunk|end).");
72+
}
73+
74+
if (0 == strcmp(phase, "begin")) {
75+
if (cli_arg_count(cli) != 1) {
76+
cli_error_arg_count(cli);
77+
return;
78+
}
79+
80+
// Reset our state
81+
bootloader_len = 0;
82+
cli_ok(cli, "BOOT: begin");
83+
84+
} else if (0 == strcmp(phase, "chunk")) {
85+
if (cli_arg_count(cli) < 2) {
86+
cli_error_arg_count(cli);
87+
return;
88+
}
89+
90+
// Receive next piece of the image
91+
size_t chunk_len = 0;
92+
// Temporary buffer for this chunk; tweak max if you like
93+
uint8_t chunk_buf[1024];
94+
95+
if (!cli_arg_hex(cli, "hex-data", chunk_buf, sizeof(chunk_buf),
96+
&chunk_len)) {
97+
cli_error_arg(cli, "Expecting hex data for chunk.");
98+
return;
99+
}
100+
101+
if (bootloader_len + chunk_len > BOOTLOADER_MAXSIZE) {
102+
cli_error(cli, CLI_ERROR, "BOOT: buffer overflow (have %u, %u more)",
103+
(unsigned)bootloader_len, (unsigned)chunk_len);
104+
return;
105+
}
106+
107+
memcpy(&bootloader_buffer[bootloader_len], chunk_buf, chunk_len);
108+
bootloader_len += chunk_len;
109+
110+
cli_ok(cli, "BOOT: got %u bytes (total %u)", (unsigned)chunk_len,
111+
(unsigned)bootloader_len);
112+
113+
} else if (0 == strcmp(phase, "end")) {
114+
if (cli_arg_count(cli) != 1) {
115+
cli_error_arg_count(cli);
116+
return;
117+
}
118+
119+
if (bootloader_len == 0) {
120+
cli_error(cli, CLI_ERROR, "BOOT: no data received");
121+
return;
122+
}
123+
124+
parse_boardloader_capabilities();
125+
bl_check_replace(bootloader_buffer, bootloader_len);
126+
127+
cli_ok(cli, "BOOT: update successful (%u bytes)", (unsigned)bootloader_len);
128+
129+
// Optionally reset state so next begin must come before chunks
130+
bootloader_len = 0;
131+
132+
} else {
133+
cli_error(cli, CLI_ERROR, "BOOT: unknown phase '%s' (begin|chunk|end)",
134+
phase);
135+
}
136+
}
137+
#endif
138+
54139
// clang-format off
55140

56141
PRODTEST_CLI_CMD(
@@ -59,3 +144,13 @@ PRODTEST_CLI_CMD(
59144
.info = "Retrieve the bootloader version",
60145
.args = ""
61146
);
147+
148+
#ifndef TREZOR_MODEL_T2T1
149+
PRODTEST_CLI_CMD(
150+
.name = "bootloader-update",
151+
.func = prodtest_bootloader_update,
152+
.info = "Update bootloader",
153+
.args = "<phase> <hex-data>"
154+
);
155+
#endif
156+

core/embed/sys/linker/stm32u58/prodtest.ld

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ MEMORY {
55

66
MAIN_RAM (wal) : ORIGIN = MAIN_RAM_START, LENGTH = MAIN_RAM_SIZE
77
AUX1_RAM (wal) : ORIGIN = AUX1_RAM_START, LENGTH = AUX1_RAM_SIZE
8+
AUX2_RAM (wal) : ORIGIN = AUX2_RAM_START, LENGTH = AUX2_RAM_SIZE
89
BOOT_ARGS (wal) : ORIGIN = BOOTARGS_START, LENGTH = BOOTARGS_SIZE
910
FB1_RAM (wal) : ORIGIN = FB1_RAM_START, LENGTH = FB1_RAM_SIZE
1011
FB2_RAM (wal) : ORIGIN = FB2_RAM_START, LENGTH = FB2_RAM_SIZE
@@ -77,7 +78,7 @@ SECTIONS {
7778
. = ALIGN(4);
7879
*(.no_dma_buffers*);
7980
. = ALIGN(4);
80-
} >AUX1_RAM
81+
} >AUX2_RAM
8182

8283
.stack : ALIGN(8) {
8384
. = 16K; /* Overflow causes UsageFault */

core/tools/bld_update.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env python3
2+
import time
3+
from pathlib import Path
4+
import sys
5+
import zlib
6+
7+
import click
8+
import serial
9+
10+
def _compress(data: bytes) -> bytes:
11+
"""
12+
Compress data with zlib at max compression, raw deflate.
13+
"""
14+
compressor = zlib.compressobj(level=9, wbits=-10)
15+
return compressor.compress(data) + compressor.flush()
16+
17+
def send_cmd(ser, cmd, expect_ok=True):
18+
"""Send a line, read response, and abort on CLI_ERROR."""
19+
ser.write((cmd + "\r\n").encode())
20+
# Give the device a moment to process
21+
time.sleep(0.05)
22+
resp = ser.readline().decode(errors='ignore').strip()
23+
click.echo(f"> {cmd}")
24+
click.echo(f"< {resp}")
25+
if expect_ok and resp.startswith("CLI_ERROR"):
26+
click.echo("Error from device, aborting.", err=True)
27+
sys.exit(1)
28+
return resp
29+
30+
def upload_bootloader(port, bin_path, chunk_size):
31+
# Read binary file
32+
data = Path(bin_path).read_bytes()
33+
orig_size = len(data)
34+
click.echo(f"Read {orig_size} bytes from {bin_path!r}")
35+
36+
# Compress the data
37+
comp_data = _compress(data)
38+
comp_size = len(comp_data)
39+
click.echo(f"Compressed to {comp_size} bytes ({comp_size*100//orig_size}% of original)")
40+
41+
# Open USB-VCP port
42+
ser = serial.Serial(port, timeout=2)
43+
time.sleep(0.1)
44+
ser.reset_input_buffer()
45+
ser.reset_output_buffer()
46+
47+
# 1) Begin transfer
48+
send_cmd(ser, "bootloader-update begin")
49+
50+
# 2) Stream compressed chunks
51+
offset = 0
52+
while offset < comp_size:
53+
chunk = comp_data[offset:offset+chunk_size]
54+
hexstr = chunk.hex()
55+
send_cmd(ser, f"bootloader-update chunk {hexstr}")
56+
offset += len(chunk)
57+
pct = offset * 100 // comp_size
58+
click.echo(f" Uploaded {offset}/{comp_size} bytes ({pct}%)")
59+
60+
# 3) Finish transfer
61+
send_cmd(ser, "bootloader-update end")
62+
click.echo("Bootloader upload complete.")
63+
64+
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
65+
@click.argument("port", metavar="<serial-port>")
66+
@click.argument("binary", metavar="<bootloader-binary>", type=click.Path(exists=True, dir_okay=False))
67+
@click.option("--chunk-size", "-c", default=512, show_default=True,
68+
help="Max bytes per chunk (in compressed form)")
69+
def main(port, binary, chunk_size):
70+
"""
71+
Upload a (compressed) bootloader image via USB-VCP CLI.
72+
73+
<serial-port> e.g. /dev/ttyUSB0 or COM3
74+
<bootloader-binary> path to the .bin file
75+
"""
76+
try:
77+
upload_bootloader(port, binary, chunk_size)
78+
except serial.SerialException as e:
79+
click.echo(f"Serial error: {e}", err=True)
80+
sys.exit(1)
81+
except KeyboardInterrupt:
82+
click.echo("Interrupted by user.", err=True)
83+
sys.exit(1)
84+
85+
if __name__ == "__main__":
86+
main()

0 commit comments

Comments
 (0)