feat(core/bootloader): enable debuglink in bootloader
What changed, and why it matters
This commit adds a debug-only feature to the Trezor bootloader that lets automated testing tools talk to the bootloader over a special USB debug channel. It is controlled by a build-time flag (DEBUGLINK=1) and is not enabled in normal production firmware. The feature can read screen contents, simulate button presses and touch events, and record the screen. It does not appear to be a vulnerability by itself, but it is a powerful debugging interface that must remain disabled in production devices.
Verify that DEBUGLINK cannot be enabled in production or signed release builds, and that the debug USB interface is not exposed on retail devices. Review the build pipeline and signing process to ensure QA/debug bootloaders are not shipped to end users. No immediate patch is needed if the flag is properly restricted.
Security signals we found
New USB debug interface added to bootloader
Host can read bootloader screen state and layout tokens
Host can inject button, touch, and swipe events into bootloader workflow
Host can start/stop screen recording in bootloader
Feature is gated by DEBUGLINK build flag and not enabled by default
Normal USB start/stop is bypassed when DEBUGLINK is enabled
Evidence from the diff
The commit introduces a debuglink interface in the bootloader, gated by the DEBUGLINK build flag. It adds a second USB interface (SYSHANDLE_USB_DEBUG), protobuf message definitions for DebugLinkGetState/DebugLinkState/DebugLinkDecision/DebugLinkRecordScreen, and handlers that allow a host to query the current layout, inject button/touch events, and start/stop screen recording. The Rust UI layer calls debuglink_notify_layout_change() after rendering. The normal USB interface skips usb_start/usb_stop when DEBUGLINK is defined, apparently to let the debug interface manage USB startup. The feature is intended for QA/testing builds (BOOTLOADER_QA path sets PRODUCTION=0), not for production firmware.
Changed components
Trezor Core bootloaderbootloader USB wire/debug interfacesbootloader workflow and protobuf layerRust bootloader UI event loopInspect captured patch +1077 / −9
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index e9cc84b7..603148ea 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -7,6 +7,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T2T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1'
PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1'
+DEBUGLINK = ARGUMENTS.get('DEBUGLINK', '0') == '1'
HW_REVISION = ARGUMENTS.get('HW_REVISION', None)
UI_PERFORMANCE_OVERLAY = ARGUMENTS.get('UI_PERFORMANCE_OVERLAY', '0') == '1'
DBG_CONSOLE = ARGUMENTS.get('DBG_CONSOLE', '')
@@ -45,6 +46,10 @@ SOURCE_HAL = []
PATH_HAL = []
RUST_UI_FEATURES = []
+if DEBUGLINK:
+ FEATURES_WANTED.append("usb_iface_debug")
+ CPPDEFINES_MOD.append("DEBUGLINK")
+
if DBG_CONSOLE != "":
FEATURES_WANTED += ["dbg_console"]
@@ -164,6 +169,14 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/version_check.c',
]
+if DEBUGLINK:
+ SOURCE_BOOTLOADER += [
+ 'embed/projects/bootloader/workflow/debuglink.c',
+ 'embed/projects/bootloader/wire/debug_iface_usb.c',
+ 'embed/projects/bootloader/protob/protob_debug.c',
+ 'embed/projects/bootloader/protob/pb/messages-debug.pb.c',
+ ]
+
if "boot_ucb" in FEATURES_AVAILABLE:
env.Replace(
HEADERTOOL='headertool_pq',
@@ -255,6 +268,11 @@ cmake_gen = env.Command(
#
features = ['bootloader',] + FEATURES_AVAILABLE + RUST_UI_FEATURES
+if DEBUGLINK:
+ features.append('debuglink')
+ features.append('ui_debug')
+ features.append('ui_debug_overlay')
+
if UI_PERFORMANCE_OVERLAY:
features.append('ui_performance_overlay')
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index 7a1466aa..88a14d4b 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -7,6 +7,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T2T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1'
PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1'
+DEBUGLINK = ARGUMENTS.get('DEBUGLINK', '0') == '1'
HW_REVISION = 'emulator'
DBG_CONSOLE = ARGUMENTS.get('DBG_CONSOLE', '')
@@ -47,6 +48,10 @@ SOURCE_MOD = []
SOURCE_MOD_CRYPTO = []
RUST_UI_FEATURES = []
+if DEBUGLINK:
+ FEATURES_WANTED.append("usb_iface_debug")
+ CPPDEFINES_MOD.append("DEBUGLINK")
+
# modtrezorcrypto
CCFLAGS_MOD += '-Wno-sequence-point '
CPPPATH_MOD += [
@@ -137,6 +142,14 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/emulator.c',
]
+if DEBUGLINK:
+ SOURCE_BOOTLOADER += [
+ 'embed/projects/bootloader/workflow/debuglink.c',
+ 'embed/projects/bootloader/wire/debug_iface_usb.c',
+ 'embed/projects/bootloader/protob/protob_debug.c',
+ 'embed/projects/bootloader/protob/pb/messages-debug.pb.c',
+ ]
+
SOURCE_UNIX = [
'embed/projects/unix/profile.c',
]
@@ -235,6 +248,12 @@ cmake_gen = env.Command(
#
env.get("ENV")["RUST_TARGET"] = os.popen("rustc -vV | sed -n 's/host: //p'").read().strip()
features = ['bootloader',] + FEATURES_AVAILABLE + RUST_UI_FEATURES
+
+if DEBUGLINK:
+ features.append('debuglink')
+ features.append('ui_debug')
+ features.append('ui_debug_overlay')
+
if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1':
profile = 'dev'
else:
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index a81c981e..ea95aae9 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -98,6 +98,10 @@
#include "wire/wire_iface_usb.h"
#include "workflow/workflow.h"
+#ifdef DEBUGLINK
+#include "workflow/debuglink.h"
+#endif
+
#ifdef TREZOR_EMULATOR
#include "SDL.h"
#include "emulator.h"
@@ -333,6 +337,10 @@ static void drivers_init(secbool manufacturing_mode,
// increase BLE speed for sake of upload speed
ble_set_high_speed(true);
#endif
+
+#ifdef DEBUGLINK
+ debuglink_init();
+#endif
}
static void drivers_deinit(void) {
@@ -347,6 +355,10 @@ static void drivers_deinit(void) {
ble_deinit();
#endif
#endif
+#ifdef DEBUGLINK
+ debuglink_deinit();
+#endif
+
display_deinit(DISPLAY_JUMP_BEHAVIOR);
#ifdef USE_POWER_MANAGER
pm_deinit();
diff --git a/core/embed/projects/bootloader/protob/pb/Makefile b/core/embed/projects/bootloader/protob/pb/Makefile
index 576276b4..056629fc 100644
--- a/core/embed/projects/bootloader/protob/pb/Makefile
+++ b/core/embed/projects/bootloader/protob/pb/Makefile
@@ -1,4 +1,4 @@
-all: messages.pb.c
+all: messages.pb.c messages-debug.pb.c
%.pb.c: %.pb %.options
nanopb_generator $< -T -D . -s "mangle_names:M_FLATTEN"
diff --git a/core/embed/projects/bootloader/protob/pb/messages-debug.options b/core/embed/projects/bootloader/protob/pb/messages-debug.options
new file mode 100644
index 00000000..e69de29b
diff --git a/core/embed/projects/bootloader/protob/pb/messages-debug.pb.c b/core/embed/projects/bootloader/protob/pb/messages-debug.pb.c
new file mode 100644
index 00000000..bf8eb453
--- /dev/null
+++ b/core/embed/projects/bootloader/protob/pb/messages-debug.pb.c
@@ -0,0 +1,35 @@
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.4.9.1 */
+
+#include "messages-debug.pb.h"
+#if PB_PROTO_HEADER_VERSION != 40
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+PB_BIND(DebugLinkGetState, DebugLinkGetState, AUTO)
+
+
+PB_BIND(DebugLinkState, DebugLinkState, AUTO)
+
+
+PB_BIND(DebugLinkDecision, DebugLinkDecision, AUTO)
+
+
+PB_BIND(DebugLinkLayout, DebugLinkLayout, AUTO)
+
+
+PB_BIND(DebugLinkReseedRandom, DebugLinkReseedRandom, AUTO)
+
+
+PB_BIND(DebugLinkRecordScreen, DebugLinkRecordScreen, AUTO)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/embed/projects/bootloader/protob/pb/messages-debug.pb.h b/core/embed/projects/bootloader/protob/pb/messages-debug.pb.h
new file mode 100644
index 00000000..f3cf4885
--- /dev/null
+++ b/core/embed/projects/bootloader/protob/pb/messages-debug.pb.h
@@ -0,0 +1,282 @@
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.4.9.1 */
+
+#ifndef PB_HW_TREZOR_MESSAGES_DEBUG_MESSAGES_DEBUG_PB_H_INCLUDED
+#define PB_HW_TREZOR_MESSAGES_DEBUG_MESSAGES_DEBUG_PB_H_INCLUDED
+#include <pb.h>
+#include "messages.pb.h"
+
+#if PB_PROTO_HEADER_VERSION != 40
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+/* Enum definitions */
+typedef enum _DebugWaitType {
+ DebugWaitType_IMMEDIATE = 0,
+ DebugWaitType_NEXT_LAYOUT = 1,
+ DebugWaitType_CURRENT_LAYOUT = 2
+} DebugWaitType;
+
+typedef enum _DebugSwipeDirection {
+ DebugSwipeDirection_UP = 0,
+ DebugSwipeDirection_DOWN = 1,
+ DebugSwipeDirection_LEFT = 2,
+ DebugSwipeDirection_RIGHT = 3
+} DebugSwipeDirection;
+
+typedef enum _DebugButton {
+ DebugButton_NO = 0,
+ DebugButton_YES = 1,
+ DebugButton_INFO = 2
+} DebugButton;
+
+typedef enum _DebugPhysicalButton {
+ DebugPhysicalButton_LEFT_BTN = 0,
+ DebugPhysicalButton_MIDDLE_BTN = 1,
+ DebugPhysicalButton_RIGHT_BTN = 2
+} DebugPhysicalButton;
+
+typedef enum _DebugTouchEventType {
+ DebugTouchEventType_TOUCH_FULL_CLICK = 0,
+ DebugTouchEventType_TOUCH_START = 1,
+ DebugTouchEventType_TOUCH_END = 2
+} DebugTouchEventType;
+
+/* Struct definitions */
+typedef struct _DebugLinkGetState {
+ bool has_wait_word_list;
+ bool wait_word_list;
+ bool has_wait_word_pos;
+ bool wait_word_pos;
+ bool has_wait_layout;
+ DebugWaitType wait_layout;
+ bool has_return_empty_state;
+ bool return_empty_state;
+} DebugLinkGetState;
+
+typedef struct _DebugLinkState {
+ pb_callback_t layout;
+ pb_callback_t tokens;
+} DebugLinkState;
+
+typedef struct _DebugLinkDecision {
+ bool has_button;
+ DebugButton button;
+ bool has_swipe;
+ DebugSwipeDirection swipe;
+ pb_callback_t input;
+ bool has_x;
+ uint32_t x;
+ bool has_y;
+ uint32_t y;
+ bool has_wait;
+ bool wait;
+ bool has_hold_ms;
+ uint32_t hold_ms;
+ bool has_physical_button;
+ DebugPhysicalButton physical_button;
+ bool has_touch_event_type;
+ DebugTouchEventType touch_event_type;
+} DebugLinkDecision;
+
+typedef struct _DebugLinkLayout {
+ pb_callback_t tokens;
+} DebugLinkLayout;
+
+typedef struct _DebugLinkReseedRandom {
+ bool has_value;
+ uint32_t value;
+} DebugLinkReseedRandom;
+
+typedef struct _DebugLinkRecordScreen {
+ pb_callback_t target_directory;
+ bool has_refresh_index;
+ uint32_t refresh_index;
+} DebugLinkRecordScreen;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Helper constants for enums */
+#define _DebugWaitType_MIN DebugWaitType_IMMEDIATE
+#define _DebugWaitType_MAX DebugWaitType_CURRENT_LAYOUT
+#define _DebugWaitType_ARRAYSIZE ((DebugWaitType)(DebugWaitType_CURRENT_LAYOUT+1))
+
+#define _DebugSwipeDirection_MIN DebugSwipeDirection_UP
+#define _DebugSwipeDirection_MAX DebugSwipeDirection_RIGHT
+#define _DebugSwipeDirection_ARRAYSIZE ((DebugSwipeDirection)(DebugSwipeDirection_RIGHT+1))
+
+#define _DebugButton_MIN DebugButton_NO
+#define _DebugButton_MAX DebugButton_INFO
+#define _DebugButton_ARRAYSIZE ((DebugButton)(DebugButton_INFO+1))
+
+#define _DebugPhysicalButton_MIN DebugPhysicalButton_LEFT_BTN
+#define _DebugPhysicalButton_MAX DebugPhysicalButton_RIGHT_BTN
+#define _DebugPhysicalButton_ARRAYSIZE ((DebugPhysicalButton)(DebugPhysicalButton_RIGHT_BTN+1))
+
+#define _DebugTouchEventType_MIN DebugTouchEventType_TOUCH_FULL_CLICK
+#define _DebugTouchEventType_MAX DebugTouchEventType_TOUCH_END
+#define _DebugTouchEventType_ARRAYSIZE ((DebugTouchEventType)(DebugTouchEventType_TOUCH_END+1))
+
+#define DebugLinkGetState_wait_layout_ENUMTYPE DebugWaitType
+
+
+#define DebugLinkDecision_button_ENUMTYPE DebugButton
+#define DebugLinkDecision_swipe_ENUMTYPE DebugSwipeDirection
+#define DebugLinkDecision_physical_button_ENUMTYPE DebugPhysicalButton
+#define DebugLinkDecision_touch_event_type_ENUMTYPE DebugTouchEventType
+
+
+
+
+
+/* Initializer values for message structs */
+#define DebugLinkGetState_init_default {false, 0, false, 0, false, DebugWaitType_IMMEDIATE, false, false}
+#define DebugLinkState_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define DebugLinkDecision_init_default {false, _DebugButton_MIN, false, _DebugSwipeDirection_MIN, {{NULL}, NULL}, false, 0, false, 0, false, 0, false, 0, false, _DebugPhysicalButton_MIN, false, _DebugTouchEventType_MIN}
+#define DebugLinkLayout_init_default {{{NULL}, NULL}}
+#define DebugLinkReseedRandom_init_default {false, 0}
+#define DebugLinkRecordScreen_init_default {{{NULL}, NULL}, false, 0u}
+#define DebugLinkGetState_init_zero {false, 0, false, 0, false, _DebugWaitType_MIN, false, 0}
+#define DebugLinkState_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define DebugLinkDecision_init_zero {false, _DebugButton_MIN, false, _DebugSwipeDirection_MIN, {{NULL}, NULL}, false, 0, false, 0, false, 0, false, 0, false, _DebugPhysicalButton_MIN, false, _DebugTouchEventType_MIN}
+#define DebugLinkLayout_init_zero {{{NULL}, NULL}}
+#define DebugLinkReseedRandom_init_zero {false, 0}
+#define DebugLinkRecordScreen_init_zero {{{NULL}, NULL}, false, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define DebugLinkGetState_wait_word_list_tag 1
+#define DebugLinkGetState_wait_word_pos_tag 2
+#define DebugLinkGetState_wait_layout_tag 3
+#define DebugLinkGetState_return_empty_state_tag 4
+#define DebugLinkState_layout_tag 1
+#define DebugLinkState_tokens_tag 13
+#define DebugLinkDecision_button_tag 1
+#define DebugLinkDecision_swipe_tag 2
+#define DebugLinkDecision_input_tag 3
+#define DebugLinkDecision_x_tag 4
+#define DebugLinkDecision_y_tag 5
+#define DebugLinkDecision_wait_tag 6
+#define DebugLinkDecision_hold_ms_tag 7
+#define DebugLinkDecision_physical_button_tag 8
+#define DebugLinkDecision_touch_event_type_tag 9
+#define DebugLinkLayout_tokens_tag 1
+#define DebugLinkReseedRandom_value_tag 1
+#define DebugLinkRecordScreen_target_directory_tag 1
+#define DebugLinkRecordScreen_refresh_index_tag 2
+
+/* Struct field encoding specification for nanopb */
+#define DebugLinkGetState_FIELDLIST(X, a) \
+X(a, STATIC, OPTIONAL, BOOL, wait_word_list, 1) \
+X(a, STATIC, OPTIONAL, BOOL, wait_word_pos, 2) \
+X(a, STATIC, OPTIONAL, UENUM, wait_layout, 3) \
+X(a, STATIC, OPTIONAL, BOOL, return_empty_state, 4)
+#define DebugLinkGetState_CALLBACK NULL
+#define DebugLinkGetState_DEFAULT (const pb_byte_t*)"\x20\x00\x00"
+
+#define DebugLinkState_FIELDLIST(X, a) \
+X(a, CALLBACK, OPTIONAL, BYTES, layout, 1) \
+X(a, CALLBACK, REPEATED, STRING, tokens, 13)
+#define DebugLinkState_CALLBACK pb_default_field_callback
+#define DebugLinkState_DEFAULT NULL
+
+#define DebugLinkDecision_FIELDLIST(X, a) \
+X(a, STATIC, OPTIONAL, UENUM, button, 1) \
+X(a, STATIC, OPTIONAL, UENUM, swipe, 2) \
+X(a, CALLBACK, OPTIONAL, STRING, input, 3) \
+X(a, STATIC, OPTIONAL, UINT32, x, 4) \
+X(a, STATIC, OPTIONAL, UINT32, y, 5) \
+X(a, STATIC, OPTIONAL, BOOL, wait, 6) \
+X(a, STATIC, OPTIONAL, UINT32, hold_ms, 7) \
+X(a, STATIC, OPTIONAL, UENUM, physical_button, 8) \
+X(a, STATIC, OPTIONAL, UENUM, touch_event_type, 9)
+#define DebugLinkDecision_CALLBACK pb_default_field_callback
+#define DebugLinkDecision_DEFAULT NULL
+
+#define DebugLinkLayout_FIELDLIST(X, a) \
+X(a, CALLBACK, REPEATED, STRING, tokens, 1)
+#define DebugLinkLayout_CALLBACK pb_default_field_callback
+#define DebugLinkLayout_DEFAULT NULL
+
+#define DebugLinkReseedRandom_FIELDLIST(X, a) \
+X(a, STATIC, OPTIONAL, UINT32, value, 1)
+#define DebugLinkReseedRandom_CALLBACK NULL
+#define DebugLinkReseedRandom_DEFAULT NULL
+
+#define DebugLinkRecordScreen_FIELDLIST(X, a) \
+X(a, CALLBACK, OPTIONAL, STRING, target_directory, 1) \
+X(a, STATIC, OPTIONAL, UINT32, refresh_index, 2)
+#define DebugLinkRecordScreen_CALLBACK pb_default_field_callback
+#define DebugLinkRecordScreen_DEFAULT (const pb_byte_t*)"\x10\x00\x00"
+
+extern const pb_msgdesc_t DebugLinkGetState_msg;
+extern const pb_msgdesc_t DebugLinkState_msg;
+extern const pb_msgdesc_t DebugLinkDecision_msg;
+extern const pb_msgdesc_t DebugLinkLayout_msg;
+extern const pb_msgdesc_t DebugLinkReseedRandom_msg;
+extern const pb_msgdesc_t DebugLinkRecordScreen_msg;
+
+/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
+#define DebugLinkGetState_fields &DebugLinkGetState_msg
+#define DebugLinkState_fields &DebugLinkState_msg
+#define DebugLinkDecision_fields &DebugLinkDecision_msg
+#define DebugLinkLayout_fields &DebugLinkLayout_msg
+#define DebugLinkReseedRandom_fields &DebugLinkReseedRandom_msg
+#define DebugLinkRecordScreen_fields &DebugLinkRecordScreen_msg
+
+/* Maximum encoded size of messages (where known) */
+/* DebugLinkState_size depends on runtime parameters */
+/* DebugLinkDecision_size depends on runtime parameters */
+/* DebugLinkLayout_size depends on runtime parameters */
+/* DebugLinkRecordScreen_size depends on runtime parameters */
+#define DebugLinkGetState_size 8
+#define DebugLinkReseedRandom_size 6
+#define HW_TREZOR_MESSAGES_DEBUG_MESSAGES_DEBUG_PB_H_MAX_SIZE DebugLinkGetState_size
+
+/* Mapping from canonical names (mangle_names or overridden package name) */
+#define hw_trezor_messages_debug_DebugLinkGetState DebugLinkGetState
+#define hw_trezor_messages_debug_DebugLinkGetState_DebugWaitType DebugWaitType
+#define hw_trezor_messages_debug_DebugLinkState DebugLinkState
+#define hw_trezor_messages_debug_DebugLinkDecision DebugLinkDecision
+#define hw_trezor_messages_debug_DebugLinkDecision_DebugSwipeDirection DebugSwipeDirection
+#define hw_trezor_messages_debug_DebugLinkDecision_DebugButton DebugButton
+#define hw_trezor_messages_debug_DebugLinkDecision_DebugPhysicalButton DebugPhysicalButton
+#define hw_trezor_messages_debug_DebugLinkDecision_DebugTouchEventType DebugTouchEventType
+#define hw_trezor_messages_debug_DebugLinkLayout DebugLinkLayout
+#define hw_trezor_messages_debug_DebugLinkReseedRandom DebugLinkReseedRandom
+#define hw_trezor_messages_debug_DebugLinkRecordScreen DebugLinkRecordScreen
+#define _hw_trezor_messages_debug_DebugLinkGetState_DebugWaitType_MIN _DebugWaitType_MIN
+#define _hw_trezor_messages_debug_DebugLinkGetState_DebugWaitType_MAX _DebugWaitType_MAX
+#define _hw_trezor_messages_debug_DebugLinkGetState_DebugWaitType_ARRAYSIZE _DebugWaitType_ARRAYSIZE
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugSwipeDirection_MIN _DebugSwipeDirection_MIN
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugSwipeDirection_MAX _DebugSwipeDirection_MAX
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugSwipeDirection_ARRAYSIZE _DebugSwipeDirection_ARRAYSIZE
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugButton_MIN _DebugButton_MIN
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugButton_MAX _DebugButton_MAX
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugButton_ARRAYSIZE _DebugButton_ARRAYSIZE
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugPhysicalButton_MIN _DebugPhysicalButton_MIN
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugPhysicalButton_MAX _DebugPhysicalButton_MAX
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugPhysicalButton_ARRAYSIZE _DebugPhysicalButton_ARRAYSIZE
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugTouchEventType_MIN _DebugTouchEventType_MIN
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugTouchEventType_MAX _DebugTouchEventType_MAX
+#define _hw_trezor_messages_debug_DebugLinkDecision_DebugTouchEventType_ARRAYSIZE _DebugTouchEventType_ARRAYSIZE
+#define hw_trezor_messages_debug_DebugLinkGetState_init_default DebugLinkGetState_init_default
+#define hw_trezor_messages_debug_DebugLinkState_init_default DebugLinkState_init_default
+#define hw_trezor_messages_debug_DebugLinkDecision_init_default DebugLinkDecision_init_default
+#define hw_trezor_messages_debug_DebugLinkLayout_init_default DebugLinkLayout_init_default
+#define hw_trezor_messages_debug_DebugLinkReseedRandom_init_default DebugLinkReseedRandom_init_default
+#define hw_trezor_messages_debug_DebugLinkRecordScreen_init_default DebugLinkRecordScreen_init_default
+#define hw_trezor_messages_debug_DebugLinkGetState_init_zero DebugLinkGetState_init_zero
+#define hw_trezor_messages_debug_DebugLinkState_init_zero DebugLinkState_init_zero
+#define hw_trezor_messages_debug_DebugLinkDecision_init_zero DebugLinkDecision_init_zero
+#define hw_trezor_messages_debug_DebugLinkLayout_init_zero DebugLinkLayout_init_zero
+#define hw_trezor_messages_debug_DebugLinkReseedRandom_init_zero DebugLinkReseedRandom_init_zero
+#define hw_trezor_messages_debug_DebugLinkRecordScreen_init_zero DebugLinkRecordScreen_init_zero
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/core/embed/projects/bootloader/protob/pb/messages-debug.proto b/core/embed/projects/bootloader/protob/pb/messages-debug.proto
new file mode 100644
index 00000000..1f0d4ee6
--- /dev/null
+++ b/core/embed/projects/bootloader/protob/pb/messages-debug.proto
@@ -0,0 +1,132 @@
+syntax = "proto2";
+package hw.trezor.messages.debug;
+
+// Sugar for easier handling in Java
+option java_package = "com.satoshilabs.trezor.lib.protobuf";
+option java_outer_classname = "TrezorMessageDebug";
+
+import "messages.proto";
+
+/**
+ * Request: Host asks for device state
+ * @start
+ * @next DebugLinkState
+ */
+message DebugLinkGetState {
+ /// Wait behavior of the call.
+ enum DebugWaitType {
+ /// Respond immediately. If no layout is currently displayed, the layout
+ /// response will be empty.
+ IMMEDIATE = 0;
+ /// Wait for next layout. If a layout is displayed, waits for it to change.
+ /// If no layout is displayed, waits for one to come up.
+ NEXT_LAYOUT = 1;
+ /// Return current layout. If no layout is currently displayed, waits for
+ /// one to come up.
+ CURRENT_LAYOUT = 2;
+ }
+
+ // Trezor T < 2.6.0 only - wait until mnemonic words are shown
+ optional bool wait_word_list = 1 [deprecated = true];
+ // Trezor T < 2.6.0 only - wait until reset word position is requested
+ optional bool wait_word_pos = 2 [deprecated = true];
+ // trezor-core only - wait until current layout changes
+ // changed in 2.6.4: multiple wait types instead of true/false.
+ optional DebugWaitType wait_layout = 3 [default = IMMEDIATE];
+ // Responds immediately with an empty `DebugLinkState` (used for client-side synchronization).
+ optional bool return_empty_state = 4 [default = false];
+}
+
+/**
+ * Response: Device current state
+ * @end
+ */
+message DebugLinkState {
+ optional bytes layout = 1; // raw buffer of display
+ repeated string tokens = 13; // current layout represented as a list of string tokens
+}
+
+/**
+ * Request: "Press" the button on the device
+ * @start
+ * @next DebugLinkLayout
+ */
+message DebugLinkDecision {
+ optional DebugButton button = 1; // button press
+ optional DebugSwipeDirection swipe = 2; // swipe direction
+ optional string input = 3; // keyboard input
+ /**
+ * Structure representing swipe direction
+ */
+ enum DebugSwipeDirection {
+ UP = 0;
+ DOWN = 1;
+ LEFT = 2;
+ RIGHT = 3;
+ }
+
+ /**
+ * Structure representing button presses
+ */
+ enum DebugButton {
+ NO = 0;
+ YES = 1;
+ INFO = 2;
+ }
+
+ /**
+ * Structure representing button presses of UI Caesar
+ */
+ // TODO: probably delete the middle_btn as it is not a physical one
+ enum DebugPhysicalButton {
+ LEFT_BTN = 0;
+ MIDDLE_BTN = 1;
+ RIGHT_BTN = 2;
+ }
+
+ optional uint32 x = 4; // touch X coordinate
+ optional uint32 y = 5; // touch Y coordinate
+ optional bool wait = 6 [deprecated = true]; // wait for layout change
+ optional uint32 hold_ms = 7; // touch hold duration
+ optional DebugPhysicalButton physical_button = 8; // physical button press
+
+ /**
+ * Explicit touch event type, used to separate TOUCH_START and TOUCH_END
+ * If not set, defaults to full click behavior (TOUCH_START + optional hold + TOUCH_END)
+ */
+ enum DebugTouchEventType {
+ TOUCH_FULL_CLICK = 0;
+ TOUCH_START = 1;
+ TOUCH_END = 2;
+ }
+
+ optional DebugTouchEventType touch_event_type = 9;
+}
+
+/**
+ * Response: Device text layout as a list of tokens as returned by Rust
+ * @end
+ */
+message DebugLinkLayout {
+ option deprecated = true;
+ repeated string tokens = 1;
+}
+
+/**
+ * Request: Re-seed RNG with given value
+ * @start
+ * @next Success
+ */
+message DebugLinkReseedRandom {
+ optional uint32 value = 1;
+}
+
+/**
+ * Request: Start or stop recording screen changes into given target directory
+ * @start
+ * @next Success
+ */
+message DebugLinkRecordScreen {
+ optional string target_directory = 1; // empty or missing to stop recording
+ optional uint32 refresh_index = 2 [default = 0]; // which index to give the screenshots (after emulator restarts)
+}
diff --git a/core/embed/projects/bootloader/protob/pb/messages.pb.h b/core/embed/projects/bootloader/protob/pb/messages.pb.h
index c7b1bd9e..35bf9bc9 100644
--- a/core/embed/projects/bootloader/protob/pb/messages.pb.h
+++ b/core/embed/projects/bootloader/protob/pb/messages.pb.h
@@ -23,7 +23,11 @@ typedef enum _MessageType {
MessageType_MessageType_ButtonRequest = 26,
MessageType_MessageType_ButtonAck = 27,
MessageType_MessageType_GetFeatures = 55,
- MessageType_MessageType_UnlockBootloader = 96
+ MessageType_MessageType_UnlockBootloader = 96,
+ MessageType_MessageType_DebugLinkDecision = 100,
+ MessageType_MessageType_DebugLinkGetState = 101,
+ MessageType_MessageType_DebugLinkState = 102,
+ MessageType_MessageType_DebugLinkRecordScreen = 9003
} MessageType;
typedef enum _FailureType {
@@ -160,8 +164,8 @@ extern "C" {
/* Helper constants for enums */
#define _MessageType_MIN MessageType_MessageType_Initialize
-#define _MessageType_MAX MessageType_MessageType_UnlockBootloader
-#define _MessageType_ARRAYSIZE ((MessageType)(MessageType_MessageType_UnlockBootloader+1))
+#define _MessageType_MAX MessageType_MessageType_DebugLinkRecordScreen
+#define _MessageType_ARRAYSIZE ((MessageType)(MessageType_MessageType_DebugLinkRecordScreen+1))
#define _FailureType_MIN FailureType_Failure_UnexpectedMessage
#define _FailureType_MAX FailureType_Failure_Busy
diff --git a/core/embed/projects/bootloader/protob/pb/messages.proto b/core/embed/projects/bootloader/protob/pb/messages.proto
index b73bfa86..74543588 100644
--- a/core/embed/projects/bootloader/protob/pb/messages.proto
+++ b/core/embed/projects/bootloader/protob/pb/messages.proto
@@ -17,6 +17,12 @@ enum MessageType {
MessageType_ButtonAck = 27;
MessageType_GetFeatures = 55;
MessageType_UnlockBootloader = 96;
+
+ // Debug
+ MessageType_DebugLinkDecision = 100;
+ MessageType_DebugLinkGetState = 101;
+ MessageType_DebugLinkState = 102;
+ MessageType_DebugLinkRecordScreen = 9003;
}
/**
diff --git a/core/embed/projects/bootloader/protob/protob_debug.c b/core/embed/projects/bootloader/protob/protob_debug.c
new file mode 100644
index 00000000..f657eb9c
--- /dev/null
+++ b/core/embed/projects/bootloader/protob/protob_debug.c
@@ -0,0 +1,98 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <pb.h>
+#include <pb_decode.h>
+
+#include "protob_common.h"
+
+#include "pb/messages-debug.pb.h"
+#include "pb/messages.pb.h"
+#include "protob.h"
+
+typedef struct {
+ void (*cb)(size_t len, void *ctx);
+ void *ctx;
+ uint8_t *buffer;
+ size_t buffer_size;
+} payload_ctx_t;
+
+static bool read_payload(pb_istream_t *stream, const pb_field_t *field,
+ void **arg) {
+ payload_ctx_t *payload_ctx = (payload_ctx_t *)*arg;
+
+ if (stream->bytes_left > payload_ctx->buffer_size) {
+ return false;
+ }
+
+#define BUFSIZE 32768
+
+ uint32_t bytes_written = 0;
+
+ while (stream->bytes_left) {
+ uint32_t received =
+ stream->bytes_left > BUFSIZE ? BUFSIZE : stream->bytes_left;
+
+ // read data
+ if (!pb_read(stream, (pb_byte_t *)(payload_ctx->buffer + bytes_written),
+ (received))) {
+ return false;
+ }
+ bytes_written += received;
+ }
+
+ return true;
+}
+
+secbool recv_msg_debug_link_get_state(protob_io_t *iface,
+ DebugLinkGetState *msg) {
+ MSG_RECV_INIT(DebugLinkGetState);
+ secbool result = MSG_RECV(DebugLinkGetState);
+ memcpy(msg, &msg_recv, sizeof(DebugLinkGetState));
+ return result;
+}
+
+secbool recv_msg_debug_link_decision(protob_io_t *iface,
+ DebugLinkDecision *msg) {
+ MSG_RECV_INIT(DebugLinkDecision);
+ secbool result = MSG_RECV(DebugLinkDecision);
+ memcpy(msg, &msg_recv, sizeof(DebugLinkDecision));
+ return result;
+}
+
+secbool recv_msg_debug_link_screen_record(protob_io_t *iface,
+ DebugLinkRecordScreen *msg,
+ uint8_t *buffer, size_t buffer_size) {
+ payload_ctx_t payload_ctx = {.buffer = buffer, .buffer_size = buffer_size};
+
+ MSG_RECV_INIT(DebugLinkRecordScreen);
+ MSG_RECV_CALLBACK(target_directory, read_payload, &payload_ctx);
+ secbool result = MSG_RECV(DebugLinkRecordScreen);
+ memcpy(msg, &msg_recv, sizeof(DebugLinkRecordScreen));
+ return result;
+}
+
+secbool send_msg_debug_link_state(protob_io_t *iface) {
+ MSG_SEND_INIT(DebugLinkState);
+
+ return MSG_SEND(DebugLinkState);
+}
diff --git a/core/embed/projects/bootloader/protob/protob_debug.h b/core/embed/projects/bootloader/protob/protob_debug.h
new file mode 100644
index 00000000..bc8ffd6c
--- /dev/null
+++ b/core/embed/projects/bootloader/protob/protob_debug.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include "pb/messages-debug.pb.h"
+#include "protob/protob.h"
+
+secbool recv_msg_debug_link_decision(protob_io_t *iface,
+ DebugLinkDecision *msg);
+
+secbool recv_msg_debug_link_screen_record(protob_io_t *iface,
+ DebugLinkRecordScreen *msg,
+ uint8_t *buffer, size_t buffer_size);
+
+secbool recv_msg_debug_link_get_state(protob_io_t *iface,
+ DebugLinkGetState *msg);
+
+secbool send_msg_debug_link_state(protob_io_t *iface);
diff --git a/core/embed/projects/bootloader/wire/debug_iface_usb.c b/core/embed/projects/bootloader/wire/debug_iface_usb.c
new file mode 100644
index 00000000..9b64b9c1
--- /dev/null
+++ b/core/embed/projects/bootloader/wire/debug_iface_usb.c
@@ -0,0 +1,101 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include "wire_iface_usb.h"
+
+#include <io/usb.h>
+#include <sys/sysevent.h>
+
+#define USB_TIMEOUT 500
+#define USB_PACKET_SIZE 64
+
+_Static_assert(USB_PACKET_SIZE <= MAX_PACKET_SIZE, "USB_PACKET_SIZE too large");
+
+static wire_iface_t g_usb_debug_iface = {0};
+
+static bool usb_write(uint8_t* data, size_t size) {
+ if (size != USB_PACKET_SIZE) {
+ return false;
+ }
+
+ ssize_t r =
+ syshandle_write_blocking(SYSHANDLE_USB_DEBUG, data, size, USB_TIMEOUT);
+
+ return r == size;
+}
+
+static int usb_read(uint8_t* buffer, size_t buffer_size) {
+ if (buffer_size != USB_PACKET_SIZE) {
+ return -1;
+ }
+
+ ssize_t r = syshandle_read_blocking(SYSHANDLE_USB_DEBUG, buffer, buffer_size,
+ USB_TIMEOUT);
+
+ return r;
+}
+
+static void usb_error(void) {
+ error_shutdown_ex("USB ERROR",
+ "Error reading from USB. Try different USB cable.", NULL);
+}
+
+wire_iface_t* usb_debug_iface_init(void) {
+ wire_iface_t* iface = &g_usb_debug_iface;
+
+ if (iface->initialized) {
+ return iface;
+ }
+
+ usb_start_params_t params = {
+ .serial_number = "000000000000000000000000",
+ .usb21_landing = false,
+ };
+
+ usb_start(¶ms);
+
+ memset(iface, 0, sizeof(wire_iface_t));
+
+ iface->poll_iface_id = SYSHANDLE_USB_DEBUG;
+ iface->tx_packet_size = USB_PACKET_SIZE;
+ iface->rx_packet_size = USB_PACKET_SIZE;
+ iface->write = &usb_write;
+ iface->read = &usb_read;
+ iface->error = &usb_error;
+ iface->initialized = true;
+ iface->wireless = false;
+
+ return iface;
+}
+
+void usb_debug_iface_deinit(void) {
+ wire_iface_t* iface = &g_usb_debug_iface;
+
+ if (!iface->initialized) {
+ return;
+ }
+
+ memset(iface, 0, sizeof(wire_iface_t));
+ usb_stop();
+}
+
+wire_iface_t* usb_debug_iface_get(void) { return &g_usb_debug_iface; }
diff --git a/core/embed/projects/bootloader/wire/debug_iface_usb.h b/core/embed/projects/bootloader/wire/debug_iface_usb.h
new file mode 100644
index 00000000..920e372b
--- /dev/null
+++ b/core/embed/projects/bootloader/wire/debug_iface_usb.h
@@ -0,0 +1,28 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "codec_v1.h"
+
+wire_iface_t *usb_debug_iface_init(void);
+
+wire_iface_t *usb_debug_iface_get(void);
+
+void usb_debug_iface_deinit(void);
diff --git a/core/embed/projects/bootloader/wire/wire_iface_usb.c b/core/embed/projects/bootloader/wire/wire_iface_usb.c
index 106a128c..713b6189 100644
--- a/core/embed/projects/bootloader/wire/wire_iface_usb.c
+++ b/core/embed/projects/bootloader/wire/wire_iface_usb.c
@@ -66,12 +66,14 @@ wire_iface_t* usb_iface_init(secbool usb21_landing) {
return iface;
}
+#ifndef DEBUGLINK
usb_start_params_t params = {
.serial_number = "000000000000000000000000",
.usb21_landing = usb21_landing,
};
usb_start(¶ms);
+#endif
memset(iface, 0, sizeof(wire_iface_t));
@@ -95,7 +97,10 @@ void usb_iface_deinit(void) {
}
memset(iface, 0, sizeof(wire_iface_t));
+
+#ifndef DEBUGLINK
usb_stop();
+#endif
}
wire_iface_t* usb_iface_get(void) {
diff --git a/core/embed/projects/bootloader/workflow/debuglink.c b/core/embed/projects/bootloader/workflow/debuglink.c
new file mode 100644
index 00000000..12856240
--- /dev/null
+++ b/core/embed/projects/bootloader/workflow/debuglink.c
@@ -0,0 +1,204 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#ifdef USE_TOUCH
+#include <io/../../touch_debug.h>
+#endif
+
+#ifdef USE_BUTTON
+#include <io/../../button_debug.h>
+#endif
+
+#include "debuglink.h"
+
+#include <io/display_utils.h>
+
+#include "fw_check.h"
+#include "protob/protob.h"
+#include "protob/protob_debug.h"
+#include "wire/debug_iface_usb.h"
+#include "workflow.h"
+
+static protob_io_t g_debug_io;
+
+static bool layout_ready = false;
+static bool query_pending = false;
+
+void debuglink_init(void) {
+ wire_iface_t *wire = usb_debug_iface_init();
+
+ protob_init(&g_debug_io, wire);
+}
+
+void debuglink_deinit(void) { usb_debug_iface_deinit(); }
+
+static void debuglink_process_get_state(protob_io_t *io) {
+ DebugLinkGetState msg_recv;
+
+ recv_msg_debug_link_get_state(io, &msg_recv);
+
+ if (!msg_recv.has_wait_layout) {
+ // defaults to current layout behavior
+ if (!layout_ready) {
+ query_pending = true;
+ } else {
+ send_msg_debug_link_state(io);
+ query_pending = false;
+ }
+ return;
+ }
+
+ switch (msg_recv.wait_layout) {
+ case DebugWaitType_IMMEDIATE:
+ send_msg_debug_link_state(io);
+ query_pending = false;
+ break;
+ case DebugWaitType_NEXT_LAYOUT:
+ layout_ready = false;
+ query_pending = true;
+ break;
+ default:
+ case DebugWaitType_CURRENT_LAYOUT:
+ if (!layout_ready) {
+ query_pending = true;
+ } else {
+ send_msg_debug_link_state(io);
+ query_pending = false;
+ }
+ break;
+ }
+}
+
+static void debuglink_process_decision(protob_io_t *io) {
+ DebugLinkDecision msg_recv;
+ recv_msg_debug_link_decision(io, &msg_recv);
+
+#ifdef USE_TOUCH
+ if (msg_recv.has_x && msg_recv.has_y) {
+ if (!msg_recv.has_touch_event_type) {
+ touch_debug_click(msg_recv.x, msg_recv.y);
+ } else {
+ switch (msg_recv.touch_event_type) {
+ case DebugTouchEventType_TOUCH_START:
+ touch_debug_start(msg_recv.x, msg_recv.y);
+ break;
+ case DebugTouchEventType_TOUCH_END:
+ touch_debug_end(msg_recv.x, msg_recv.y);
+ break;
+ default:
+ case DebugTouchEventType_TOUCH_FULL_CLICK:
+ touch_debug_click(msg_recv.x, msg_recv.y);
+ break;
+ }
+ }
+ }
+#endif
+#ifdef USE_BUTTON
+ if (msg_recv.has_physical_button) {
+ switch (msg_recv.physical_button) {
+ case DebugPhysicalButton_LEFT_BTN: {
+ button_debug_click(BTN_LEFT);
+ break;
+ }
+ case DebugPhysicalButton_RIGHT_BTN: {
+ button_debug_click(BTN_RIGHT);
+ break;
+ }
+ case DebugPhysicalButton_MIDDLE_BTN: {
+ button_debug_press(BTN_LEFT);
+ button_debug_press(BTN_RIGHT);
+ button_debug_release(BTN_LEFT);
+ button_debug_release(BTN_RIGHT);
+ break;
+ }
+ }
+ }
+#endif
+}
+
+static debuglink_result_t debuglink_process_record_screen(protob_io_t *io) {
+ DebugLinkRecordScreen msg;
+
+ debuglink_result_t res = DEBUGLINK_RESULT_NONE;
+
+ char buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+
+ recv_msg_debug_link_screen_record(io, &msg, (uint8_t *)buffer,
+ sizeof(buffer));
+
+ size_t dir_len = strnlen(buffer, sizeof(buffer));
+
+ if (dir_len > 0) {
+ display_record_start((uint8_t *)buffer, dir_len, 0);
+ res = DEBUGLINK_RESULT_REPAINT;
+ } else {
+ display_record_stop();
+ }
+
+ send_msg_success(&g_debug_io, "success");
+
+ return res;
+}
+
+debuglink_result_t debuglink_process(void) {
+ fw_info_t fw = {0};
+ fw_check(&fw);
+
+ debuglink_result_t res = DEBUGLINK_RESULT_NONE;
+
+ uint16_t msg_id = 0;
+ if (protob_get_msg_header(&g_debug_io, &msg_id) == sectrue) {
+ switch (msg_id) {
+ case MessageType_MessageType_Initialize:
+ workflow_initialize(&g_debug_io, &fw);
+ break;
+ case MessageType_MessageType_GetFeatures:
+ workflow_get_features(&g_debug_io, &fw);
+ break;
+ case MessageType_MessageType_Ping:
+ workflow_ping(&g_debug_io);
+ break;
+ case MessageType_MessageType_DebugLinkGetState:
+ debuglink_process_get_state(&g_debug_io);
+ break;
+ case MessageType_MessageType_DebugLinkDecision:
+ debuglink_process_decision(&g_debug_io);
+ break;
+ case MessageType_MessageType_DebugLinkRecordScreen:
+ res = debuglink_process_record_screen(&g_debug_io);
+ break;
+ default:
+ send_msg_success(&g_debug_io, "success");
+ break;
+ }
+ }
+
+ return res;
+}
+
+void debuglink_notify_layout_change(void) {
+ layout_ready = true;
+ if (query_pending) {
+ send_msg_debug_link_state(&g_debug_io);
+ query_pending = false;
+ }
+}
diff --git a/core/embed/projects/bootloader/workflow/debuglink.h b/core/embed/projects/bootloader/workflow/debuglink.h
new file mode 100644
index 00000000..3acb7b00
--- /dev/null
+++ b/core/embed/projects/bootloader/workflow/debuglink.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+typedef enum {
+ DEBUGLINK_RESULT_REPAINT,
+ DEBUGLINK_RESULT_NONE,
+} debuglink_result_t;
+
+void debuglink_init(void);
+
+void debuglink_deinit(void);
+
+debuglink_result_t debuglink_process(void);
+
+void debuglink_notify_layout_change(void);
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 3616a8da..9832efea 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -506,7 +506,9 @@ fn generate_trezorhal_bindings() {
// c_layout
.allowlist_type("c_layout_t")
.allowlist_function("bootloader_process_ble")
- .allowlist_function("bootloader_process_usb");
+ .allowlist_function("bootloader_process_usb")
+ .allowlist_function("debuglink_process")
+ .allowlist_function("debuglink_notify_layout_change");
// Write the bindings to a file in the OUR_DIR.
bindings
diff --git a/core/embed/rust/src/bootloader/mod.rs b/core/embed/rust/src/bootloader/mod.rs
index e0e74f3f..21ccfea0 100644
--- a/core/embed/rust/src/bootloader/mod.rs
+++ b/core/embed/rust/src/bootloader/mod.rs
@@ -29,6 +29,11 @@ use crate::{
#[cfg(all(feature = "haptic", feature = "power_manager"))]
use crate::trezorhal::haptic::{play, HapticEffect};
+#[cfg(feature = "debuglink")]
+use crate::trezorhal::bootloader::{
+ debuglink_notify_layout_change, debuglink_process, DebuglinkResult,
+};
+
use heapless::Vec;
#[cfg(feature = "power_manager")]
@@ -65,6 +70,11 @@ pub fn run(
render(frame);
ModelUI::fadein();
+ #[cfg(feature = "debuglink")]
+ {
+ debuglink_notify_layout_change();
+ }
+
#[cfg(all(feature = "power_manager", feature = "haptic"))]
let mut haptic_played = false;
#[cfg(feature = "power_manager")]
@@ -91,6 +101,9 @@ pub fn run(
#[cfg(feature = "power_manager")]
unwrap!(ifaces.push(Syshandle::PowerManager));
+ #[cfg(feature = "debuglink")]
+ unwrap!(ifaces.push(Syshandle::UsbDebug));
+
if communication {
unwrap!(ifaces.push(Syshandle::UsbWire));
@@ -122,6 +135,17 @@ pub fn run(
faded = false;
}
+ #[cfg(feature = "debuglink")]
+ if e == Event::USBDebug {
+ let res = debuglink_process();
+
+ if res == DebuglinkResult::Repaint {
+ render(frame);
+ }
+
+ continue;
+ }
+
if e == Event::USBWire {
let res = bootloader_process_usb();
if res == BootloaderWFResult::Ok {
diff --git a/core/embed/rust/src/trezorhal/bootloader.rs b/core/embed/rust/src/trezorhal/bootloader.rs
index 5a5fbf63..64aa1ea4 100644
--- a/core/embed/rust/src/trezorhal/bootloader.rs
+++ b/core/embed/rust/src/trezorhal/bootloader.rs
@@ -31,3 +31,22 @@ pub fn bootloader_process_ble() -> BootloaderWFResult {
.unwrap_or(BootloaderWFResult::Error)
}
}
+
+#[derive(PartialEq, Debug, Eq, Clone, Copy, FromPrimitive, ToPrimitive)]
+pub enum DebuglinkResult {
+ Repaint = ffi::debuglink_result_t_DEBUGLINK_RESULT_REPAINT as _,
+ None = ffi::debuglink_result_t_DEBUGLINK_RESULT_NONE as _,
+}
+
+pub fn debuglink_process() -> DebuglinkResult {
+ unsafe {
+ unwrap!(
+ DebuglinkResult::from_u32(ffi::debuglink_process() as _),
+ "Invalid debuglink result"
+ )
+ }
+}
+
+pub fn debuglink_notify_layout_change() {
+ unsafe { ffi::debuglink_notify_layout_change() }
+}
diff --git a/core/embed/rust/src/trezorhal/sysevent.rs b/core/embed/rust/src/trezorhal/sysevent.rs
index 71b914c1..73631a46 100644
--- a/core/embed/rust/src/trezorhal/sysevent.rs
+++ b/core/embed/rust/src/trezorhal/sysevent.rs
@@ -146,14 +146,14 @@ pub fn parse_event(signalled: &sysevents_t) -> Option<Event> {
}
}
- if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_USB_WIRE) != 0 {
- return Some(Event::USBWire);
- }
-
if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_USB_DEBUG) != 0 {
return Some(Event::USBDebug);
}
+ if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_USB_WIRE) != 0 {
+ return Some(Event::USBWire);
+ }
+
#[cfg(feature = "ble")]
if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_BLE_IFACE_0) != 0 {
return Some(Event::BLEIface);
diff --git a/core/embed/rust/src/ui/layout/simplified.rs b/core/embed/rust/src/ui/layout/simplified.rs
index 3c9b0d12..ea9a4f29 100644
--- a/core/embed/rust/src/ui/layout/simplified.rs
+++ b/core/embed/rust/src/ui/layout/simplified.rs
@@ -105,5 +105,11 @@ pub fn show(frame: &mut impl Component<Msg = impl ReturnToC>, fading: bool) -> u
ModelUI::fadein()
};
+ #[cfg(all(feature = "debuglink", feature = "bootloader"))]
+ {
+ use crate::trezorhal::bootloader::debuglink_notify_layout_change;
+ debuglink_notify_layout_change();
+ }
+
0
}
diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h
index 36b2a585..b7387146 100644
--- a/core/embed/rust/trezorhal.h
+++ b/core/embed/rust/trezorhal.h
@@ -56,6 +56,7 @@
#endif
#ifdef BOOTLOADER
+#include "workflow/debuglink.h"
#include "workflow/workflow_common.h"
#endif
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.