1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-22 18:19:03 +00:00

feat(core): add BLE to bootloader

This commit is contained in:
tychovrahe 2025-04-10 20:11:31 +02:00
parent c00c8487be
commit 03f736d39a
49 changed files with 2054 additions and 311 deletions

View File

@ -11,7 +11,7 @@ PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1'
HW_REVISION = ARGUMENTS.get('HW_REVISION', None)
UI_DEBUG_OVERLAY = ARGUMENTS.get('UI_DEBUG_OVERLAY', '0') == '1'
FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d"]
FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d", "ble"]
CCFLAGS_MOD = ''
CPPPATH_MOD = []
@ -105,7 +105,6 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/header.S',
'embed/projects/bootloader/bootui.c',
'embed/projects/bootloader/main.c',
'embed/projects/bootloader/poll.c',
'embed/projects/bootloader/antiglitch.c',
'embed/projects/bootloader/workflow/wf_firmware_update.c',
'embed/projects/bootloader/workflow/wf_wipe_device.c',
@ -116,8 +115,10 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/workflow/wf_empty_device.c',
'embed/projects/bootloader/workflow/wf_auto_update.c',
'embed/projects/bootloader/workflow/wf_host_control.c',
'embed/projects/bootloader/workflow/wf_ble_pairing_request.c',
'embed/projects/bootloader/wire/codec_v1.c',
'embed/projects/bootloader/wire/wire_iface_usb.c',
'embed/projects/bootloader/wire/wire_iface_ble.c',
'embed/projects/bootloader/protob/protob.c',
'embed/projects/bootloader/protob/pb/messages.pb.c',
'embed/projects/bootloader/version_check.c',

View File

@ -99,7 +99,6 @@ SOURCE_NANOPB = [
SOURCE_BOOTLOADER = [
'embed/projects/bootloader/bootui.c',
'embed/projects/bootloader/main.c',
'embed/projects/bootloader/poll.c',
'embed/projects/bootloader/antiglitch.c',
'embed/projects/bootloader/workflow/wf_firmware_update.c',
'embed/projects/bootloader/workflow/wf_wipe_device.c',
@ -184,6 +183,7 @@ ALLPATHS = ['embed/rust',
'embed/gfx/inc',
'embed/io/display/inc',
'embed/io/usb/inc',
'embed/io/poll/inc',
'embed/sec/entropy/inc',
'embed/sec/monoctr/inc',
'embed/sec/random_delays/inc',

View File

@ -0,0 +1 @@
Add cancel button to Connect to host screen.

View File

@ -24,7 +24,7 @@
#include <rtl/mini_printf.h>
#include "bootui.h"
#include "rust_ui.h"
#include "rust_ui_bootloader.h"
#include "version.h"
#define BACKLIGHT_NORMAL 150
@ -47,6 +47,8 @@ static bool initial_setup = true;
void ui_set_initial_setup(bool initial) { initial_setup = initial; }
bool ui_get_initial_setup(void) { return initial_setup; }
#if defined USE_TOUCH
#include <io/touch.h>
@ -97,10 +99,6 @@ void ui_screen_boot(const vendor_header *const vhdr,
vimg_len, wait);
}
// welcome UI
void ui_screen_welcome(void) { screen_welcome(); }
uint32_t ui_screen_intro(const vendor_header *const vhdr,
const image_header *const hdr, bool fw_ok) {
char bld_ver[32];
@ -111,12 +109,6 @@ uint32_t ui_screen_intro(const vendor_header *const vhdr,
return screen_intro(bld_ver, vhdr->vstr, vhdr->vstr_len, ver_str, fw_ok);
}
uint32_t ui_screen_menu(secbool firmware_present) {
return screen_menu(firmware_present);
}
void ui_screen_connect(void) { screen_connect(initial_setup); }
// install UI
ui_result_t ui_screen_install_confirm(const vendor_header *const vhdr,
@ -180,3 +172,9 @@ void ui_screen_install_restricted(void) { screen_install_fail(); }
void ui_fadein(void) { display_fade(0, BACKLIGHT_NORMAL, 1000); }
void ui_fadeout(void) { display_fade(BACKLIGHT_NORMAL, 0, 500); }
#ifdef USE_BLE
uint32_t ui_screen_confirm_pairing(uint32_t code) {
return screen_confirm_pairing(code, initial_setup);
}
#endif

View File

@ -34,6 +34,7 @@ typedef enum {
MENU_EXIT = 0xAABBCCDD,
MENU_REBOOT = 0x11223344,
MENU_WIPE = 0x55667788,
MENU_BLUETOOTH = 0x99AABBCC,
} menu_result_t;
// todo: use bindgen to tie this to rust
@ -42,6 +43,22 @@ typedef enum {
INTRO_HOST = 2,
} intro_result_t;
typedef enum {
WAIT_CANCEL = 1,
WAIT_PAIRING_MODE = 2,
WAIT_MENU = 3,
} wait_result_t;
typedef enum {
PAIRING_MODE_CANCEL = 1000000,
} pairing_mode_result_t;
typedef enum {
PAIRING_FINALIZATION_COMPLETED = 1,
PAIRING_FINALIZATION_CANCEL = 2,
PAIRING_FINALIZATION_FAILED = 3,
} pairing_mode_finalization_result_t;
// Displays a warning screen before jumping to the untrusted firmware
//
// Shows vendor image, vendor string and firmware version
@ -60,15 +77,9 @@ void ui_screen_boot(const vendor_header* const vhdr,
// the user presses a button, touches the display
void ui_click(void);
void ui_screen_welcome(void);
uint32_t ui_screen_intro(const vendor_header* const vhdr,
const image_header* const hdr, bool fw_ok);
uint32_t ui_screen_menu(secbool firmware_present);
void ui_screen_connect(void);
ui_result_t ui_screen_install_confirm(const vendor_header* const vhdr,
const image_header* const hdr,
secbool shold_keep_seed,
@ -89,9 +100,14 @@ void ui_screen_fail(void);
void ui_fadein(void);
void ui_fadeout(void);
void ui_set_initial_setup(bool initial);
bool ui_get_initial_setup(void);
void ui_screen_boot_stage_1(bool fading);
#ifdef USE_OPTIGA
uint32_t ui_screen_unlock_bootloader_confirm(void);
#endif
#ifdef USE_BLE
uint32_t ui_screen_confirm_pairing(uint32_t code);
#endif

View File

@ -59,10 +59,18 @@
#ifdef USE_TAMPER
#include <sys/tamper.h>
#endif
#ifdef USE_BLE
#include <io/ble.h>
#endif
#ifdef USE_BLE
#include "wire/wire_iface_ble.h"
#endif
#include "antiglitch.h"
#include "bootui.h"
#include "version_check.h"
#include "wire/wire_iface_usb.h"
#include "workflow/workflow.h"
#ifdef TREZOR_EMULATOR
@ -122,6 +130,9 @@ static void drivers_init(secbool *touch_initialized) {
#ifdef USE_RGB_LED
rgb_led_init();
#endif
#ifdef USE_BLE
ble_init();
#endif
}
static void drivers_deinit(void) {
@ -381,7 +392,7 @@ int bootloader_main(void) {
jump_reset();
if (header_present == sectrue) {
if (auto_upgrade == sectrue) {
if (auto_upgrade == sectrue && firmware_present == sectrue) {
result = workflow_auto_update(&vhdr, hdr);
} else {
result = workflow_bootloader(&vhdr, hdr, firmware_present);
@ -390,6 +401,11 @@ int bootloader_main(void) {
result = workflow_empty_device();
}
usb_iface_deinit();
#ifdef USE_BLE
ble_iface_deinit();
#endif
switch (result) {
case WF_OK_FIRMWARE_INSTALLED:
firmware_present = sectrue;

View File

@ -1,62 +0,0 @@
/*
* 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_bsp.h>
#include "poll.h"
#include <io/usb.h>
#include <sys/systick.h>
#ifdef TREZOR_EMULATOR
#include "SDL.h"
#endif
uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num,
poll_event_t* event, uint32_t timeout_ms) {
uint32_t deadline = ticks_timeout(timeout_ms);
while (!ticks_expired(deadline)) {
#ifdef TREZOR_EMULATOR
// Ensures that SDL events are processed. This prevents the emulator from
// freezing when the user interacts with the window.
SDL_PumpEvents();
#endif
for (size_t i = 0; i < ifaces_num; i++) {
uint8_t iface_num = ifaces[i] & 0xFF;
if (iface_num < IFACE_USB_MAX) {
if ((ifaces[i] & MODE_READ) == MODE_READ) {
// check if USB can read
if (sectrue == usb_webusb_can_read(iface_num)) {
event->type = EVENT_USB_CAN_READ;
return iface_num;
}
}
}
}
#ifndef TREZOR_EMULATOR
__WFI();
#endif
}
event->type = EVENT_NONE;
return 0;
}

View File

@ -26,6 +26,8 @@
#define MAX_PACKET_SIZE 256
typedef struct {
// initialized flag
bool initialized;
// identifier of the interface used for polling communication events
uint8_t poll_iface_id;
// size of TX packet

View File

@ -0,0 +1,173 @@
/*
* 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/>.
*/
#ifdef USE_BLE
#include <trezor_model.h>
#include <trezor_rtl.h>
#include "wire_iface_ble.h"
#include <io/ble.h>
#include <sys/sysevent.h>
#include <sys/systick.h>
static wire_iface_t g_ble_iface = {0};
static bool is_connected(void) {
ble_state_t state = {0};
ble_get_state(&state);
return state.connected;
}
static bool ble_write_(uint8_t* data, size_t size) {
if (size != BLE_TX_PACKET_SIZE) {
return false;
}
uint32_t deadline = ticks_timeout(500);
while (true) {
if (ticks_expired(deadline)) {
return false;
}
if (!is_connected()) {
return false;
}
if (ble_can_write()) {
break;
}
}
return ble_write(data, size);
}
static int ble_read_(uint8_t* buffer, size_t buffer_size) {
if (buffer_size != BLE_RX_PACKET_SIZE) {
return -1;
}
uint32_t deadline = ticks_timeout(500);
while (true) {
if (ticks_expired(deadline)) {
return false;
}
if (!is_connected()) {
return false;
}
if (ble_can_read()) {
break;
}
}
int r = ble_read(buffer, buffer_size);
return r;
}
static void ble_error(void) {
error_shutdown_ex("BLE ERROR",
"Error reading from BLE. Try different BLE cable.", NULL);
}
wire_iface_t* ble_iface_init(void) {
wire_iface_t* iface = &g_ble_iface;
if (iface->initialized) {
return iface;
}
memset(iface, 0, sizeof(wire_iface_t));
iface->poll_iface_id = SYSHANDLE_BLE_IFACE_0;
iface->tx_packet_size = BLE_TX_PACKET_SIZE;
iface->rx_packet_size = BLE_RX_PACKET_SIZE;
iface->write = &ble_write_;
iface->read = &ble_read_;
iface->error = &ble_error;
ble_start();
ble_state_t state = {0};
ble_get_state(&state);
if (!state.connectable && !state.pairing) {
if (state.peer_count > 0) {
ble_command_t cmd = {
.cmd_type = BLE_SWITCH_ON,
.data = {.adv_start =
{
.name = "Trezor Bootloader",
.static_mac = false,
}},
};
ble_issue_command(&cmd);
}
}
iface->initialized = true;
return iface;
}
void ble_iface_deinit(void) {
wire_iface_t* iface = &g_ble_iface;
if (!iface->initialized) {
return;
}
ble_command_t cmd = {
.cmd_type = BLE_SWITCH_OFF,
};
ble_issue_command(&cmd);
ble_stop();
memset(iface, 0, sizeof(wire_iface_t));
}
void ble_iface_start_pairing(void) {
ble_state_t state = {0};
ble_get_state(&state);
while (state.connected) {
ble_command_t cmd_disconnect = {
.cmd_type = BLE_DISCONNECT,
};
ble_issue_command(&cmd_disconnect);
systick_delay_ms(20);
ble_get_state(&state);
}
ble_command_t cmd = {
.cmd_type = BLE_PAIRING_MODE,
.data = {.adv_start =
{
.name = "Trezor Bootloader",
.static_mac = false,
}},
.data_len = 0,
};
ble_issue_command(&cmd);
}
#endif

View File

@ -19,21 +19,10 @@
#pragma once
#include <trezor_types.h>
#include "codec_v1.h"
#define IFACE_USB_MAX (15) // 0-15 reserved for USB
wire_iface_t* ble_iface_init(void);
#define MODE_READ 0x0000
#define MODE_WRITE 0x0100
void ble_iface_deinit(void);
typedef enum {
EVENT_NONE = 0,
EVENT_USB_CAN_READ = 0x01,
} poll_event_type_t;
typedef struct {
poll_event_type_t type;
} poll_event_t;
uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num,
poll_event_t* event, uint32_t timeout_ms);
void ble_iface_start_pairing(void);

View File

@ -23,19 +23,22 @@
#include "wire_iface_usb.h"
#include <io/usb.h>
#include <sys/sysevent.h>
#define USB_TIMEOUT 500
#define USB_PACKET_SIZE 64
#define USB_IFACE_NUM 0
_Static_assert(USB_PACKET_SIZE <= MAX_PACKET_SIZE, "USB_PACKET_SIZE too large");
static wire_iface_t g_usb_iface = {0};
static bool usb_write(uint8_t* data, size_t size) {
if (size != USB_PACKET_SIZE) {
return false;
}
int r = usb_webusb_write_blocking(USB_IFACE_NUM, data, size, USB_TIMEOUT);
int r =
usb_webusb_write_blocking(SYSHANDLE_USB_IFACE_0, data, size, USB_TIMEOUT);
return r == size;
}
@ -45,8 +48,8 @@ static int usb_read(uint8_t* buffer, size_t buffer_size) {
return -1;
}
int r = usb_webusb_read_blocking(USB_IFACE_NUM, buffer, USB_PACKET_SIZE,
USB_TIMEOUT);
int r = usb_webusb_read_blocking(SYSHANDLE_USB_IFACE_0, buffer,
USB_PACKET_SIZE, USB_TIMEOUT);
return r;
}
@ -75,7 +78,7 @@ static void usb_init_all(secbool usb21_landing) {
static uint8_t rx_buffer[USB_PACKET_SIZE];
static const usb_webusb_info_t webusb_info = {
.iface_num = USB_IFACE_NUM,
.iface_num = SYSHANDLE_USB_IFACE_0,
#ifdef TREZOR_EMULATOR
.emu_port = 21324,
#else
@ -96,20 +99,35 @@ static void usb_init_all(secbool usb21_landing) {
ensure(usb_start(), NULL);
}
void usb_iface_init(wire_iface_t* iface, secbool usb21_landing) {
wire_iface_t* usb_iface_init(secbool usb21_landing) {
wire_iface_t* iface = &g_usb_iface;
if (iface->initialized) {
return iface;
}
usb_init_all(usb21_landing);
memset(iface, 0, sizeof(wire_iface_t));
iface->poll_iface_id = USB_IFACE_NUM;
iface->poll_iface_id = SYSHANDLE_USB_IFACE_0;
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;
return iface;
}
void usb_iface_deinit(wire_iface_t* iface) {
void usb_iface_deinit(void) {
wire_iface_t* iface = &g_usb_iface;
if (!iface->initialized) {
return;
}
memset(iface, 0, sizeof(wire_iface_t));
usb_deinit();
}

View File

@ -21,6 +21,6 @@
#include "codec_v1.h"
void usb_iface_init(wire_iface_t* iface, secbool usb21_landing);
wire_iface_t* usb_iface_init(secbool usb21_landing);
void usb_iface_deinit(wire_iface_t* iface);
void usb_iface_deinit(void);

View File

@ -17,12 +17,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <antiglitch.h>
#include <sys/bootargs.h>
#include <trezor_model.h>
#include <trezor_rtl.h>
#include <util/image.h>
#include "bootui.h"
#include "rust_ui_bootloader.h"
#include "workflow.h"
workflow_result_t workflow_auto_update(const vendor_header *const vhdr,
@ -30,8 +33,18 @@ workflow_result_t workflow_auto_update(const vendor_header *const vhdr,
ui_set_initial_setup(true);
workflow_result_t res = WF_CANCELLED;
while (res == WF_CANCELLED) {
res = workflow_host_control(vhdr, hdr, ui_screen_connect);
uint32_t ui_result = WAIT_CANCEL;
uint8_t buf[1024] = {0};
screen_connect(true, false, buf, sizeof(buf));
res = workflow_host_control(vhdr, hdr, buf, sizeof(buf), &ui_result);
if (res == WF_OK_UI_ACTION && ui_result == WAIT_CANCEL) {
bootargs_set(BOOT_COMMAND_NONE, NULL, 0);
jump_allow_1();
jump_allow_2();
return WF_OK_REBOOT_SELECTED;
}
return res;
}

View File

@ -0,0 +1,131 @@
/*
* 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/>.
*/
#ifdef USE_BLE
#include <trezor_model.h>
#include <trezor_rtl.h>
#include <io/ble.h>
#include "bootui.h"
#include "rust_ui_bootloader.h"
#include "wire/wire_iface_ble.h"
#include "workflow.h"
static bool encode_pairing_code(uint32_t code, uint8_t *outbuf) {
if (code < 0 || code > 999999) {
return false;
}
for (size_t i = 0; i < BLE_PAIRING_CODE_LEN; i++) {
outbuf[BLE_PAIRING_CODE_LEN - i - 1] = '0' + (code % 10);
code /= 10;
}
return true;
}
static void end_pairing_mode(void) {
ble_state_t state = {0};
ble_get_state(&state);
if (state.peer_count > 0) {
ble_command_t cmd = {.cmd_type = BLE_SWITCH_ON};
ble_issue_command(&cmd);
} else {
ble_command_t cmd = {.cmd_type = BLE_SWITCH_OFF};
ble_issue_command(&cmd);
}
}
workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr,
const image_header *const hdr) {
ble_iface_start_pairing();
uint8_t buf[1024] = {0};
screen_pairing_mode(ui_get_initial_setup(), buf, sizeof(buf));
uint32_t code = 0;
workflow_result_t res =
workflow_host_control(vhdr, hdr, buf, sizeof(buf), &code);
if (res != WF_OK_UI_ACTION) {
end_pairing_mode();
return res;
}
if (code == PAIRING_MODE_CANCEL) {
end_pairing_mode();
return WF_OK_PAIRING_FAILED;
}
uint32_t result = ui_screen_confirm_pairing(code);
uint8_t pairing_code[BLE_PAIRING_CODE_LEN] = {0};
if (result != UI_RESULT_CONFIRM || !encode_pairing_code(code, pairing_code)) {
ble_command_t cmd = {
.cmd_type = BLE_REJECT_PAIRING,
};
ble_issue_command(&cmd);
return WF_OK_PAIRING_FAILED;
}
ble_command_t cmd = {
.cmd_type = BLE_ALLOW_PAIRING,
.data_len = sizeof(pairing_code),
};
memcpy(cmd.data.raw, pairing_code, sizeof(pairing_code));
ble_issue_command(&cmd);
bool skip_finalization = false;
sysevents_t awaited = {0};
sysevents_t signalled = {0};
awaited.read_ready |= 1 << SYSHANDLE_BLE;
sysevents_poll(&awaited, &signalled, 500);
if (signalled.read_ready == 1 << SYSHANDLE_BLE) {
ble_event_t event = {0};
if (ble_get_event(&event)) {
if (event.type == BLE_PAIRING_COMPLETED) {
skip_finalization = true;
}
}
}
if (!skip_finalization) {
pairing_mode_finalization_result_t r =
screen_pairing_mode_finalizing(ui_get_initial_setup());
if (r == PAIRING_FINALIZATION_FAILED) {
end_pairing_mode();
return WF_OK_PAIRING_FAILED;
}
if (r == PAIRING_FINALIZATION_CANCEL) {
ble_command_t disconnect = {.cmd_type = BLE_DISCONNECT};
ble_issue_command(&disconnect);
end_pairing_mode();
return WF_OK_PAIRING_FAILED;
}
}
return WF_OK_PAIRING_COMPLETED;
}
#endif

View File

@ -25,6 +25,7 @@
#include "antiglitch.h"
#include "bootui.h"
#include "rust_ui_bootloader.h"
#include "workflow.h"
typedef enum {
@ -33,6 +34,46 @@ typedef enum {
SCREEN_WAIT_FOR_HOST,
} screen_t;
workflow_result_t workflow_menu(const vendor_header *const vhdr,
const image_header *const hdr,
secbool firmware_present) {
while (true) {
uint8_t buf[1024];
screen_menu(ui_get_initial_setup(), firmware_present, buf, sizeof(buf));
uint32_t ui_result = 0;
workflow_result_t result =
workflow_host_control(vhdr, hdr, buf, sizeof(buf), &ui_result);
if (result != WF_OK_UI_ACTION) {
return result;
}
menu_result_t menu_result = (menu_result_t)ui_result;
if (menu_result == MENU_EXIT) { // exit menu
return WF_OK;
}
#ifdef USE_BLE
if (menu_result == MENU_BLUETOOTH) {
workflow_ble_pairing_request(vhdr, hdr);
continue;
}
#endif
if (menu_result == MENU_REBOOT) { // reboot
jump_allow_1();
jump_allow_2();
return WF_OK_REBOOT_SELECTED;
}
if (menu_result == MENU_WIPE) { // wipe
workflow_result_t r = workflow_wipe_device(NULL);
if (r == WF_ERROR || r == WF_OK_DEVICE_WIPED || r == WF_CANCELLED) {
return r;
}
}
return WF_ERROR_FATAL;
}
}
workflow_result_t workflow_bootloader(const vendor_header *const vhdr,
const image_header *const hdr,
secbool firmware_present) {
@ -52,37 +93,50 @@ workflow_result_t workflow_bootloader(const vendor_header *const vhdr,
}
} break;
case SCREEN_MENU: {
menu_result_t menu_result = ui_screen_menu(firmware_present);
if (menu_result == MENU_EXIT) { // exit menu
workflow_result_t res = workflow_menu(vhdr, hdr, firmware_present);
if (res == WF_OK) {
screen = SCREEN_INTRO;
continue;
}
if (menu_result == MENU_REBOOT) { // reboot
#ifndef USE_HASH_PROCESSOR
ui_screen_boot_stage_1(true);
#endif
jump_allow_1();
jump_allow_2();
return WF_OK_REBOOT_SELECTED;
}
if (menu_result == MENU_WIPE) { // wipe
workflow_result_t r = workflow_wipe_device(NULL);
if (r == WF_ERROR) {
return r;
}
if (r == WF_OK_DEVICE_WIPED) {
return r;
}
if (r == WF_CANCELLED) {
screen = SCREEN_MENU;
continue;
}
return WF_ERROR_FATAL;
if (res == WF_CANCELLED) {
screen = SCREEN_MENU;
continue;
}
return res;
} break;
case SCREEN_WAIT_FOR_HOST: {
uint8_t buf[1024] = {0};
uint32_t ui_result = 0;
screen_connect(false, true, buf, sizeof(buf));
workflow_result_t res =
workflow_host_control(vhdr, hdr, ui_screen_connect);
workflow_host_control(vhdr, hdr, buf, sizeof(buf), &ui_result);
switch (res) {
case WF_OK_UI_ACTION:
switch (ui_result) {
case WAIT_CANCEL:
screen = SCREEN_INTRO;
break;
#ifdef USE_BLE
case WAIT_PAIRING_MODE:
res = workflow_ble_pairing_request(vhdr, hdr);
if (res == WF_OK_PAIRING_COMPLETED ||
res == WF_OK_PAIRING_FAILED) {
screen = SCREEN_WAIT_FOR_HOST;
break;
}
if (res == WF_CANCELLED) {
screen = SCREEN_INTRO;
break;
}
return res;
#endif
case WAIT_MENU:
screen = SCREEN_MENU;
break;
default:
return WF_ERROR_FATAL;
}
continue;
case WF_CANCELLED:
screen = SCREEN_INTRO;
continue;

View File

@ -30,6 +30,7 @@
#endif
#include "bootui.h"
#include "rust_ui_bootloader.h"
#include "workflow.h"
workflow_result_t workflow_empty_device(void) {
@ -50,8 +51,34 @@ workflow_result_t workflow_empty_device(void) {
#endif
workflow_result_t res = WF_CANCELLED;
while (res == WF_CANCELLED) {
res = workflow_host_control(NULL, NULL, ui_screen_welcome);
uint32_t ui_result = WAIT_CANCEL;
while (res == WF_CANCELLED ||
(res == WF_OK_UI_ACTION && ui_result == WAIT_CANCEL)) {
uint8_t buf[1024] = {0};
screen_welcome(buf, sizeof(buf));
res = workflow_host_control(NULL, NULL, buf, sizeof(buf), &ui_result);
#ifdef USE_BLE
if (res == WF_OK_UI_ACTION && ui_result == WAIT_PAIRING_MODE) {
res = workflow_ble_pairing_request(NULL, NULL);
if (res == WF_OK_PAIRING_COMPLETED || res == WF_OK_PAIRING_FAILED) {
res = WF_CANCELLED;
ui_result = WAIT_CANCEL;
continue;
}
}
#endif
if (res == WF_OK_UI_ACTION && ui_result == WAIT_MENU) {
do {
res = workflow_menu(NULL, NULL, false);
} while (res == WF_CANCELLED);
if (res == WF_OK) {
res = WF_CANCELLED;
ui_result = WAIT_CANCEL;
continue;
}
return res;
}
}
return res;
}

View File

@ -29,8 +29,6 @@
#include <sec/secret.h>
#endif
#include <poll.h>
#include "bootui.h"
#include "protob/protob.h"
#include "version_check.h"
@ -519,11 +517,14 @@ workflow_result_t workflow_firmware_update(protob_io_t *iface) {
upload_status_t s = UPLOAD_IN_PROGRESS;
while (true) {
uint16_t ifaces[1] = {protob_get_iface_flag(iface) | MODE_READ};
poll_event_t e = {0};
uint8_t i = poll_events(ifaces, 1, &e, 100);
sysevents_t awaited = {0};
sysevents_t signalled = {0};
if (e.type == EVENT_NONE || i != protob_get_iface_flag(iface)) {
awaited.read_ready = 1 << protob_get_iface_flag(iface);
sysevents_poll(&awaited, &signalled, 100);
if (awaited.read_ready != signalled.read_ready) {
continue;
}

View File

@ -17,57 +17,101 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bootui.h>
#include <rust_ui_bootloader.h>
#include <trezor_model.h>
#include <trezor_rtl.h>
#include <sys/sysevent.h>
#include <sys/systick.h>
#include <sys/types.h>
#include <util/image.h>
#include "antiglitch.h"
#include "poll.h"
#include "protob/protob.h"
#include "wire/wire_iface_usb.h"
#include "workflow.h"
#ifdef USE_BLE
#include <wire/wire_iface_ble.h>
#endif
workflow_result_t workflow_host_control(const vendor_header *const vhdr,
const image_header *const hdr,
void (*redraw_wait_screen)(void)) {
wire_iface_t usb_iface = {0};
uint8_t *wait_layout,
size_t wait_layout_len,
uint32_t *ui_action_result) {
protob_io_t protob_usb_iface = {0};
redraw_wait_screen();
// if both are NULL, we don't have a firmware installed
// let's show a webusb landing page in this case
usb_iface_init(&usb_iface,
(vhdr == NULL && hdr == NULL) ? sectrue : secfalse);
wire_iface_t *usb_iface =
usb_iface_init((vhdr == NULL && hdr == NULL) ? sectrue : secfalse);
protob_init(&protob_usb_iface, &usb_iface);
protob_init(&protob_usb_iface, usb_iface);
#ifdef USE_BLE
wire_iface_t *ble_iface = ble_iface_init();
protob_io_t protob_ble_iface = {0};
protob_init(&protob_ble_iface, ble_iface);
#endif
workflow_result_t result = WF_ERROR_FATAL;
for (;;) {
uint16_t ifaces[1] = {protob_get_iface_flag(&protob_usb_iface) | MODE_READ};
poll_event_t e = {0};
sysevents_t awaited = {0};
uint8_t i = poll_events(ifaces, 1, &e, 100);
awaited.read_ready |= 1 << protob_get_iface_flag(&protob_usb_iface);
#ifdef USE_BLE
awaited.read_ready |= 1 << protob_get_iface_flag(&protob_ble_iface);
awaited.read_ready |= 1 << SYSHANDLE_BLE;
#endif
#ifdef USE_BUTTON
awaited.read_ready |= 1 << SYSHANDLE_BUTTON;
#endif
#ifdef USE_TOUCH
awaited.read_ready |= 1 << SYSHANDLE_TOUCH;
#endif
for (;;) {
sysevents_t signalled = {0};
sysevents_poll(&awaited, &signalled, 100);
if (signalled.read_ready == 0) {
continue;
}
uint16_t msg_id = 0;
protob_io_t *active_iface = NULL;
switch (e.type) {
case EVENT_USB_CAN_READ:
if (i == protob_get_iface_flag(&protob_usb_iface) &&
sectrue == protob_get_msg_header(&protob_usb_iface, &msg_id)) {
active_iface = &protob_usb_iface;
} else {
continue;
if (signalled.read_ready ==
(1 << protob_get_iface_flag(&protob_usb_iface)) &&
sectrue == protob_get_msg_header(&protob_usb_iface, &msg_id)) {
active_iface = &protob_usb_iface;
}
#ifdef USE_BLE
if (signalled.read_ready ==
(1 << protob_get_iface_flag(&protob_ble_iface)) &&
sectrue == protob_get_msg_header(&protob_ble_iface, &msg_id)) {
active_iface = &protob_ble_iface;
}
#endif
// no data, lets pass the event signal to UI
if (active_iface == NULL) {
uint32_t res = screen_event(wait_layout, wait_layout_len, &signalled);
if (res != 0) {
if (ui_action_result != NULL) {
*ui_action_result = res;
}
break;
case EVENT_NONE:
default:
continue;
result = WF_OK_UI_ACTION;
goto exit_host_control;
}
continue;
}
switch (msg_id) {
@ -109,6 +153,9 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr,
exit_host_control:
systick_delay_ms(100);
usb_iface_deinit(&usb_iface);
usb_iface_deinit();
#ifdef USE_BLE
ble_iface_init();
#endif
return result;
}

View File

@ -24,7 +24,7 @@
#include "bootui.h"
#include "protob.h"
#include "rust_ui.h"
#include "rust_ui_bootloader.h"
#include "workflow.h"
workflow_result_t workflow_unlock_bootloader(protob_io_t *iface) {

View File

@ -22,16 +22,67 @@
#include <util/flash_utils.h>
#ifdef USE_BLE
#include <io/ble.h>
#endif
#include <sys/systick.h>
#include "bootui.h"
#include "protob.h"
#include "rust_ui.h"
#include "rust_ui_bootloader.h"
#include "workflow.h"
workflow_result_t workflow_wipe_device(protob_io_t *iface) {
static void send_error_conditionally(protob_io_t* iface, char* msg) {
if (iface != NULL) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Could not read BLE status");
}
}
#ifdef USE_BLE
static bool wipe_bonds(protob_io_t* iface) {
ble_state_t state = {0};
ble_get_state(&state);
if (!state.state_known) {
send_error_conditionally(iface, "Could not read BLE status");
screen_wipe_fail();
return false;
}
ble_command_t ble_command = {0};
ble_command.cmd_type = BLE_ERASE_BONDS;
if (!ble_issue_command(&ble_command)) {
send_error_conditionally(iface, "Could not issue BLE command");
screen_wipe_fail();
return false;
}
uint32_t deadline = ticks_timeout(100);
while (true) {
ble_get_state(&state);
if (state.peer_count == 0) {
break;
}
if (ticks_expired(deadline)) {
send_error_conditionally(iface, "Could not erase bonds");
screen_wipe_fail();
return false;
}
}
return true;
}
#endif
workflow_result_t workflow_wipe_device(protob_io_t* iface) {
WipeDevice msg_recv;
if (iface != NULL) {
recv_msg_wipe_device(iface, &msg_recv);
}
ui_result_t response = ui_screen_wipe_confirm();
if (UI_RESULT_CONFIRM != response) {
if (iface != NULL) {
@ -42,11 +93,14 @@ workflow_result_t workflow_wipe_device(protob_io_t *iface) {
ui_screen_wipe();
secbool wipe_result = erase_device(ui_screen_wipe_progress);
#ifdef USE_BLE
if (!wipe_bonds(iface)) {
return WF_ERROR;
}
#endif
if (sectrue != wipe_result) {
if (iface != NULL) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Could not erase flash");
}
send_error_conditionally(iface, "Could not erase flash");
screen_wipe_fail();
return WF_ERROR;
}

View File

@ -21,6 +21,7 @@
#include <trezor_types.h>
#include <sys/sysevent.h>
#include <util/image.h>
#include "protob/protob.h"
@ -33,6 +34,9 @@ typedef enum {
WF_OK_FIRMWARE_INSTALLED = 0x04D9D07F,
WF_OK_DEVICE_WIPED = 0x30DC3841,
WF_OK_BOOTLOADER_UNLOCKED = 0x23FCBD03,
WF_OK_UI_ACTION = 0xAABBCCEE,
WF_OK_PAIRING_COMPLETED = 0xAABBCCEF,
WF_OK_PAIRING_FAILED = 0xAABBCCF0,
WF_CANCELLED = 0x55667788,
} workflow_result_t;
@ -54,6 +58,9 @@ workflow_result_t workflow_get_features(protob_io_t *iface,
const vendor_header *const vhdr,
const image_header *const hdr);
workflow_result_t workflow_menu(const vendor_header *const vhdr,
const image_header *const hdr,
secbool firmware_present);
workflow_result_t workflow_bootloader(const vendor_header *const vhdr,
const image_header *const hdr,
secbool firmware_present);
@ -62,7 +69,14 @@ workflow_result_t workflow_empty_device(void);
workflow_result_t workflow_host_control(const vendor_header *const vhdr,
const image_header *const hdr,
void (*redraw_wait_screen)(void));
uint8_t *wait_layout,
size_t wait_layout_len,
uint32_t *ui_action_result);
workflow_result_t workflow_auto_update(const vendor_header *const vhdr,
const image_header *const hdr);
#ifdef USE_BLE
workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr,
const image_header *const hdr);
#endif

View File

@ -44,6 +44,7 @@ const DEFAULT_BINDGEN_MACROS_COMMON: &[&str] = &[
"-I../io/usb/inc",
"-I../sec/entropy/inc",
"-I../sys/time/inc",
"-I../sys/task/inc",
"-I../util/flash/inc",
"-I../util/translations/inc",
"-I../models",
@ -402,13 +403,16 @@ fn generate_trezorhal_bindings() {
.allowlist_function("usb_get_state")
// ble
.allowlist_function("ble_get_state")
.allowlist_function("ble_get_event")
.allowlist_function("ble_issue_command")
.allowlist_type("ble_command_t")
.allowlist_type("ble_state_t")
.allowlist_type("ble_event_t")
// touch
.allowlist_function("touch_get_event")
// button
.allowlist_type("button_t")
.allowlist_type("button_event_t")
.allowlist_function("button_get_event")
// haptic
.allowlist_type("haptic_effect_t")
@ -426,7 +430,11 @@ fn generate_trezorhal_bindings() {
.allowlist_function("jpegdec_process")
.allowlist_function("jpegdec_get_info")
.allowlist_function("jpegdec_get_slice_rgba8888")
.allowlist_function("jpegdec_get_slice_mono8");
.allowlist_function("jpegdec_get_slice_mono8")
// sysevent
.allowlist_type("syshandle_t")
.allowlist_type("sysevents_t")
.allowlist_function("sysevents_poll");
// Write the bindings to a file in the OUR_DIR.
bindings

View File

@ -1,2 +0,0 @@
#include "rust_ui_bootloader.h"
#include "rust_ui_common.h"

View File

@ -1,5 +1,9 @@
#include <trezor_types.h>
#include <sys/sysevent.h>
uint32_t screen_event(uint8_t* mem, size_t mem_len, sysevents_t* signalled);
uint32_t screen_install_confirm(const char* vendor_str, uint8_t vendor_str_len,
const char* version_str,
const uint8_t* fingerprint,
@ -12,14 +16,16 @@ void screen_wipe_progress(int16_t progress, bool initialize);
uint32_t screen_intro(const char* bld_version_str, const char* vendor_str,
uint8_t vendor_str_len, const char* version_str,
bool fw_ok);
uint32_t screen_menu(secbool firmware_present);
void screen_connect(bool initial_setup);
void screen_menu(bool initial_setup, secbool firmware_present, uint8_t* mem,
size_t mem_len);
void screen_connect(bool initial_setup, bool auto_update, uint8_t* mem,
size_t mem_len);
void screen_wipe_success(void);
void screen_wipe_fail(void);
uint32_t screen_install_success(uint8_t restart_seconds, bool initial_setup,
bool complete_draw);
uint32_t screen_install_fail(void);
void screen_welcome(void);
void screen_welcome(uint8_t* mem, size_t mem_len);
void screen_boot_stage_1(bool fading);
uint32_t screen_unlock_bootloader_confirm(void);
void screen_unlock_bootloader_success(void);
@ -27,3 +33,9 @@ void bld_continue_label(uint16_t bg_color);
void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len,
uint32_t version, const void* vendor_img,
size_t vendor_img_len, int wait);
uint32_t screen_pairing_mode(bool initial_setup, uint8_t* mem, size_t mem_len);
uint32_t screen_confirm_pairing(uint32_t code, bool initial_setup);
uint32_t screen_pairing_mode_finalizing(bool initial_setup);

View File

@ -1,4 +1,24 @@
use super::ffi;
use crate::ui::event::BLEEvent;
pub fn ble_parse_event(event: ffi::ble_event_t) -> BLEEvent {
match event.type_ {
ffi::ble_event_type_t_BLE_CONNECTED => BLEEvent::Connected,
ffi::ble_event_type_t_BLE_DISCONNECTED => BLEEvent::Disconnected,
ffi::ble_event_type_t_BLE_PAIRING_REQUEST => {
let code: u32 = event
.data
.iter()
.take(6)
.map(|&b| (b - b'0'))
.fold(0, |acc, d| acc * 10 + d as u32);
BLEEvent::PairingRequest(code)
}
ffi::ble_event_type_t_BLE_PAIRING_CANCELLED => BLEEvent::PairingCanceled,
ffi::ble_event_type_t_BLE_PAIRING_COMPLETED => BLEEvent::PairingCompleted,
_ => panic!(),
}
}
pub fn connected() -> bool {
unsafe {

View File

@ -17,6 +17,13 @@ pub enum PhysicalButtonEvent {
Up = ffi::button_event_type_t_BTN_EVENT_UP as _,
}
pub fn button_parse_event(event: ffi::button_event_t) -> (PhysicalButton, PhysicalButtonEvent) {
(
unwrap!(PhysicalButton::from_u8(event.button as _)),
unwrap!(PhysicalButtonEvent::from_u8(event.event_type as _)),
)
}
pub fn button_get_event() -> Option<(PhysicalButton, PhysicalButtonEvent)> {
unsafe {
let mut e = ffi::button_event_t {
@ -24,10 +31,7 @@ pub fn button_get_event() -> Option<(PhysicalButton, PhysicalButtonEvent)> {
button: ffi::button_t_BTN_LEFT,
};
if ffi::button_get_event(&mut e as _) {
Some((
unwrap!(PhysicalButton::from_u8(e.button as _)),
unwrap!(PhysicalButtonEvent::from_u8(e.event_type as _)),
))
Some(button_parse_event(e))
} else {
None
}

View File

@ -35,3 +35,6 @@ pub mod wordlist;
pub mod secbool;
pub mod time;
#[cfg(feature = "ui")]
pub mod sysevent;

View File

@ -0,0 +1,84 @@
#![allow(unused_imports)]
use super::ffi;
use core::mem;
use crate::ui::component::Event;
pub use ffi::{sysevents_t, syshandle_t};
#[cfg(feature = "ble")]
use crate::trezorhal::ble::ble_parse_event;
#[cfg(feature = "ble")]
use crate::ui::event::BLEEvent;
#[cfg(feature = "touch")]
use crate::trezorhal::touch::touch_get_event;
#[cfg(feature = "touch")]
use crate::ui::event::TouchEvent;
#[cfg(feature = "button")]
use crate::trezorhal::button::button_parse_event;
#[cfg(feature = "button")]
use crate::trezorhal::ffi::button_get_event;
#[cfg(feature = "button")]
use crate::ui::event::ButtonEvent;
#[derive(PartialEq, Debug, Eq, Clone, Copy)]
pub enum Syshandle {
Button = ffi::syshandle_t_SYSHANDLE_BUTTON as _,
Touch = ffi::syshandle_t_SYSHANDLE_TOUCH as _,
Ble = ffi::syshandle_t_SYSHANDLE_BLE as _,
}
pub fn parse_event(signalled: &sysevents_t) -> Option<Event> {
#[cfg(feature = "ble")]
if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_BLE) != 0 {
let mut ble_event: ffi::ble_event_t = unsafe { mem::zeroed() };
let event_available = unsafe { ffi::ble_get_event(&mut ble_event) };
if event_available {
let ble_event = ble_parse_event(ble_event);
return Some(Event::BLE(ble_event));
}
}
#[cfg(feature = "button")]
if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_BUTTON) != 0 {
let mut button_event: ffi::button_event_t = unsafe { mem::zeroed() };
let event_available = unsafe { button_get_event(&mut button_event) };
if event_available {
let (btn, evt) = button_parse_event(button_event);
return Some(Event::Button(unwrap!(ButtonEvent::new(evt, btn))));
}
}
#[cfg(feature = "touch")]
if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_TOUCH) != 0 {
let touch_event = touch_get_event();
if touch_event != 0 {
let (event_type, ex, ey) = {
let event_type = touch_event >> 24;
let ex = (touch_event >> 12) & 0xFFF;
let ey = touch_event & 0xFFF;
(event_type, ex, ey)
};
return Some(Event::Touch(unwrap!(TouchEvent::new(event_type, ex, ey))));
}
}
None
}
pub fn sysevents_poll(ifaces: &[Syshandle]) -> Option<Event> {
let mut awaited: sysevents_t = unsafe { mem::zeroed() };
for i in ifaces {
let bit: u32 = 1 << *i as u32;
awaited.read_ready |= bit;
}
let mut signalled: sysevents_t = unsafe { mem::zeroed() };
unsafe { ffi::sysevents_poll(&awaited as _, &mut signalled as _, 100) };
parse_event(&signalled)
}

View File

@ -1,16 +1,31 @@
use crate::{
strutil::hexlify,
trezorhal::secbool::secbool,
trezorhal::{
secbool::secbool,
sysevent::{parse_event, sysevents_t},
},
ui::{
ui_bootloader::BootloaderUI,
util::{from_c_array, from_c_str},
ModelUI,
},
};
use core::slice;
#[no_mangle]
extern "C" fn screen_welcome() {
ModelUI::screen_welcome();
extern "C" fn screen_event(mem: *mut u8, mem_size: usize, signalled: &sysevents_t) -> u32 {
let buf = unsafe { slice::from_raw_parts_mut(mem, mem_size) };
let e = parse_event(signalled);
ModelUI::screen_event(buf, e)
}
#[no_mangle]
extern "C" fn screen_welcome(mem: *mut u8, mem_size: usize) {
let buf = unsafe { slice::from_raw_parts_mut(mem, mem_size) };
ModelUI::screen_welcome(buf);
}
#[no_mangle]
@ -75,8 +90,15 @@ extern "C" fn screen_unlock_bootloader_success() {
}
#[no_mangle]
extern "C" fn screen_menu(firmware_present: secbool) -> u32 {
ModelUI::screen_menu(firmware_present)
extern "C" fn screen_menu(
initial_setup: bool,
firmware_present: secbool,
mem: *mut u8,
mem_size: usize,
) {
let buf = unsafe { slice::from_raw_parts_mut(mem, mem_size) };
ModelUI::screen_menu(initial_setup, firmware_present, buf);
}
#[no_mangle]
@ -131,8 +153,15 @@ extern "C" fn screen_install_progress(progress: u16, initialize: bool, initial_s
}
#[no_mangle]
extern "C" fn screen_connect(initial_setup: bool) {
ModelUI::screen_connect(initial_setup)
extern "C" fn screen_connect(
initial_setup: bool,
auto_update: bool,
mem: *mut u8,
mem_size: usize,
) {
let buf = unsafe { slice::from_raw_parts_mut(mem, mem_size) };
ModelUI::screen_connect(initial_setup, auto_update, buf)
}
#[no_mangle]
@ -144,3 +173,23 @@ extern "C" fn screen_wipe_success() {
extern "C" fn screen_wipe_fail() {
ModelUI::screen_wipe_fail()
}
#[cfg(feature = "ble")]
#[no_mangle]
extern "C" fn screen_confirm_pairing(code: u32, initial_setup: bool) -> u32 {
ModelUI::screen_confirm_pairing(code, initial_setup)
}
#[cfg(feature = "ble")]
#[no_mangle]
extern "C" fn screen_pairing_mode(initial_setup: bool, mem: *mut u8, mem_size: usize) {
let buf = unsafe { slice::from_raw_parts_mut(mem, mem_size) };
ModelUI::screen_pairing_mode(initial_setup, buf);
}
#[cfg(feature = "ble")]
#[no_mangle]
extern "C" fn screen_pairing_mode_finalizing(initial_setup: bool) {
ModelUI::screen_pairing_mode_finalizing(initial_setup);
}

View File

@ -5,6 +5,7 @@ pub mod obj;
#[cfg(feature = "micropython")]
pub mod result;
pub mod simplified;
#[cfg(feature = "micropython")]

View File

@ -1,19 +1,23 @@
#[cfg(feature = "button")]
use crate::trezorhal::button::button_get_event;
#[cfg(feature = "touch")]
use crate::trezorhal::touch::touch_get_event;
#[cfg(feature = "button")]
use crate::ui::event::ButtonEvent;
#[cfg(feature = "touch")]
use crate::ui::event::TouchEvent;
use crate::ui::{
component::{Component, Event, EventCtx, Never},
component::{Component, EventCtx, Never},
display, CommonUI, ModelUI,
};
use core::{
mem::{align_of, size_of, MaybeUninit},
ptr,
};
use crate::ui::{component::Event, display::color::Color, shape::render_on_display};
use num_traits::ToPrimitive;
use crate::ui::{display::color::Color, shape::render_on_display};
use crate::trezorhal::sysevent::{sysevents_poll, Syshandle};
use heapless::Vec;
pub trait ReturnToC {
fn return_to_c(self) -> u32;
@ -57,7 +61,7 @@ pub fn touch_unpack(event: u32) -> Option<TouchEvent> {
TouchEvent::new(event_type, ex as _, ey as _).ok()
}
fn render(frame: &mut impl Component) {
pub(crate) fn render(frame: &mut impl Component) {
display::sync();
render_on_display(None, Some(Color::black()), |target| {
frame.render(target);
@ -65,26 +69,98 @@ fn render(frame: &mut impl Component) {
display::refresh();
}
fn convert_layout<A>(buf: &mut [u8]) -> *mut MaybeUninit<A> {
let required_size = size_of::<A>();
let required_align = align_of::<A>();
let ptr = buf.as_mut_ptr();
let addr = ptr as usize;
// Check alignment and size
if addr % required_align != 0 || buf.len() < required_size {
panic!("Buffer is not aligned or too small");
}
// SAFETY: we have just verified alignment and size
ptr as *mut MaybeUninit<A>
}
pub fn get_layout<A>(buf: &mut [u8]) -> &mut A {
let required_size = size_of::<A>();
let required_align = align_of::<A>();
let ptr = buf.as_mut_ptr();
let addr = ptr as usize;
// Check alignment and size
if addr % required_align != 0 || buf.len() < required_size {
panic!("Buffer is not aligned or too small");
}
// SAFETY: we have just verified alignment and size
let layout = ptr as *mut MaybeUninit<A>;
unsafe { &mut *layout.cast::<A>() }
}
pub fn init_layout<A>(buf: &mut [u8], frame: A) -> &mut A {
let frame_ptr: *mut MaybeUninit<A> = convert_layout(buf);
unsafe {
// Construct in-place
ptr::write(frame_ptr as *mut A, frame);
// Get mutable reference to initialized object
&mut *frame_ptr.cast::<A>()
}
}
pub fn process_frame_event<A>(frame: &mut A, event: Option<Event>) -> u32
where
A: Component,
A::Msg: ReturnToC,
{
if let Some(event) = event {
let mut ctx = EventCtx::new();
let msg = frame.event(&mut ctx, event);
if let Some(message) = msg {
return message.return_to_c();
}
}
render(frame);
0
}
pub fn run(frame: &mut impl Component<Msg = impl ReturnToC>) -> u32 {
frame.place(ModelUI::SCREEN);
ModelUI::fadeout();
render(frame);
ModelUI::fadein();
// flush any pending events
#[cfg(feature = "button")]
while button_eval().is_some() {}
let mut ifaces: Vec<Syshandle, 16> = Vec::new();
#[cfg(feature = "ble")]
unwrap!(ifaces.push(Syshandle::Ble));
#[cfg(feature = "button")]
unwrap!(ifaces.push(Syshandle::Button));
#[cfg(feature = "touch")]
unwrap!(ifaces.push(Syshandle::Touch));
loop {
#[cfg(all(feature = "button", not(feature = "touch")))]
let event = button_eval();
#[cfg(feature = "touch")]
let event = touch_unpack(touch_get_event());
let event = sysevents_poll(ifaces.as_slice());
if let Some(e) = event {
let mut ctx = EventCtx::new();
#[cfg(all(feature = "button", not(feature = "touch")))]
let msg = frame.event(&mut ctx, Event::Button(e));
#[cfg(feature = "touch")]
let msg = frame.event(&mut ctx, Event::Touch(e));
let msg = frame.event(&mut ctx, e);
if let Some(message) = msg {
return message.return_to_c();

View File

@ -0,0 +1,132 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Pad},
display::{Color, Font, Icon},
geometry::{Alignment, Insets, Offset, Point, Rect},
layout_bolt::{
component::{Button, ButtonMsg},
constant::WIDTH,
theme::bootloader::{
button_bld, button_bld_menu, button_initial, BLD_BG, BUTTON_AREA_START,
BUTTON_HEIGHT, CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, WELCOME_COLOR,
},
},
shape::{self, Renderer},
},
};
#[repr(u32)]
#[derive(Copy, Clone, ToPrimitive)]
pub enum ConnectMsg {
Cancel = 1,
PairingMode = 2,
Menu = 3,
}
pub struct Connect {
fg: Color,
bg: Pad,
message: TString<'static>,
font: Font,
button: Button,
menu: Option<Button>,
}
impl Connect {
pub fn new<T>(message: T, font: Font, fg: Color, initial_setup: bool, auto_update: bool) -> Self
where
T: Into<TString<'static>>,
{
let (bg, menu_btn, cancel) = if initial_setup {
(
WELCOME_COLOR,
Button::with_icon(Icon::new(MENU32))
.styled(button_initial())
.with_expanded_touch_area(Insets::uniform(13)),
Button::with_text("Cancel".into()).styled(button_initial()),
)
} else {
(
BLD_BG,
Button::with_icon(Icon::new(MENU32))
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(13)),
Button::with_text("Cancel".into()).styled(button_bld()),
)
};
let menu = if auto_update { Some(menu_btn) } else { None };
let mut instance = Self {
fg,
bg: Pad::with_background(bg),
message: message.into(),
font,
button: cancel,
menu,
};
instance.bg.clear();
instance
}
}
impl Component for Connect {
type Msg = ConnectMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
self.menu.place(CORNER_BUTTON_AREA);
self.button.place(Rect::new(
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
return Some(ConnectMsg::Cancel);
}
#[cfg(all(feature = "ble", feature = "button"))]
if let Event::Button(_) = event {
return Some(ConnectMsg::PairingMode);
}
#[cfg(feature = "powerctl")]
if let Some(ButtonMsg::Clicked) = self.menu.event(ctx, event) {
return Some(ConnectMsg::Menu);
};
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.button.render(target);
#[cfg(feature = "powerctl")]
self.menu.render(target);
self.message.map(|t| {
shape::Text::new(
self.bg.area.center() + Offset::y(self.font.text_height() / 2),
t,
self.font,
)
.with_fg(self.fg)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Connect {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Connect");
t.string("message", self.message);
}
}

View File

@ -1,7 +1,7 @@
use crate::{
strutil::TString,
ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
component::{Component, Event, EventCtx, Label, Pad},
constant::screen,
display::Icon,
geometry::{Alignment, Insets, Point, Rect},
@ -27,29 +27,27 @@ pub enum IntroMsg {
pub struct Intro<'a> {
bg: Pad,
title: Child<Label<'a>>,
menu: Child<Button>,
host: Child<Button>,
text: Child<Label<'a>>,
warn: Option<Child<Label<'a>>>,
title: Label<'a>,
menu: Button,
host: Button,
text: Label<'a>,
warn: Option<Label<'a>>,
}
impl<'a> Intro<'a> {
pub fn new(title: TString<'a>, content: TString<'a>, fw_ok: bool) -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
title: Child::new(Label::left_aligned(title, text_title(BLD_BG)).vertically_centered()),
menu: Child::new(
Button::with_icon(Icon::new(MENU32))
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(13)),
),
host: Child::new(Button::with_text("INSTALL FIRMWARE".into()).styled(button_bld())),
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
warn: (!fw_ok).then_some(Child::new(
title: Label::left_aligned(title, text_title(BLD_BG)).vertically_centered(),
menu: Button::with_icon(Icon::new(MENU32))
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(13)),
host: Button::with_text("INSTALL FIRMWARE".into()).styled(button_bld()),
text: Label::left_aligned(content, TEXT_NORMAL).vertically_centered(),
warn: (!fw_ok).then_some(
Label::new("FIRMWARE CORRUPTED".into(), Alignment::Start, TEXT_WARNING)
.vertically_centered(),
)),
),
}
}
}

View File

@ -1,7 +1,7 @@
use crate::{
trezorhal::secbool::{secbool, sectrue},
ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
component::{Component, Event, EventCtx, Label, Pad},
constant::{screen, WIDTH},
display::Icon,
geometry::{Insets, Point, Rect},
@ -26,37 +26,39 @@ pub enum MenuMsg {
Close = 0xAABBCCDD,
Reboot = 0x11223344,
FactoryReset = 0x55667788,
Bluetooth = 0x99AABBCC,
}
pub struct Menu {
bg: Pad,
title: Child<Label<'static>>,
close: Child<Button>,
reboot: Child<Button>,
reset: Child<Button>,
title: Label<'static>,
close: Button,
reboot: Button,
reset: Button,
bluetooth: Button,
}
impl Menu {
pub fn new(firmware_present: secbool) -> Self {
let content_reboot = IconText::new("REBOOT TREZOR", Icon::new(REFRESH24));
let content_reset = IconText::new("FACTORY RESET", Icon::new(FIRE24));
let content_bluetooth = IconText::new("BLUETOOTH", Icon::new(FIRE24));
let mut instance = Self {
bg: Pad::with_background(BLD_BG),
title: Child::new(
Label::left_aligned("BOOTLOADER".into(), text_title(BLD_BG)).vertically_centered(),
),
close: Child::new(
Button::with_icon(Icon::new(X32))
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)),
),
reboot: Child::new(
Button::with_icon_and_text(content_reboot)
.styled(button_bld())
.initially_enabled(sectrue == firmware_present),
),
reset: Child::new(Button::with_icon_and_text(content_reset).styled(button_bld())),
title: Label::left_aligned("BOOTLOADER".into(), text_title(BLD_BG))
.vertically_centered(),
close: Button::with_icon(Icon::new(X32))
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)),
reboot: Button::with_icon_and_text(content_reboot)
.styled(button_bld())
.initially_enabled(sectrue == firmware_present),
reset: Button::with_icon_and_text(content_reset).styled(button_bld()),
bluetooth: Button::with_icon_and_text(content_bluetooth).styled(button_bld()),
};
instance.bg.clear();
instance
@ -84,6 +86,16 @@ impl Component for Menu {
BUTTON_AREA_START + 2 * BUTTON_HEIGHT + BUTTON_SPACING,
),
));
self.bluetooth.place(Rect::new(
Point::new(
CONTENT_PADDING,
BUTTON_AREA_START + 2 * BUTTON_HEIGHT + 2 * BUTTON_SPACING,
),
Point::new(
WIDTH - CONTENT_PADDING,
BUTTON_AREA_START + 3 * BUTTON_HEIGHT + 2 * BUTTON_SPACING,
),
));
bounds
}
@ -97,6 +109,9 @@ impl Component for Menu {
if let Some(Clicked) = self.reset.event(ctx, event) {
return Some(Self::Msg::FactoryReset);
}
if let Some(Clicked) = self.bluetooth.event(ctx, event) {
return Some(Self::Msg::Bluetooth);
}
None
}
@ -107,5 +122,6 @@ impl Component for Menu {
self.close.render(target);
self.reboot.render(target);
self.reset.render(target);
self.bluetooth.render(target);
}
}

View File

@ -1,57 +1,58 @@
use heapless::String;
pub mod connect;
pub mod intro;
pub mod menu;
pub mod welcome;
use crate::{
trezorhal::secbool::secbool,
ui::{
component::{connect::Connect, Label},
display::{self, Color, Icon},
geometry::{Point, Rect},
layout::simplified::{run, show},
},
};
#[cfg(feature = "ble")]
pub mod pairing_finalization;
use heapless::String;
use ufmt::uwrite;
use super::{
bootloader::welcome::Welcome,
bootloader::{connect::Connect, welcome::Welcome},
component::{
bl_confirm::{Confirm, ConfirmTitle},
Button, ResultScreen, WelcomeScreen,
},
cshape::{render_loader, LoaderRange},
fonts,
theme::{
self,
bootloader::{
button_bld, button_bld_menu, button_confirm, button_wipe_cancel, button_wipe_confirm,
BLD_BG, BLD_FG, BLD_TITLE_COLOR, BLD_WIPE_COLOR, CHECK24, CHECK40, DOWNLOAD32, FIRE32,
FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL,
TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
BLD_BG, BLD_FG, BLD_TITLE_COLOR, BLD_WARN_COLOR, BLD_WIPE_COLOR, CHECK24, CHECK40,
DOWNLOAD32, FIRE32, FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, TEXT_BOLD,
TEXT_NORMAL, TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
},
FG,
},
UIBolt,
};
use crate::ui::{ui_bootloader::BootloaderUI, CommonUI};
use crate::ui::{
display::toif::Toif,
geometry::{Alignment, Alignment2D, Offset},
shape,
shape::render_on_display,
use crate::{
trezorhal::secbool::secbool,
ui::{
component::{Event, Label},
display::{self, toif::Toif, Color, Icon, LOADER_MAX},
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
layout::simplified::{get_layout, init_layout, process_frame_event, run, show},
shape,
shape::render_on_display,
ui_bootloader::BootloaderUI,
CommonUI,
},
};
use ufmt::uwrite;
use super::theme::bootloader::BLD_WARN_COLOR;
use intro::Intro;
use menu::Menu;
use super::cshape::{render_loader, LoaderRange};
use crate::ui::display::LOADER_MAX;
#[cfg(feature = "ble")]
use super::theme::bootloader::{button_confirm_initial, button_initial};
pub mod intro;
pub mod menu;
pub mod welcome;
#[cfg(feature = "ble")]
use crate::ui::layout_bolt::{
bootloader::pairing_finalization::PairingFinalization,
component::{confirm_pairing::ConfirmPairing, pairing_mode::PairingMode},
};
pub type BootloaderString = String<128>;
@ -137,10 +138,39 @@ impl UIBolt {
}
}
#[allow(clippy::large_enum_variant)]
enum BootloaderLayout {
Welcome(Welcome),
Menu(Menu),
Connect(Connect),
#[cfg(feature = "ble")]
PairingMode(PairingMode),
}
impl BootloaderLayout {
fn process_event(&mut self, event: Option<Event>) -> u32 {
match self {
BootloaderLayout::Welcome(f) => process_frame_event::<Welcome>(f, event),
BootloaderLayout::Menu(f) => process_frame_event::<Menu>(f, event),
BootloaderLayout::Connect(f) => process_frame_event::<Connect>(f, event),
#[cfg(feature = "ble")]
BootloaderLayout::PairingMode(f) => process_frame_event::<PairingMode>(f, event),
}
}
}
impl BootloaderUI for UIBolt {
fn screen_welcome() {
let mut frame = Welcome::new();
show(&mut frame, true);
fn screen_event(buf: &mut [u8], event: Option<Event>) -> u32 {
let layout = get_layout::<BootloaderLayout>(buf);
layout.process_event(event)
}
fn screen_welcome(buf: &mut [u8]) {
let mut welcome = Welcome::new();
show(&mut welcome, true);
init_layout(buf, BootloaderLayout::Welcome(welcome));
}
fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool) {
@ -257,8 +287,12 @@ impl BootloaderUI for UIBolt {
unimplemented!();
}
fn screen_menu(firmware_present: secbool) -> u32 {
run(&mut Menu::new(firmware_present))
fn screen_menu(_initial_setup: bool, firmware_present: secbool, buf: &mut [u8]) {
let mut frame = Menu::new(firmware_present);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Menu(frame));
}
fn screen_intro(bld_version: &str, vendor: &str, version: &str, fw_ok: bool) -> u32 {
@ -321,15 +355,18 @@ impl BootloaderUI for UIBolt {
)
}
fn screen_connect(initial_setup: bool) {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
fn screen_connect(initial_setup: bool, auto_update: bool, buf: &mut [u8]) {
let mut frame = Connect::new(
"Waiting for host...",
fonts::FONT_NORMAL,
BLD_TITLE_COLOR,
bg,
initial_setup,
auto_update,
);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Connect(frame));
}
fn screen_wipe_success() {
@ -439,4 +476,70 @@ impl BootloaderUI for UIBolt {
display::refresh();
}
#[cfg(feature = "ble")]
fn screen_confirm_pairing(code: u32, initial_setup: bool) -> u32 {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let title = Label::centered("Pair device".into(), TEXT_NORMAL);
let (right, left) = if initial_setup {
(
Button::with_text("CONFIRM".into()).styled(button_confirm_initial()),
Button::with_text("REJECT".into()).styled(button_initial()),
)
} else {
(
Button::with_text("CONFIRM".into()).styled(button_confirm()),
Button::with_text("REJECT".into()).styled(button_bld()),
)
};
let mut frame = ConfirmPairing::new(bg, left, right, title, code);
run(&mut frame)
}
#[cfg(feature = "ble")]
fn screen_pairing_mode(initial_setup: bool, buf: &mut [u8]) {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let btn = if initial_setup {
Button::with_text("Cancel".into()).styled(button_initial())
} else {
Button::with_text("Cancel".into()).styled(button_bld())
};
let mut frame = PairingMode::new(
"Waiting for pairing...".into(),
fonts::FONT_NORMAL,
BLD_TITLE_COLOR,
bg,
btn,
);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::PairingMode(frame));
}
#[cfg(feature = "ble")]
fn screen_pairing_mode_finalizing(initial_setup: bool) -> u32 {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let btn = if initial_setup {
Button::with_text("Cancel".into()).styled(button_initial())
} else {
Button::with_text("Cancel".into()).styled(button_bld())
};
let mut frame = PairingFinalization::new(
"Waiting for host confirmation...",
fonts::FONT_NORMAL,
BLD_TITLE_COLOR,
bg,
btn,
);
run(&mut frame)
}
}

View File

@ -0,0 +1,107 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Pad},
display::{Color, Font},
event::BLEEvent,
geometry::{Alignment, Offset, Point, Rect},
layout_bolt::{
component::{Button, ButtonMsg},
constant::WIDTH,
theme::bootloader::{BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING},
},
shape::{self, Renderer},
},
};
#[repr(u32)]
#[derive(Copy, Clone, ToPrimitive)]
pub enum PairingFinalizationMsg {
Completed = 1,
Cancel = 2,
Failed = 3,
}
pub struct PairingFinalization {
fg: Color,
bg: Pad,
message: TString<'static>,
font: Font,
button: Button,
}
impl PairingFinalization {
pub fn new<T>(message: T, font: Font, fg: Color, bg: Color, button: Button) -> Self
where
T: Into<TString<'static>>,
{
let mut instance = Self {
fg,
bg: Pad::with_background(bg),
message: message.into(),
font,
button,
};
instance.bg.clear();
instance
}
}
impl Component for PairingFinalization {
type Msg = PairingFinalizationMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
self.button.place(Rect::new(
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
return Some(PairingFinalizationMsg::Cancel);
}
if let Event::BLE(BLEEvent::PairingCanceled) = event {
return Some(PairingFinalizationMsg::Failed);
}
if let Event::BLE(BLEEvent::Disconnected) = event {
return Some(PairingFinalizationMsg::Failed);
}
if let Event::BLE(BLEEvent::PairingCompleted) = event {
return Some(PairingFinalizationMsg::Completed);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.button.render(target);
self.message.map(|t| {
shape::Text::new(
self.bg.area.center() + Offset::y(self.font.text_height() / 2),
t,
self.font,
)
.with_fg(self.fg)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PairingFinalization {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ParingFinalization");
t.string("message", self.message);
}
}

View File

@ -1,11 +1,3 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
constant::screen,
display::toif::Toif,
geometry::{Alignment, Alignment2D, Offset, Rect},
shape::{self, Renderer},
};
use super::super::{
fonts,
theme::{
@ -13,33 +5,70 @@ use super::super::{
GREY_MEDIUM, WHITE,
},
};
use crate::ui::{
component::{Component, Event, EventCtx, Pad},
constant::screen,
display::{toif::Toif, Icon},
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
layout_bolt::{
component::Button,
theme::bootloader::{button_initial, CORNER_BUTTON_AREA, MENU32},
},
shape::{self, Renderer},
};
#[cfg(feature = "powerctl")]
use crate::ui::layout_bolt::component::ButtonMsg::Clicked;
#[repr(u32)]
#[derive(Copy, Clone, ToPrimitive)]
pub enum WelcomeMsg {
Cancel = 1,
PairingMode = 2,
Menu = 3,
}
pub struct Welcome {
bg: Pad,
menu: Button,
}
impl Welcome {
pub fn new() -> Self {
Self {
bg: Pad::with_background(WELCOME_COLOR).with_clear(),
menu: Button::with_icon(Icon::new(MENU32))
.styled(button_initial())
.with_expanded_touch_area(Insets::uniform(13)),
}
}
}
impl Component for Welcome {
type Msg = Never;
type Msg = WelcomeMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(screen());
self.menu.place(CORNER_BUTTON_AREA);
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
#[cfg(all(feature = "ble", feature = "button"))]
if let Event::Button(_) = _event {
return Some(WelcomeMsg::PairingMode);
}
#[cfg(feature = "powerctl")]
if let Some(Clicked) = self.menu.event(_ctx, _event) {
return Some(Self::Msg::Menu);
};
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
#[cfg(feature = "powerctl")]
self.menu.render(target);
shape::Text::new(
screen().top_center() + Offset::y(102),

View File

@ -0,0 +1,130 @@
use super::{
super::{
constant::WIDTH,
theme::bootloader::{BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING, TITLE_AREA},
},
Button,
ButtonMsg::Clicked,
};
use crate::{
strutil::format_i64,
ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
constant,
display::Color,
event::BLEEvent,
geometry::{Offset, Point, Rect},
layout_bolt::{fonts, theme::WHITE},
shape,
shape::Renderer,
},
};
const ICON_TOP: i16 = 17;
const CONTENT_START: i16 = 72;
const CONTENT_AREA: Rect = Rect::new(
Point::new(CONTENT_PADDING, CONTENT_START),
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
);
#[derive(Copy, Clone, ToPrimitive)]
#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
pub enum ConfirmPairingMsg {
Cancel = 1,
Confirm = 2,
}
pub struct ConfirmPairing<'a> {
bg: Pad,
content_pad: Pad,
bg_color: Color,
title: Label<'a>,
code: u32,
left_button: Child<Button>,
right_button: Child<Button>,
}
impl<'a> ConfirmPairing<'a> {
pub fn new(
bg_color: Color,
left_button: Button,
right_button: Button,
title: Label<'a>,
code: u32,
) -> Self {
Self {
bg: Pad::with_background(bg_color).with_clear(),
content_pad: Pad::with_background(bg_color),
bg_color,
title,
left_button: Child::new(left_button),
right_button: Child::new(right_button),
code,
}
}
}
impl Component for ConfirmPairing<'_> {
type Msg = ConfirmPairingMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(constant::screen());
self.content_pad.place(Rect::new(
Point::zero(),
Point::new(WIDTH, BUTTON_AREA_START),
));
self.title.place(TITLE_AREA);
let button_size = Offset::new((WIDTH - 3 * CONTENT_PADDING) / 2, BUTTON_HEIGHT);
self.left_button.place(Rect::from_top_left_and_size(
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
button_size,
));
self.right_button.place(Rect::from_top_left_and_size(
Point::new(2 * CONTENT_PADDING + button_size.x, BUTTON_AREA_START),
button_size,
));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(Clicked) = self.left_button.event(ctx, event) {
return Some(Self::Msg::Cancel);
};
if let Some(Clicked) = self.right_button.event(ctx, event) {
return Some(Self::Msg::Confirm);
};
match event {
Event::BLE(BLEEvent::PairingCanceled) => Some(Self::Msg::Cancel),
Event::BLE(BLEEvent::Disconnected) => Some(Self::Msg::Cancel),
Event::BLE(BLEEvent::Connected) => Some(Self::Msg::Cancel),
_ => None,
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.content_pad.render(target);
self.left_button.render(target);
self.right_button.render(target);
self.title.render(target);
let mut buf = [0; 20];
let text = unwrap!(format_i64(self.code as _, &mut buf));
shape::Text::new(CONTENT_AREA.left_center(), text, fonts::FONT_BOLD_UPPER)
.with_fg(WHITE)
.render(target);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ConfirmPairing<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ConfirmPairing");
}
}

View File

@ -1,11 +1,16 @@
#[cfg(feature = "translations")]
mod address_details;
pub mod bl_confirm;
mod button;
#[cfg(feature = "translations")]
mod coinjoin_progress;
#[cfg(feature = "ble")]
pub mod confirm_pairing;
mod dialog;
mod fido;
#[cfg(feature = "ble")]
pub mod pairing_mode;
#[rustfmt::skip]
mod fido_icons;
mod error;

View File

@ -0,0 +1,104 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Pad},
display::{Color, Font},
event::BLEEvent,
geometry::{Alignment, Offset, Rect},
layout::simplified::ReturnToC,
layout_bolt::component::{Button, ButtonMsg},
shape::{self, Renderer},
},
};
#[repr(u32)]
pub enum PairingMsg {
Cancel,
Pairing(u32),
}
impl ReturnToC for PairingMsg {
fn return_to_c(self) -> u32 {
match self {
PairingMsg::Cancel => 1000000,
PairingMsg::Pairing(code) => code,
}
}
}
pub struct PairingMode {
fg: Color,
bg: Pad,
message: TString<'static>,
font: Font,
button: Button,
}
impl PairingMode {
pub fn new(
message: TString<'static>,
font: Font,
fg: Color,
bg: Color,
button: Button,
) -> Self {
let mut instance = Self {
fg,
bg: Pad::with_background(bg),
message,
font,
button,
};
instance.bg.clear();
instance
}
}
impl Component for PairingMode {
type Msg = PairingMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
self.button.place(bounds.split_bottom(60).1);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
return Some(PairingMsg::Cancel);
}
if let Event::BLE(BLEEvent::PairingRequest(code)) = event {
return Some(PairingMsg::Pairing(code));
}
if let Event::BLE(BLEEvent::PairingCanceled) = event {
return Some(PairingMsg::Cancel);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.button.render(target);
self.message.map(|t| {
shape::Text::new(
self.bg.area.center() + Offset::y(self.font.text_height() / 2),
t,
self.font,
)
.with_fg(self.fg)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PairingMode {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("PairingMode");
t.string("message", self.message);
}
}

View File

@ -1,17 +1,17 @@
use super::super::{
component::{ButtonStyle, ButtonStyleSheet, ResultStyle},
fonts,
theme::{BLACK, FG, GREY_DARK, GREY_LIGHT, WHITE},
};
use crate::ui::{
component::{text::TextStyle, LineBreaking::BreakWordsNoHyphen},
constant::{HEIGHT, WIDTH},
display::Color,
geometry::{Offset, Point, Rect},
layout_bolt::theme::GREY_MEDIUM,
util::include_res,
};
use super::super::{
component::{ButtonStyle, ButtonStyleSheet, ResultStyle},
fonts,
theme::{BLACK, FG, GREY_DARK, GREY_LIGHT, WHITE},
};
pub const BLD_BG: Color = Color::rgb(0x00, 0x1E, 0xAD);
pub const BLD_FG: Color = WHITE;
pub const BLD_WIPE_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
@ -30,6 +30,9 @@ pub const BLD_INSTALL_BTN_COLOR_ACTIVE: Color = Color::rgb(0xCD, 0xD2, 0xEF);
pub const BLD_BTN_COLOR: Color = Color::rgb(0x2D, 0x42, 0xBF);
pub const BLD_BTN_COLOR_ACTIVE: Color = Color::rgb(0x04, 0x10, 0x58);
pub const BLD_BTN_INITIAL_COLOR: Color = GREY_MEDIUM;
pub const BLD_BTN_INITIAL_ACTIVE: Color = GREY_DARK;
pub const BLD_TITLE_COLOR: Color = WHITE;
pub const WELCOME_COLOR: Color = BLACK;
@ -234,6 +237,70 @@ pub fn button_bld() -> ButtonStyleSheet {
}
}
pub fn button_confirm_initial() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: WELCOME_COLOR,
button_color: WHITE,
background_color: WELCOME_COLOR,
border_color: WELCOME_COLOR,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: WELCOME_COLOR,
button_color: GREY_LIGHT,
background_color: WELCOME_COLOR,
border_color: WELCOME_COLOR,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: FG,
button_color: GREY_DARK,
background_color: FG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub fn button_initial() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: WHITE,
button_color: BLD_BTN_INITIAL_COLOR,
background_color: WELCOME_COLOR,
border_color: WELCOME_COLOR,
border_radius: 4,
border_width: 0,
},
active: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: WHITE,
button_color: BLD_BTN_INITIAL_ACTIVE,
background_color: WELCOME_COLOR,
border_color: WELCOME_COLOR,
border_radius: 4,
border_width: 0,
},
disabled: &ButtonStyle {
font: fonts::FONT_BOLD_UPPER,
text_color: GREY_LIGHT,
button_color: BLD_BTN_COLOR,
background_color: WELCOME_COLOR,
border_color: WELCOME_COLOR,
border_radius: 4,
border_width: 0,
},
}
}
pub const fn text_title(bg: Color) -> TextStyle {
TextStyle::new(
fonts::FONT_BOLD_UPPER,

View File

@ -0,0 +1,96 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Pad},
display::{Color, Font},
geometry::{Alignment, Offset, Rect},
layout_caesar::{
component::{ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos},
theme::{BUTTON_HEIGHT, TITLE_AREA_HEIGHT},
},
shape::{self, Renderer},
},
};
#[repr(u32)]
#[derive(Copy, Clone, ToPrimitive)]
pub enum ConnectMsg {
Cancel = 1,
PairingMode = 2,
}
pub struct Connect {
fg: Color,
bg: Pad,
message: TString<'static>,
font: Font,
buttons: ButtonController,
}
impl Connect {
pub fn new<T>(message: T, font: Font, fg: Color, bg: Color) -> Self
where
T: Into<TString<'static>>,
{
let mut instance = Self {
fg,
bg: Pad::with_background(bg),
message: message.into(),
font,
buttons: ButtonController::new(ButtonLayout::none_none_text("Cancel".into())),
};
instance.bg.clear();
instance
}
}
impl Component for Connect {
type Msg = ConnectMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
// Title on top, buttons on bottom, text in between
let (_, text_and_buttons_area) = bounds.split_top(TITLE_AREA_HEIGHT);
let (_, buttons_area) = text_and_buttons_area.split_bottom(BUTTON_HEIGHT);
self.buttons.place(buttons_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Right, false)) =
self.buttons.event(ctx, event)
{
return Some(ConnectMsg::Cancel);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.buttons.render(target);
self.message.map(|t| {
shape::Text::new(
self.bg.area.center() + Offset::y(self.font.text_height() / 2),
t,
self.font,
)
.with_fg(self.fg)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Connect {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Connect");
t.string("message", self.message);
}
}

View File

@ -3,7 +3,7 @@ use heapless::String;
use crate::{
trezorhal::secbool::secbool,
ui::{
component::{connect::Connect, Label, LineBreaking::BreakWordsNoHyphen},
component::{Label, LineBreaking::BreakWordsNoHyphen},
constant,
constant::{HEIGHT, SCREEN},
display::{self, Color, Icon},
@ -33,7 +33,14 @@ mod intro;
mod menu;
mod welcome;
use crate::ui::ui_bootloader::BootloaderUI;
mod connect;
use crate::ui::{
component::Event,
layout::simplified::{get_layout, init_layout, process_frame_event},
ui_bootloader::BootloaderUI,
};
use connect::Connect;
use intro::Intro;
use menu::Menu;
use welcome::Welcome;
@ -88,11 +95,34 @@ impl UICaesar {
display::refresh();
}
}
enum BootloaderLayout {
Welcome(Welcome),
Menu(Menu),
Connect(Connect),
}
impl BootloaderLayout {
fn process_event(&mut self, event: Option<Event>) -> u32 {
match self {
BootloaderLayout::Welcome(f) => process_frame_event::<Welcome>(f, event),
BootloaderLayout::Menu(f) => process_frame_event::<Menu>(f, event),
BootloaderLayout::Connect(f) => process_frame_event::<Connect>(f, event),
}
}
}
impl BootloaderUI for UICaesar {
fn screen_welcome() {
fn screen_event(buf: &mut [u8], event: Option<Event>) -> u32 {
let layout = get_layout::<BootloaderLayout>(buf);
layout.process_event(event)
}
fn screen_welcome(buf: &mut [u8]) {
let mut frame = Welcome::new();
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Welcome(frame));
}
fn screen_install_success(restart_seconds: u8, _initial_setup: bool, complete_draw: bool) {
@ -221,8 +251,12 @@ impl BootloaderUI for UICaesar {
show(&mut frame, false);
}
fn screen_menu(firmware_present: secbool) -> u32 {
run(&mut Menu::new(firmware_present))
fn screen_menu(_initial_setup: bool, firmware_present: secbool, buf: &mut [u8]) {
let mut frame = Menu::new(firmware_present);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Menu(frame));
}
fn screen_intro(bld_version: &str, vendor: &str, version: &str, fw_ok: bool) -> u32 {
@ -273,9 +307,12 @@ impl BootloaderUI for UICaesar {
);
}
fn screen_connect(_initial_setup: bool) {
fn screen_connect(_initial_setup: bool, _auto_update: bool, buf: &mut [u8]) {
let mut frame = Connect::new("Waiting for host...", fonts::FONT_NORMAL, BLD_FG, BLD_BG);
show(&mut frame, false);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Connect(frame));
}
fn screen_wipe_success() {
@ -381,4 +418,19 @@ impl BootloaderUI for UICaesar {
display::refresh();
}
#[cfg(feature = "ble")]
fn screen_confirm_pairing(_code: u32, _initial_setup: bool) -> u32 {
unimplemented!()
}
#[cfg(feature = "ble")]
fn screen_pairing_mode(_initial_setup: bool, _buf: &mut [u8]) {
unimplemented!()
}
#[cfg(feature = "ble")]
fn screen_pairing_mode_finalizing(_initial_setup: bool) -> u32 {
unimplemented!()
}
}

View File

@ -0,0 +1,95 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Pad},
display::{Color, Font},
geometry::{Alignment, Offset, Point, Rect},
layout_delizia::{
component::{Button, ButtonMsg},
constant::WIDTH,
theme::bootloader::{button_bld, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING},
},
shape::{self, Renderer},
},
};
#[repr(u32)]
#[derive(Copy, Clone, ToPrimitive)]
pub enum ConnectMsg {
Cancel = 1,
PairingMode = 2,
}
pub struct Connect {
fg: Color,
bg: Pad,
message: TString<'static>,
font: Font,
button: Button,
}
impl Connect {
pub fn new<T>(message: T, font: Font, fg: Color, bg: Color) -> Self
where
T: Into<TString<'static>>,
{
let mut instance = Self {
fg,
bg: Pad::with_background(bg),
message: message.into(),
font,
button: Button::with_text("Cancel".into())
.styled(button_bld())
.with_text_align(Alignment::Center),
};
instance.bg.clear();
instance
}
}
impl Component for Connect {
type Msg = ConnectMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
self.button.place(Rect::new(
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
return Some(ConnectMsg::Cancel);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.button.render(target);
self.message.map(|t| {
shape::Text::new(
self.bg.area.center() + Offset::y(self.font.text_height() / 2),
t,
self.font,
)
.with_fg(self.fg)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Connect {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Connect");
t.string("message", self.message);
}
}

View File

@ -3,7 +3,7 @@ use heapless::String;
use crate::{
trezorhal::secbool::secbool,
ui::{
component::{connect::Connect, Label},
component::Label,
display::{self, Color, Icon},
geometry::{Alignment, Offset, Point, Rect},
layout::simplified::{run, show},
@ -43,6 +43,11 @@ use crate::ui::{
use ufmt::uwrite;
use super::theme::bootloader::BLD_WARN_COLOR;
use crate::ui::{
component::Event,
layout::simplified::{get_layout, init_layout, process_frame_event},
};
use connect::Connect;
use intro::Intro;
use menu::Menu;
@ -50,6 +55,8 @@ pub mod intro;
pub mod menu;
pub mod welcome;
pub mod connect;
pub type BootloaderString = String<128>;
const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE";
@ -121,11 +128,32 @@ impl UIDelizia {
}
}
}
#[allow(clippy::large_enum_variant)]
enum BootloaderLayout {
Welcome(Welcome),
Menu(Menu),
Connect(Connect),
}
impl BootloaderLayout {
fn process_event(&mut self, event: Option<Event>) -> u32 {
match self {
BootloaderLayout::Welcome(f) => process_frame_event::<Welcome>(f, event),
BootloaderLayout::Menu(f) => process_frame_event::<Menu>(f, event),
BootloaderLayout::Connect(f) => process_frame_event::<Connect>(f, event),
}
}
}
impl BootloaderUI for UIDelizia {
fn screen_welcome() {
fn screen_event(buf: &mut [u8], event: Option<Event>) -> u32 {
let layout = get_layout::<BootloaderLayout>(buf);
layout.process_event(event)
}
fn screen_welcome(buf: &mut [u8]) {
let mut frame = Welcome::new();
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Welcome(frame));
}
fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool) {
@ -290,8 +318,12 @@ impl BootloaderUI for UIDelizia {
show(&mut frame, true);
}
fn screen_menu(firmware_present: secbool) -> u32 {
run(&mut Menu::new(firmware_present))
fn screen_menu(_initial_setup: bool, firmware_present: secbool, buf: &mut [u8]) {
let mut frame = Menu::new(firmware_present);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Menu(frame));
}
fn screen_intro(bld_version: &str, vendor: &str, version: &str, fw_ok: bool) -> u32 {
@ -357,7 +389,7 @@ impl BootloaderUI for UIDelizia {
)
}
fn screen_connect(initial_setup: bool) {
fn screen_connect(initial_setup: bool, _auto_update: bool, buf: &mut [u8]) {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let mut frame = Connect::new(
"Waiting for host...",
@ -366,6 +398,7 @@ impl BootloaderUI for UIDelizia {
bg,
);
show(&mut frame, true);
init_layout(buf, BootloaderLayout::Connect(frame));
}
fn screen_wipe_success() {
@ -475,4 +508,19 @@ impl BootloaderUI for UIDelizia {
display::refresh();
}
#[cfg(feature = "ble")]
fn screen_confirm_pairing(_code: u32, _initial_setup: bool) -> u32 {
unimplemented!()
}
#[cfg(feature = "ble")]
fn screen_pairing_mode(_initial_setup: bool, _buf: &mut [u8]) {
unimplemented!()
}
#[cfg(feature = "ble")]
fn screen_pairing_mode_finalizing(_initial_setup: bool) -> u32 {
unimplemented!()
}
}

View File

@ -1,9 +1,12 @@
#[cfg(feature = "bootloader")]
use crate::trezorhal::secbool::secbool;
use crate::ui::component::Event;
#[cfg(feature = "bootloader")]
pub trait BootloaderUI {
fn screen_welcome();
fn screen_event(buf: &mut [u8], event: Option<Event>) -> u32;
fn screen_welcome(buf: &mut [u8]);
fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool);
@ -25,7 +28,7 @@ pub trait BootloaderUI {
fn screen_unlock_bootloader_success();
fn screen_menu(firmware_present: secbool) -> u32;
fn screen_menu(initial_setup: bool, firmware_present: secbool, buf: &mut [u8]);
fn screen_intro(bld_version: &str, vendor: &str, version: &str, fw_ok: bool) -> u32;
@ -35,7 +38,7 @@ pub trait BootloaderUI {
fn screen_install_progress(progress: u16, initialize: bool, initial_setup: bool);
fn screen_connect(initial_setup: bool);
fn screen_connect(initial_setup: bool, auto_update: bool, buf: &mut [u8]);
fn screen_wipe_success();
@ -48,4 +51,13 @@ pub trait BootloaderUI {
vendor_img: &'static [u8],
wait: i32,
);
#[cfg(feature = "ble")]
fn screen_confirm_pairing(code: u32, initial_setup: bool) -> u32;
#[cfg(feature = "ble")]
fn screen_pairing_mode(initial_setup: bool, buf: &mut [u8]);
#[cfg(feature = "ble")]
fn screen_pairing_mode_finalizing(initial_setup: bool) -> u32;
}

View File

@ -7,6 +7,7 @@
#include <io/usb.h>
#include <rtl/secbool.h>
#include <sec/entropy.h>
#include <sys/sysevent.h>
#include <sys/systick.h>
#include <util/flash.h>
#include <util/translations.h>

View File

@ -221,7 +221,7 @@ void sysevents_poll(const sysevents_t *awaited, sysevents_t *signalled,
bool ready = (poller->signalled->read_ready != 0) ||
(poller->signalled->write_ready != 0);
if (ready || timed_out) {
systask_t *task = poller->task;
systask_t *task = poller->task; // USB interface
remove_poller(dispatcher, prio);
if (task == kernel_task) {
return;

View File

@ -110,7 +110,7 @@ void rsod_terminal(const systask_postmortem_t* pminfo) {
#ifdef FANCY_FATAL_ERROR
#include "rust_ui.h"
#include "rust_ui_common.h"
void rsod_gui(const systask_postmortem_t* pminfo) {
const char* title = RSOD_DEFAULT_TITLE;