Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions core/embed/projects/prodtest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,25 @@ OK

Runs a Tropic stress test that repeatedly calls `lt_init()`, `lt_session_start()`, `lt_mac_and_destroy()`, `lt_ecc_key_generate()` and `lt_random_value_get()` to test that Tropic doesn't enter alarm mode.

### tropic-benchmark

Measures the actual average duration of individual Tropic operations over 25 iterations and prints the results.

Example:
```
tropic-benchmark
# session_start() 204 ms
# mac_and_destroy() 28 ms
# r_mem_data_write() 37 ms
# r_mem_data_read() 33 ms
# r_mem_data_erase() 4 ms
# mcounter_init() 9 ms
# mcounter_get() 4 ms
# mcounter_update() 4 ms
# random_value_get() 10 ms
OK
```
Comment on lines +1474 to +1486
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the new fenced example block.

This block currently violates markdownlint MD040 and can fail docs linting.

Suggested fix
-```
+```text
 tropic-benchmark
 # session_start()    204 ms
 # mac_and_destroy()  28 ms
 # r_mem_data_write() 37 ms
 # r_mem_data_read()  33 ms
 # r_mem_data_erase() 4 ms
 # mcounter_init()    9 ms
 # mcounter_get()     4 ms
 # mcounter_update()  4 ms
 # random_value_get() 10 ms
 OK
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 1474-1474: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/embed/projects/prodtest/README.md` around lines 1474 - 1486, The fenced
code block showing the tropic-benchmark output in README.md violates
markdownlint MD040 because it lacks a language; update the opening fence from
``` to ```text (or another appropriate language such as `console`) so the block
explicitly declares a language and resolves the lint error; locate the block
containing the "tropic-benchmark" output in
core/embed/projects/prodtest/README.md and change its fence accordingly.


### wpc-info
Retrieves detailed information from the wireless power receiver, including chip identification, firmware version, configuration settings, and error status.

Expand Down
262 changes: 227 additions & 35 deletions core/embed/projects/prodtest/cmd/prodtest_tropic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1529,9 +1529,6 @@ static void prodtest_tropic_keyfido_read(cli_t* cli) {
}

static void prodtest_tropic_update_fw(cli_t* cli) {
#define FW_APP_UPDATE_BANK TR01_FW_BANK_FW1
#define FW_SPECT_UPDATE_BANK TR01_FW_BANK_SPECT1

if (cli_arg_count(cli) > 0) {
cli_error_arg_count(cli);
return;
Expand All @@ -1549,7 +1546,7 @@ static void prodtest_tropic_update_fw(cli_t* cli) {
chip_id.silicon_rev[1], chip_id.silicon_rev[2],
chip_id.silicon_rev[3]);

#ifdef ABAB
#ifdef LT_SILICON_REV_ABAB
if (strncmp((char*)chip_id.silicon_rev, "ABAB", 4) != 0) {
cli_error(cli, CLI_ERROR, "Wrong tropic chip silicon revision");
return;
Expand All @@ -1570,20 +1567,11 @@ static void prodtest_tropic_update_fw(cli_t* cli) {

cli_trace(cli, "Chip is executing bootloader");

cli_trace(cli, "Updating RISC-V FW");
ret = lt_do_mutable_fw_update(h, fw_CPU, sizeof(fw_CPU), FW_APP_UPDATE_BANK);
if (ret != LT_OK) {
cli_error(cli, CLI_ERROR, "RISC-V FW update failed, ret=%s",
lt_ret_verbose(ret));
goto cleanup;
}

cli_trace(cli, "Updating SPECT FW");
ret = lt_do_mutable_fw_update(h, fw_SPECT, sizeof(fw_SPECT),
FW_SPECT_UPDATE_BANK);
cli_trace(cli, "Updating RISC-V and SPECT FW");
ret = lt_do_mutable_fw_update(h, fw_CPU, sizeof(fw_CPU), fw_SPECT,
sizeof(fw_SPECT));
if (ret != LT_OK) {
cli_error(cli, CLI_ERROR, "SPECT FW update failed, ret=%s",
lt_ret_verbose(ret));
cli_error(cli, CLI_ERROR, "FW update failed, ret=%s", lt_ret_verbose(ret));
goto cleanup;
}

Expand Down Expand Up @@ -1632,6 +1620,27 @@ static void prodtest_tropic_update_fw(cli_t* cli) {
tropic_deinit();
}

static bool find_pairing_key(cli_t* cli, lt_pkey_index_t* pairing_key_index) {
*pairing_key_index = -1;

for (lt_pkey_index_t i = TROPIC_FACTORY_PAIRING_KEY_SLOT;
i <= TROPIC_PRIVILEGED_PAIRING_KEY_SLOT; i++) {
lt_ret_t res = tropic_custom_session_start(cli, i);
if (res == LT_OK) {
*pairing_key_index = i;
return true;
}
if (res != LT_L2_HSK_ERR) {
cli_trace(
cli,
"`tropic_custom_session_start()` for key %d failed with error '%s'",
i, lt_ret_verbose(res));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return false;
}

static void prodtest_tropic_stress_test(cli_t* cli) {
if (cli_arg_count(cli) > 6) {
cli_error_arg_count(cli);
Expand Down Expand Up @@ -1706,24 +1715,7 @@ static void prodtest_tropic_stress_test(cli_t* cli) {
lt_ret_t res = LT_FAIL;
lt_pkey_index_t pairing_key_index = -1;

// Find an available pairing key
for (lt_pkey_index_t i = TROPIC_FACTORY_PAIRING_KEY_SLOT;
i <= TROPIC_PRIVILEGED_PAIRING_KEY_SLOT; i++) {
res = tropic_custom_session_start(cli, i);
if (res == LT_OK) {
pairing_key_index = i;
break;
}
if (res != LT_L2_HSK_ERR) {
cli_error(
cli, CLI_ERROR,
"`tropic_custom_session_start() for key %d failed with error '%s'", i,
lt_ret_verbose(res));
return;
}
}

if (pairing_key_index == -1) {
if (!find_pairing_key(cli, &pairing_key_index)) {
cli_error(cli, CLI_ERROR, "No pairing key is available");
return;
}
Expand Down Expand Up @@ -1814,6 +1806,199 @@ static void prodtest_tropic_stress_test(cli_t* cli) {
cli_ok(cli, "");
}

static void prodtest_tropic_benchmark(cli_t* cli) {
if (cli_arg_count(cli) != 0) {
cli_error_arg_count(cli);
return;
}

g_tropic_handshake_state = TROPIC_HANDSHAKE_STATE_0;

lt_ret_t res = LT_FAIL;
lt_pkey_index_t pairing_key_index = -1;

if (!find_pairing_key(cli, &pairing_key_index)) {
cli_error(cli, CLI_ERROR, "No pairing key is available");
return;
}

lt_handle_t* h = tropic_get_handle();
uint32_t start_ms = 0;

const int iterations = 25;
const uint16_t timing_data_slot = TR01_R_MEM_DATA_SLOT_MAX;
const lt_mcounter_index_t timing_mcounter_slot = TR01_MCOUNTER_INDEX_15;

// Ensure data slot is empty before the first iteration
res = lt_r_mem_data_erase(h, timing_data_slot);
if (res != LT_OK) {
cli_error(cli, CLI_ERROR, "`lt_r_mem_data_erase()` failed with error '%s'",
lt_ret_verbose(res));
return;
}

uint32_t total_session_start_ms = 0;
uint32_t total_mac_and_destroy_ms = 0;
uint32_t total_r_mem_data_write_ms = 0;
uint32_t total_r_mem_data_read_ms = 0;
uint32_t total_r_mem_data_erase_ms = 0;
uint32_t total_mcounter_init_ms = 0;
uint32_t total_mcounter_get_ms = 0;
uint32_t total_mcounter_update_ms = 0;
uint32_t total_random_value_get_ms = 0;

for (int i = 0; i < iterations; i++) {
// Measure `tropic_custom_session_start()`
{
res = tropic_session_invalidate();
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`tropic_session_invalidate()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
start_ms = systick_ms();
res = tropic_custom_session_start(cli, pairing_key_index);
total_session_start_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`tropic_custom_session_start()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_mac_and_destroy()`
{
uint8_t buffer[TROPIC_MAC_AND_DESTROY_SIZE] = {0};
rng_fill_buffer(buffer, sizeof(buffer));
start_ms = systick_ms();
res = lt_mac_and_destroy(
h, TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED, buffer, buffer);
total_mac_and_destroy_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_mac_and_destroy()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

uint8_t data[320] = {0};

// Measure `lt_r_mem_data_write()` (320 bytes)
{
rng_fill_buffer(data, sizeof(data));
start_ms = systick_ms();
res = lt_r_mem_data_write(h, timing_data_slot, data, sizeof(data));
total_r_mem_data_write_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_r_mem_data_write()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_r_mem_data_read()` (320 bytes)
{
uint16_t read_size = 0;
start_ms = systick_ms();
res = lt_r_mem_data_read(h, timing_data_slot, data, sizeof(data),
&read_size);
total_r_mem_data_read_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_r_mem_data_read()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_r_mem_data_erase()`
{
start_ms = systick_ms();
res = lt_r_mem_data_erase(h, timing_data_slot);
total_r_mem_data_erase_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_r_mem_data_erase()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_mcounter_init()`
{
start_ms = systick_ms();
res = lt_mcounter_init(h, timing_mcounter_slot, 1);
total_mcounter_init_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR, "`lt_mcounter_init()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_mcounter_get()`
{
uint32_t counter_value = 0;
start_ms = systick_ms();
res = lt_mcounter_get(h, timing_mcounter_slot, &counter_value);
total_mcounter_get_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR, "`lt_mcounter_get()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_mcounter_update()`
{
start_ms = systick_ms();
res = lt_mcounter_update(h, timing_mcounter_slot);
total_mcounter_update_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_mcounter_update()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}

// Measure `lt_random_value_get()` (32 bytes)
{
uint8_t buffer[32] = {0};
start_ms = systick_ms();
res = lt_random_value_get(h, buffer, sizeof(buffer));
total_random_value_get_ms += systick_ms() - start_ms;
if (res != LT_OK) {
cli_error(cli, CLI_ERROR,
"`lt_random_value_get()` failed with error '%s'",
lt_ret_verbose(res));
return;
}
}
}

#define CLI_TRACE_AVERAGE(name, total) \
cli_trace(cli, name " %lu ms", (((total) + iterations / 2) / iterations))

CLI_TRACE_AVERAGE("session_start() ", total_session_start_ms);
CLI_TRACE_AVERAGE("mac_and_destroy() ", total_mac_and_destroy_ms);
CLI_TRACE_AVERAGE("r_mem_data_write()", total_r_mem_data_write_ms);
CLI_TRACE_AVERAGE("r_mem_data_read() ", total_r_mem_data_read_ms);
CLI_TRACE_AVERAGE("r_mem_data_erase()", total_r_mem_data_erase_ms);
CLI_TRACE_AVERAGE("mcounter_init() ", total_mcounter_init_ms);
CLI_TRACE_AVERAGE("mcounter_get() ", total_mcounter_get_ms);
CLI_TRACE_AVERAGE("mcounter_update() ", total_mcounter_update_ms);
CLI_TRACE_AVERAGE("random_value_get()", total_random_value_get_ms);

#undef CLI_TRACE_AVERAGE

cli_ok(cli, "");
}

static bool privileged_session_start(cli_t* cli) {
g_tropic_handshake_state = TROPIC_HANDSHAKE_STATE_0;

Expand Down Expand Up @@ -2174,6 +2359,13 @@ PRODTEST_CLI_CMD(
.args = "[<init-iterations> <start-session-iterations> <mac-and-destroy-slot-count> <mac-and-destroy-per-slot-iterations> <signing-iterations> <rng-iterations>]"
);

PRODTEST_CLI_CMD(
.name = "tropic-benchmark",
.func = prodtest_tropic_benchmark,
.info = "Measure actual duration of Tropic operations",
.args = ""
);

PRODTEST_CLI_CMD(
.name = "tropic-set-sensors",
.func = prodtest_tropic_set_sensors,
Expand Down
18 changes: 9 additions & 9 deletions core/embed/sec/tropic/tropic.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
bool tropic_session_start(void);

static bool is_retryable(lt_ret_t ret) {
return ret == LT_L1_CHIP_ALARM_MODE || ret == LT_L1_SPI_ERROR ||
return ret == LT_L1_CHIP_ALARM_MODE || ret == LT_HAL_ERROR ||
ret == LT_L2_IN_CRC_ERR || ret == LT_L2_CRC_ERR;
}

Expand Down Expand Up @@ -362,7 +362,7 @@ bool tropic_session_start(void) {

void tropic_session_start_time(uint32_t *time_ms) {
if (!g_tropic_driver.session_started) {
*time_ms += 210;
*time_ms += 204;
}
}

Expand Down Expand Up @@ -581,7 +581,7 @@ bool tropic_random_buffer(void *buffer, size_t length) {

void tropic_random_buffer_time(uint32_t *time_ms) {
// Assuming the data size is 32 bytes
*time_ms += 50;
*time_ms += 10;
}

#ifdef USE_STORAGE
Expand All @@ -607,23 +607,23 @@ static uint16_t get_kek_masks_slot(tropic_driver_t *drv) {
: TROPIC_KEK_MASKS_PRIVILEGED_SLOT;
}

static void lt_mac_and_destroy_time(uint32_t *time_ms) { *time_ms += 51; }
static void lt_mac_and_destroy_time(uint32_t *time_ms) { *time_ms += 28; }

static void lt_r_mem_data_read_time(uint32_t *time_ms) {
// Assuming the data size is 320 bytes
*time_ms += 100;
*time_ms += 33;
}

static void lt_r_mem_data_write_time(uint32_t *time_ms) {
// Assuming the data size is 320 bytes
*time_ms += 77;
*time_ms += 37;
}

static void lt_r_mem_data_erase_time(uint32_t *time_ms) { *time_ms += 55; }
static void lt_r_mem_data_erase_time(uint32_t *time_ms) { *time_ms += 4; }

static void lt_mcounter_get_time(uint32_t *time_ms) { *time_ms += 51; }
static void lt_mcounter_get_time(uint32_t *time_ms) { *time_ms += 4; }

static void lt_mcounter_update_time(uint32_t *time_ms) { *time_ms += 51; }
static void lt_mcounter_update_time(uint32_t *time_ms) { *time_ms += 4; }

lt_ret_t lt_r_mem_data_erase_write(lt_handle_t *h, const uint16_t udata_slot,
uint8_t *data, const uint16_t size) {
Expand Down
Loading
Loading