diff --git a/core/embed/extmod/modtrezorio/modtrezorio-ble.h b/core/embed/extmod/modtrezorio/modtrezorio-ble.h
new file mode 100644
index 000000000..9d8f134ad
--- /dev/null
+++ b/core/embed/extmod/modtrezorio/modtrezorio-ble.h
@@ -0,0 +1,201 @@
+/*
+ * 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 .
+ */
+
+#include "ble/dfu.h"
+#include "ble/messages.h"
+
+/// package: trezorio.ble
+
+/// INTERNAL: int # interface id for internal (stm<->nrf) connection
+/// EXTERNAL: int # interface id for ble client connection
+
+/// def update_init(data: bytes, binsize: int) -> bool:
+/// """
+/// Initializes the BLE firmware update. Returns true if the update finished
+/// with only the initial chunk. False means calling `update_chunk` is
+/// expected.
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_update_init(mp_obj_t data, mp_obj_t binsize) {
+ mp_buffer_info_t buffer = {0};
+ mp_int_t binsize_int = mp_obj_get_int(binsize);
+
+ mp_get_buffer_raise(data, &buffer, MP_BUFFER_READ);
+
+ ble_set_dfu_mode(true);
+
+ dfu_result_t result = dfu_update_init(buffer.buf, buffer.len, binsize_int);
+ if (result == DFU_NEXT_CHUNK) {
+ return mp_const_false;
+ } else if (result == DFU_SUCCESS) {
+ ble_set_dfu_mode(false);
+ return mp_const_true;
+ } else {
+ ble_set_dfu_mode(false);
+ mp_raise_msg(&mp_type_RuntimeError, "Upload failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_update_init_obj,
+ mod_trezorio_BLE_update_init);
+
+/// def update_chunk(chunk: bytes) -> bool:
+/// """
+/// Writes next chunk of BLE firmware update. Returns true if the update is
+/// finished, or false if more chunks are expected.
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_update_chunk(mp_obj_t data) {
+ mp_buffer_info_t buffer = {0};
+
+ mp_get_buffer_raise(data, &buffer, MP_BUFFER_READ);
+
+ dfu_result_t result = dfu_update_chunk(buffer.buf, buffer.len);
+
+ if (result == DFU_NEXT_CHUNK) {
+ return mp_const_false;
+ } else if (result == DFU_SUCCESS) {
+ ble_set_dfu_mode(false);
+ return mp_const_true;
+ } else {
+ ble_set_dfu_mode(false);
+ mp_raise_msg(&mp_type_RuntimeError, "Upload failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_update_chunk_obj,
+ mod_trezorio_BLE_update_chunk);
+
+/// def write_int(self, msg: bytes) -> int:
+/// """
+/// Sends internal message to NRF.
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_write_int(mp_obj_t self, mp_obj_t msg) {
+ mp_buffer_info_t buf = {0};
+ mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ);
+ ble_int_comm_send(buf.buf, buf.len, INTERNAL_MESSAGE);
+ return MP_OBJ_NEW_SMALL_INT(buf.len);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_write_int_obj,
+ mod_trezorio_BLE_write_int);
+
+/// def write_ext(self, msg: bytes) -> int:
+/// """
+/// Sends message over BLE
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_write_ext(mp_obj_t self, mp_obj_t msg) {
+ mp_buffer_info_t buf = {0};
+ mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ);
+ ble_int_comm_send(buf.buf, buf.len, EXTERNAL_MESSAGE);
+ return MP_OBJ_NEW_SMALL_INT(buf.len);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_write_ext_obj,
+ mod_trezorio_BLE_write_ext);
+
+/// def erase_bonds() -> None:
+/// """
+/// Erases all BLE bonds
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_erase_bonds(void) {
+ bool result = send_erase_bonds();
+ if (result) {
+ return mp_const_none;
+ } else {
+ mp_raise_msg(&mp_type_RuntimeError, "Erase bonds failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_erase_bonds_obj,
+ mod_trezorio_BLE_erase_bonds);
+
+/// def start_comm() -> None:
+/// """
+/// Start communication with BLE chip
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_start_comm(void) {
+ ble_comm_start();
+ auto_start_advertising();
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_start_comm_obj,
+ mod_trezorio_BLE_start_comm);
+
+/// def start_advertising(whitelist: bool) -> None:
+/// """
+/// Start advertising
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_start_advertising(mp_obj_t whitelist) {
+ bool whitelist_bool = mp_obj_is_true(whitelist);
+
+ start_advertising(whitelist_bool);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_start_advertising_obj,
+ mod_trezorio_BLE_start_advertising);
+
+/// def stop_advertising(whitelist: bool) -> None:
+/// """
+/// Stop advertising
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_stop_advertising(void) {
+ ble_comm_start();
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_stop_advertising_obj,
+ mod_trezorio_BLE_stop_advertising);
+
+/// def disconnect() -> None:
+/// """
+/// Disconnect BLE
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_disconnect(void) {
+ bool result = send_disconnect();
+ if (result) {
+ return mp_const_none;
+ } else {
+ mp_raise_msg(&mp_type_RuntimeError, "Disconnect failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_disconnect_obj,
+ mod_trezorio_BLE_disconnect);
+
+STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR_INTERNAL), MP_ROM_INT(BLE_IFACE_INT)},
+ {MP_ROM_QSTR(MP_QSTR_EXTERNAL), MP_ROM_INT(BLE_IFACE_EXT)},
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ble)},
+ {MP_ROM_QSTR(MP_QSTR_update_init),
+ MP_ROM_PTR(&mod_trezorio_BLE_update_init_obj)},
+ {MP_ROM_QSTR(MP_QSTR_update_chunk),
+ MP_ROM_PTR(&mod_trezorio_BLE_update_chunk_obj)},
+ {MP_ROM_QSTR(MP_QSTR_write_int),
+ MP_ROM_PTR(&mod_trezorio_BLE_write_int_obj)},
+ {MP_ROM_QSTR(MP_QSTR_write_ext),
+ MP_ROM_PTR(&mod_trezorio_BLE_write_ext_obj)},
+ {MP_ROM_QSTR(MP_QSTR_erase_bonds),
+ MP_ROM_PTR(&mod_trezorio_BLE_erase_bonds_obj)},
+ {MP_ROM_QSTR(MP_QSTR_start_comm),
+ MP_ROM_PTR(&mod_trezorio_BLE_start_comm_obj)},
+ {MP_ROM_QSTR(MP_QSTR_start_advertising),
+ MP_ROM_PTR(&mod_trezorio_BLE_start_advertising_obj)},
+ {MP_ROM_QSTR(MP_QSTR_stop_advertising),
+ MP_ROM_PTR(&mod_trezorio_BLE_stop_advertising_obj)},
+ {MP_ROM_QSTR(MP_QSTR_disconnect),
+ MP_ROM_PTR(&mod_trezorio_BLE_disconnect_obj)},
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorio_BLE_globals,
+ mod_trezorio_BLE_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorio_BLE_module = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mod_trezorio_BLE_globals};
diff --git a/core/embed/extmod/modtrezorio/modtrezorio-poll.h b/core/embed/extmod/modtrezorio/modtrezorio-poll.h
index da5b12247..5b703970f 100644
--- a/core/embed/extmod/modtrezorio/modtrezorio-poll.h
+++ b/core/embed/extmod/modtrezorio/modtrezorio-poll.h
@@ -24,14 +24,22 @@
#include "display.h"
#include "embed/extmod/trezorobj.h"
+#ifdef USE_BLE
+#include "ble/int_comm_defs.h"
+#include "ble/state.h"
+#include "ble_hal.h"
+#endif
+
+#define BLE_EVENTS_IFACE (252)
#define USB_DATA_IFACE (253)
#define BUTTON_IFACE (254)
#define TOUCH_IFACE (255)
+#define USB_RW_IFACE_MAX (15) // 0-15 reserved for USB
+#define BLE_IFACE_INT (16)
+#define BLE_IFACE_EXT (17)
#define POLL_READ (0x0000)
#define POLL_WRITE (0x0100)
-extern bool usb_connected_previously;
-
/// package: trezorio.__init__
/// def poll(ifaces: Iterable[int], list_ref: list, timeout_ms: int) -> bool:
@@ -145,35 +153,80 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref,
}
}
#endif
- else if (mode == POLL_READ) {
- if (sectrue == usb_hid_can_read(iface)) {
- uint8_t buf[64] = {0};
- int len = usb_hid_read(iface, buf, sizeof(buf));
- if (len > 0) {
- ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
- ret->items[1] = mp_obj_new_bytes(buf, len);
- return mp_const_true;
+#ifdef USE_BLE
+ else if (iface == BLE_EVENTS_IFACE) {
+ ble_event_poll();
+ uint8_t connected = ble_connected();
+ if (connected != ble_connected_previously) {
+ ble_connected_previously = connected;
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = connected ? mp_const_true : mp_const_false;
+ return mp_const_true;
+ }
+ }
+#endif
+ else if (iface <= USB_RW_IFACE_MAX) {
+ if (mode == POLL_READ) {
+ if (sectrue == usb_hid_can_read(iface)) {
+ uint8_t buf[64] = {0};
+ int len = usb_hid_read(iface, buf, sizeof(buf));
+ if (len > 0) {
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = mp_obj_new_bytes(buf, len);
+ return mp_const_true;
+ }
+ } else if (sectrue == usb_webusb_can_read(iface)) {
+ uint8_t buf[64] = {0};
+ int len = usb_webusb_read(iface, buf, sizeof(buf));
+ if (len > 0) {
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = mp_obj_new_bytes(buf, len);
+ return mp_const_true;
+ }
}
- } else if (sectrue == usb_webusb_can_read(iface)) {
- uint8_t buf[64] = {0};
- int len = usb_webusb_read(iface, buf, sizeof(buf));
- if (len > 0) {
+ } else if (mode == POLL_WRITE) {
+ if (sectrue == usb_hid_can_write(iface)) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
- ret->items[1] = mp_obj_new_bytes(buf, len);
+ ret->items[1] = mp_const_none;
+ return mp_const_true;
+ } else if (sectrue == usb_webusb_can_write(iface)) {
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = mp_const_none;
return mp_const_true;
}
}
- } else if (mode == POLL_WRITE) {
- if (sectrue == usb_hid_can_write(iface)) {
+ }
+#ifdef USE_BLE
+ else if (iface == BLE_IFACE_INT) {
+ if (mode == POLL_READ) {
+ uint8_t buf[64] = {0};
+ int len = ble_int_comm_receive(buf, sizeof(buf));
+ if (len > 0) {
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = mp_obj_new_bytes(buf, len);
+ return mp_const_true;
+ }
+ } else if (mode == POLL_WRITE) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
ret->items[1] = mp_const_none;
return mp_const_true;
- } else if (sectrue == usb_webusb_can_write(iface)) {
+ }
+ } else if (iface == BLE_IFACE_EXT) {
+ if (mode == POLL_READ) {
+ uint8_t buf[BLE_PACKET_SIZE] = {0};
+ int len = ble_ext_comm_receive(buf, sizeof(buf));
+ if (len > 0) {
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
+ ret->items[1] = mp_obj_new_bytes(buf, len);
+ return mp_const_true;
+ }
+ } else if (mode == POLL_WRITE) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
ret->items[1] = mp_const_none;
return mp_const_true;
}
}
+#endif
}
if (mp_hal_ticks_ms() >= deadline) {
diff --git a/core/embed/extmod/modtrezorio/modtrezorio.c b/core/embed/extmod/modtrezorio/modtrezorio.c
index 72fa781f4..b75ef562c 100644
--- a/core/embed/extmod/modtrezorio/modtrezorio.c
+++ b/core/embed/extmod/modtrezorio/modtrezorio.c
@@ -34,6 +34,9 @@
// Whether USB data pins were connected on last check (USB configured)
bool usb_connected_previously = true;
+#ifdef USE_BLE
+bool ble_connected_previously = false;
+#endif
#define CHECK_PARAM_RANGE(value, minimum, maximum) \
if (value < minimum || value > maximum) { \
@@ -48,6 +51,9 @@ bool usb_connected_previously = true;
#include "modtrezorio-webusb.h"
#include "modtrezorio-usb.h"
// clang-format on
+#ifdef USE_BLE
+#include "modtrezorio-ble.h"
+#endif
#ifdef USE_SBU
#include "modtrezorio-sbu.h"
#endif
@@ -57,7 +63,7 @@ bool usb_connected_previously = true;
#endif
/// package: trezorio.__init__
-/// from . import fatfs, sdcard
+/// from . import fatfs, sdcard, ble
/// POLL_READ: int # wait until interface is readable and return read data
/// POLL_WRITE: int # wait until interface is writable
@@ -74,8 +80,9 @@ bool usb_connected_previously = true;
/// BUTTON_RIGHT: int # button number of right button
/// USB_CHECK: int # interface id for check of USB data connection
+/// BLE_CHECK: int # interface id for check of BLE data connection
-/// WireInterface = Union[HID, WebUSB]
+/// WireInterface = Union[HID, WebUSB, BleInterface]
STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorio)},
@@ -89,6 +96,10 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_sdcard), MP_ROM_PTR(&mod_trezorio_sdcard_module)},
#endif
+#ifdef USE_BLE
+ {MP_ROM_QSTR(MP_QSTR_ble), MP_ROM_PTR(&mod_trezorio_BLE_module)},
+#endif
+
#ifdef USE_TOUCH
{MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)},
{MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_ROM_INT((TOUCH_START >> 24) & 0xFFU)},
@@ -117,6 +128,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_POLL_WRITE), MP_ROM_INT(POLL_WRITE)},
{MP_ROM_QSTR(MP_QSTR_USB_CHECK), MP_ROM_INT(USB_DATA_IFACE)},
+ {MP_ROM_QSTR(MP_QSTR_BLE_CHECK), MP_ROM_INT(BLE_EVENTS_IFACE)},
};
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorio_globals,
diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c
index 33f181381..f0898648a 100644
--- a/core/embed/extmod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c
@@ -378,6 +378,7 @@ STATIC mp_obj_str_t mod_trezorutils_full_name_obj = {
/// """Minor version."""
/// VERSION_PATCH: int
/// """Patch version."""
+/// USE_BLE: bool
/// USE_SD_CARD: bool
/// """Whether the hardware supports SD card."""
/// USE_BACKLIGHT: bool
@@ -427,6 +428,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
#else
{MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_false},
#endif
+#ifdef USE_BLE
+ {MP_ROM_QSTR(MP_QSTR_USE_BLE), mp_const_true},
+#else
+ {MP_ROM_QSTR(MP_QSTR_USE_BLE), mp_const_false},
+#endif
#ifdef USE_BACKLIGHT
{MP_ROM_QSTR(MP_QSTR_USE_BACKLIGHT), mp_const_true},
#else
diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c
index 1a0d2d546..c8e770488 100644
--- a/core/embed/firmware/main.c
+++ b/core/embed/firmware/main.c
@@ -77,6 +77,12 @@
#include "optiga_transport.h"
#include "secret.h"
#endif
+#ifdef USE_BLE
+#include "ble/dfu.h"
+#include "ble/messages.h"
+#include "ble/state.h"
+#include "ble_hal.h"
+#endif
#include "unit_variant.h"
#ifdef SYSTEM_VIEW
@@ -181,6 +187,13 @@ int main(void) {
memzero(secret, sizeof(secret));
#endif
+#ifdef USE_BLE
+ dfu_init();
+ ble_comm_init();
+ send_state_request();
+ wait_for_answer();
+#endif
+
#if !defined TREZOR_MODEL_1
drop_privileges();
#endif
diff --git a/core/embed/lib/ble/ble.h b/core/embed/lib/ble/ble.h
new file mode 100644
index 000000000..78f8d69c2
--- /dev/null
+++ b/core/embed/lib/ble/ble.h
@@ -0,0 +1,9 @@
+#ifndef BLE_BLE_H
+#define BLE_BLE_H
+
+#include "state.h"
+#include "messages.h"
+#include "fwu.h"
+#include "dfu.h"
+
+#endif
diff --git a/core/embed/lib/ble/dfu.c b/core/embed/lib/ble/dfu.c
new file mode 100644
index 000000000..61e38d33d
--- /dev/null
+++ b/core/embed/lib/ble/dfu.c
@@ -0,0 +1,137 @@
+/*
+ * 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 .
+ */
+
+#include "common.h"
+#include TREZOR_BOARD
+#include "ble_hal.h"
+#include "common.h"
+#include "dfu.h"
+#include "fwu.h"
+
+const uint32_t TIMEOUT_MS = 2000;
+
+static TFwu sFwu;
+
+static uint32_t tick_start = 0;
+
+void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len);
+static uint8_t readData(uint8_t *data, int maxLen);
+
+void dfu_init(void) {}
+
+dfu_result_t dfu_update_process(void) {
+ while (1) {
+ // Can send 4 chars...
+ // (On a microcontroller, you'd use the TX Empty interrupt or test a
+ // register.)
+
+ fwuCanSendData(&sFwu, 4);
+
+ // Data available? Get up to 4 bytes...
+ // (On a microcontroller, you'd use the RX Available interrupt or test a
+ // register.)
+ uint8_t rxBuf[4];
+ uint8_t rxLen = readData(rxBuf, 4);
+ if (rxLen > 0) {
+ fwuDidReceiveData(&sFwu, rxBuf, rxLen);
+ }
+
+ // Give the firmware update module a timeslot to continue the process.
+ EFwuProcessStatus status = fwuYield(&sFwu, 0);
+
+ if (status == FWU_STATUS_COMPLETION) {
+ ble_reset();
+ return DFU_SUCCESS;
+ }
+
+ if (status == FWU_STATUS_FAILURE) {
+ return DFU_FAIL;
+ }
+
+ if (hal_ticks_ms() - tick_start > TIMEOUT_MS) {
+ return DFU_FAIL;
+ }
+
+ if (fwuIsReadyForChunk(&sFwu)) {
+ return DFU_NEXT_CHUNK;
+ }
+ }
+}
+
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) {
+ sFwu.commandObject = data;
+ sFwu.commandObjectLen = len;
+ sFwu.dataObject = NULL;
+ sFwu.dataObjectLen = binary_len;
+ sFwu.txFunction = txFunction;
+ sFwu.responseTimeoutMillisec = TIMEOUT_MS;
+
+ if (!ble_reset_to_bootloader()) {
+ return DFU_FAIL;
+ }
+
+ tick_start = hal_ticks_ms();
+
+ // Prepare the firmware update process.
+ fwuInit(&sFwu);
+
+ // Start the firmware update process.
+ fwuExec(&sFwu);
+
+ return dfu_update_process();
+}
+
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) {
+ tick_start = hal_ticks_ms();
+
+ fwuSendChunk(&sFwu, data, len);
+
+ return dfu_update_process();
+}
+
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len) {
+ uint32_t chunk_offset = 0;
+ uint32_t rem_data = binfile_len;
+
+ dfu_result_t res = dfu_update_init(datfile, datfile_len, binfile_len);
+
+ while (res == DFU_NEXT_CHUNK) {
+ // Send the next chunk of the data object.
+ uint32_t chunk_size = 4096;
+ if (rem_data < 4096) {
+ chunk_size = rem_data;
+ rem_data = 0;
+ } else {
+ rem_data -= 4096;
+ }
+ res = dfu_update_chunk(&binfile[chunk_offset], chunk_size);
+ chunk_offset += chunk_size;
+ }
+
+ return res;
+}
+
+void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len) {
+ ble_comm_send(buf, len);
+}
+
+static uint8_t readData(uint8_t *data, int maxLen) {
+ return ble_comm_receive(data, maxLen);
+}
diff --git a/core/embed/lib/ble/dfu.h b/core/embed/lib/ble/dfu.h
new file mode 100644
index 000000000..cd064b035
--- /dev/null
+++ b/core/embed/lib/ble/dfu.h
@@ -0,0 +1,16 @@
+#ifndef __DFU_H__
+#define __DFU_H__
+
+typedef enum {
+ DFU_NEXT_CHUNK,
+ DFU_SUCCESS,
+ DFU_FAIL,
+} dfu_result_t;
+
+void dfu_init(void);
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len);
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len);
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len);
+
+#endif
diff --git a/core/embed/lib/ble/fwu.c b/core/embed/lib/ble/fwu.c
new file mode 100644
index 000000000..4937aed4e
--- /dev/null
+++ b/core/embed/lib/ble/fwu.c
@@ -0,0 +1,664 @@
+//
+// fwu.c
+// nrf52-dfu
+//
+// C library for the Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#include "fwu.h"
+#include
+
+// TODO too big, split in separate files!
+
+typedef enum {
+ FWU_PS_IDLE = 0,
+ FWU_PS_PING = 10,
+ FWU_PS_RCPT_NOTIF = 20,
+ FWU_PS_MTU = 30,
+ FWU_PS_OBJ1_SELECT = 40,
+ FWU_PS_OBJ1_CREATE = 50,
+ FWU_PS_OBJ1_WRITE = 60,
+ FWU_PS_OBJ1_CRC_GET = 70,
+ FWU_PS_OBJ1_EXECUTE = 80,
+ FWU_PS_OBJ2_SELECT = 90,
+ FWU_PS_OBJ2_WAIT_FOR_CHUNK = 91,
+ FWU_PS_OBJ2_CREATE = 100,
+ FWU_PS_OBJ2_WRITE = 110,
+ FWU_PS_OBJ2_CRC_GET = 120,
+ FWU_PS_OBJ2_EXECUTE = 130,
+ FWU_PS_FAIL = 254,
+ FWU_PS_DONE = 255,
+} EFwuProcessState;
+
+// Process requests, triggering process state transitions.
+typedef enum {
+ FWU_PR_NONE = 0,
+ FWU_PR_START = 1,
+ FWU_PR_RECEIVED_RESPONSE,
+ FWU_PR_REQUEST_FAILED,
+ FWU_PR_REQUEST_SENT,
+} EFwuProcessRequest;
+
+typedef enum {
+ FWU_CS_IDLE = 0,
+ FWU_CS_SEND = 1, // sending data from the private request buffer
+ FWU_CS_RECEIVE = 2, // receiving data into the private response buffer
+ FWU_CS_FAIL = 3,
+ FWU_CS_DONE = 4,
+} EFwuCommandState;
+
+// Command requests, triggering command state transitions.
+typedef enum {
+ FWU_CR_NONE = 0,
+ FWU_CR_SEND = 1,
+ FWU_CR_SENDONLY = 2,
+ FWU_CR_EOM_RECEIVED = 3,
+ FWU_CR_RX_OVERFLOW = 4,
+ FWU_CR_INVALID_ESCAPE_SEQ,
+} EFwuCommandRequest;
+
+#define FWU_EOM 0xC0
+#define FWU_RESPONSE_START 0x60
+#define FWU_RESPONSE_SUCCESS 0x01
+
+// PING 09 01 C0 -> 60 09 01 01 C0
+static uint8_t sPingRequest[] = {0x09, 0x01};
+static uint8_t sPingRequestLen = 2;
+
+// SET RECEIPT 02 00 00 C0 -> 60 02 01 C0
+static uint8_t sSetReceiptRequest[] = {0x02, 0x00, 0x00};
+static uint8_t sSetReceiptRequestLen = 3;
+
+// Get the preferred MTU size on the request.
+// GET MTU 07 -> 60 07 01 83 00 C0
+static uint8_t sGetMtuRequest[] = {0x07};
+static uint8_t sGetMtuRequestLen = 1;
+
+// Triggers the last transferred object of the specified type to be selected
+// and queries information (max size, cur offset, cur CRC) about the object.
+// If there's no object of the specified type, the object type is still
+// selected, CRC and offset are 0 in this case.
+// SELECT OBJECT 06 01 C0 -> 60 06 01 00 01 00 00 00 00 00 00 00 00 00 00 C0
+static uint8_t sSelectObjectRequest[] = {0x06, 0x01};
+static uint8_t sSelectObjectRequestLen = 2;
+
+// Creating a command or data object; the target reserves the space, resets the
+// progress since the last Execute command and selects the new object.)
+// CREATE OBJECT 01 01 87 00 00 00 C0 -> 60 01 01 C0
+static uint8_t sCreateObjectRequest[] = {0x01, 0x01, 0x87, 0x00, 0x00, 0x00};
+static uint8_t sCreateObjectRequestLen = 6;
+
+// CRC GET 03 C0 -> 60 03 01 87 00 00 00 38 f4 97 72 C0
+static uint8_t sGetCrcRequest[] = {0x03};
+static uint8_t sGetCrcRequestLen = 1;
+
+// Execute an object after it has been fully transmitted.
+// EXECUTE OBJECT 04 C0 -> 60 04 01 C0
+static uint8_t sExecuteObjectRequest[] = {0x04};
+static uint8_t sExecuteObjectRequestLen = 1;
+
+static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec);
+static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec);
+
+static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu);
+
+// Don't send more than FWU_REQUEST_BUF_SIZE bytes.
+// Don't include the EOM.
+static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len);
+
+static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode);
+
+// static void fwuDebugPrintStatus(TFwu *fwu, char *msg);
+
+static void updateCrc(TFwu *fwu, uint8_t b);
+static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason);
+static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes);
+static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes);
+static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes);
+
+// First function to call to set up the internal state in the FWU structure.
+void fwuInit(TFwu *fwu) {
+ fwu->privateProcessState = FWU_PS_IDLE;
+ fwu->privateProcessRequest = FWU_PR_NONE;
+ fwu->privateCommandState = FWU_CS_IDLE;
+
+ fwu->processStatus = FWU_STATUS_UNDEFINED;
+ fwu->responseStatus = FWU_RSP_OK;
+}
+
+// Execute the firmware update.
+void fwuExec(TFwu *fwu) {
+ // Start with sending a PING command to the target to see if it's there...
+ fwu->privateProcessRequest = FWU_PR_START;
+}
+
+// Call regularly to allow asynchronous processing to continue.
+EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec) {
+ // Nothing to do if processing has failed or successfully completed...
+ if (fwu->processStatus == FWU_STATUS_FAILURE ||
+ fwu->privateProcessState == FWU_PS_FAIL) {
+ return FWU_STATUS_FAILURE;
+ } else if (fwu->processStatus == FWU_STATUS_COMPLETION ||
+ fwu->privateProcessState == FWU_PS_DONE) {
+ return FWU_STATUS_COMPLETION;
+ }
+
+ // Processing is ongoing, yield to FSMs.
+ fwuYieldCommandFsm(fwu, elapsedMillisec);
+ fwuYieldProcessFsm(fwu, elapsedMillisec);
+
+ return fwu->processStatus;
+}
+
+// Call after data from the target has been received.
+void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len) {
+ while (len > 0) {
+ if (fwu->privateResponseLen == FWU_RESPONSE_BUF_SIZE) {
+ fwu->privateCommandRequest = FWU_CR_RX_OVERFLOW;
+ return;
+ }
+ uint8_t c = *bytes++;
+ if (c == FWU_EOM) {
+ fwu->privateCommandRequest = FWU_CR_EOM_RECEIVED;
+ }
+
+ if (c == 0xDB) {
+ fwu->privateResponseEscapeCharacter = 1;
+ } else {
+ if (fwu->privateResponseEscapeCharacter) {
+ fwu->privateResponseEscapeCharacter = 0;
+ if (c == 0xDC) {
+ c = 0xC0;
+ } else if (c == 0xDD) {
+ c = 0xDB;
+ } else {
+ fwu->privateCommandRequest = FWU_CR_INVALID_ESCAPE_SEQ;
+ return;
+ }
+ }
+ fwu->privateResponseBuf[fwu->privateResponseLen++] = c;
+ }
+ len--;
+ }
+}
+
+// Inform the FWU module that it may send maxLen bytes of data to the target.
+void fwuCanSendData(TFwu *fwu, uint8_t maxLen) {
+ fwu->privateSendBufSpace = maxLen;
+}
+
+void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len) {
+ if (fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK &&
+ fwu->privateDataObjectSize == len) {
+ fwu->dataObject = buf;
+ fwu->privateProcessState = FWU_PS_OBJ2_CREATE;
+ }
+}
+
+bool fwuIsReadyForChunk(TFwu *fwu) {
+ return fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+}
+
+static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec) {
+ uint8_t tmpPrivateProcessRequest = fwu->privateProcessRequest;
+ fwu->privateProcessRequest = FWU_PR_NONE;
+
+ // No processing in final states
+ if (fwu->privateProcessState == FWU_PS_DONE ||
+ fwu->privateProcessState == FWU_PS_FAIL) {
+ return;
+ }
+
+ // Failure handling
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_FAILED) {
+ fwu->privateProcessState = FWU_PS_FAIL;
+ fwu->processStatus = FWU_STATUS_FAILURE;
+ return;
+ }
+
+ // Executing the firmware update process.
+ switch (fwu->privateProcessState) {
+ case FWU_PS_IDLE:
+ if (tmpPrivateProcessRequest == FWU_PR_START) {
+ // Send a PING and switch to the PING state to wait for the response.
+ fwuPrepareSendBuffer(fwu, sPingRequest, sPingRequestLen);
+ fwu->privateProcessState = FWU_PS_PING;
+ }
+ break;
+
+ // PING: Check if the nRF52 DFU code is listening
+ case FWU_PS_PING:
+ // Wait for the PING response, then verify it.
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // ID match?
+ if (fwu->privateRequestBuf[1] == fwu->privateResponseBuf[3]) {
+ // Send a SET_RECEIPT and switch to the corresponding state to wait
+ // for the response.
+ fwuPrepareSendBuffer(fwu, sSetReceiptRequest, sSetReceiptRequestLen);
+ fwu->privateProcessState = FWU_PS_RCPT_NOTIF;
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_PING_ID_MISMATCH);
+ }
+ }
+ break;
+
+ // RCPT_NOTIF: Define Receipt settings
+ case FWU_PS_RCPT_NOTIF:
+ // Wait for the SET_RECEIPT response.
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // Send a SET_RECEIPT and switch to the corresponding state to wait for
+ // the response.
+ fwuPrepareSendBuffer(fwu, sGetMtuRequest, sGetMtuRequestLen);
+ fwu->privateProcessState = FWU_PS_MTU;
+ }
+ break;
+
+ // FWU_PS_MTU: Get maximum transmission unit size
+ case FWU_PS_MTU:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateMtuSize =
+ fwuLittleEndianToHost16(&fwu->privateResponseBuf[3]);
+ // Send a SET_RECEIPT and switch to the corresponding state to wait for
+ // the response.
+ sSelectObjectRequest[1] = 0x01; // select object 1 (command object)
+ fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
+ sSelectObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_SELECT;
+ }
+ break;
+
+ // FWU_PS_OBJ1_SELECT: Select the INIT command object
+ case FWU_PS_OBJ1_SELECT:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ uint32_t maxSize = fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ if (maxSize < fwu->commandObjectLen) {
+ fwuSignalFailure(fwu, FWU_RSP_INIT_COMMAND_TOO_LARGE);
+ } else {
+ sCreateObjectRequest[1] = 0x01; // create type 1 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->commandObjectLen,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_CREATE;
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ1_CREATE: Create the INIT command object
+ case FWU_PS_OBJ1_CREATE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateProcessState = FWU_PS_OBJ1_WRITE;
+ fwu->privateObjectBuf = fwu->commandObject;
+ fwu->privateObjectLen = fwu->commandObjectLen;
+ fwu->privateObjectIx = 0;
+ fwu->privateObjectCrc = 0xffffffff;
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ break;
+
+ // FWU_PS_OBJ1_WRITE: Write the INIT command object
+ case FWU_PS_OBJ1_WRITE:
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
+ // more to send?
+ if (fwu->privateObjectIx == fwu->privateObjectLen) {
+ // no - request the CRC of the written data...
+ fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_CRC_GET;
+ } else {
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ1_CRC_GET: Checksum verification
+ case FWU_PS_OBJ1_CRC_GET:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // uint32_t actualLen =
+ // fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ uint32_t actualCks =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
+ if (actualCks == ~fwu->privateObjectCrc) {
+ // Checksum is OK; execute the command!
+ fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
+ sExecuteObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_EXECUTE;
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
+ }
+ }
+ break;
+
+ case FWU_PS_OBJ1_EXECUTE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ sSelectObjectRequest[1] = 0x02; // select object 2 (DATA object)
+ fwu->privateDataObjectOffset = 0; // from the beginning
+ fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
+ sSelectObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_SELECT;
+ }
+ break;
+
+ // FWU_PS_OBJ2_SELECT: Select the DATA object
+ case FWU_PS_OBJ2_SELECT:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateDataObjectMaxSize =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ fwu->privateObjectCrc =
+ 0xffffffff; // do it here because it's global for the entire blob
+ // We'll create and execute multiple data objects, so it's ok if the
+ // actual size is greater than max size.
+ fwu->privateDataObjectSize =
+ (fwu->dataObjectLen -
+ fwu->privateDataObjectOffset); // nof bytes remaining
+ if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
+ fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
+ }
+ sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->privateDataObjectSize,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+ }
+ break;
+ case FWU_PS_OBJ2_WAIT_FOR_CHUNK:
+ break;
+ // FWU_PS_OBJ2_CREATE: Create the DATA object
+ case FWU_PS_OBJ2_CREATE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateProcessState = FWU_PS_OBJ2_WRITE;
+ fwu->privateObjectBuf = fwu->dataObject;
+ fwu->privateObjectLen = fwu->privateDataObjectSize;
+ fwu->privateObjectIx = 0;
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ break;
+
+ // FWU_PS_OBJ2_WRITE: Write the DATA object
+ case FWU_PS_OBJ2_WRITE:
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
+ // more to send?
+ if (fwu->privateObjectIx == fwu->privateObjectLen) {
+ // no - request the CRC of the written data...
+ fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_CRC_GET;
+ } else {
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ2_CRC_GET: Checksum verification
+ case FWU_PS_OBJ2_CRC_GET:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // uint32_t actualLen =
+ // fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ uint32_t actualCks =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
+ if (actualCks == ~fwu->privateObjectCrc) {
+ // Checksum is OK; execute the command!
+ fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
+ sExecuteObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_EXECUTE;
+
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
+ }
+ }
+ break;
+
+ case FWU_PS_OBJ2_EXECUTE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateDataObjectOffset += fwu->privateDataObjectSize;
+ if (fwu->privateDataObjectOffset == fwu->dataObjectLen) {
+ fwu->privateProcessState = FWU_PS_DONE;
+ fwu->processStatus = FWU_STATUS_COMPLETION;
+
+ } else {
+ // We'll create and execute multiple data objects, so it's ok if the
+ // actual size is greater than max size.
+ fwu->privateDataObjectSize =
+ (fwu->dataObjectLen -
+ fwu->privateDataObjectOffset); // nof bytes remaining
+ if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
+ fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
+ }
+ sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->privateDataObjectSize,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+ }
+ }
+ break;
+
+ default:
+ fwu->privateProcessState = FWU_PS_FAIL;
+ break;
+ }
+}
+
+static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec) {
+ uint8_t toSend;
+
+ // Automatically return from final states to IDLE.
+ if (fwu->privateCommandState == FWU_CS_DONE ||
+ fwu->privateCommandState == FWU_CS_FAIL) {
+ fwu->privateCommandState = FWU_CS_IDLE;
+ }
+
+ // Timeout?
+ if (fwu->privateCommandState != FWU_CS_IDLE) {
+ if (fwu->privateCommandTimeoutRemainingMillisec < elapsedMillisec) {
+ fwu->privateCommandTimeoutRemainingMillisec = 0;
+ } else {
+ fwu->privateCommandTimeoutRemainingMillisec -= elapsedMillisec;
+ }
+ if (fwu->privateCommandTimeoutRemainingMillisec == 0) {
+ fwuSignalFailure(fwu, FWU_RSP_TIMEOUT);
+ return;
+ }
+ }
+
+ // Catch errors
+ if (fwu->privateCommandRequest == FWU_CR_RX_OVERFLOW) {
+ fwuSignalFailure(fwu, FWU_RSP_RX_OVERFLOW);
+ return;
+ }
+ if (fwu->privateCommandRequest == FWU_CR_INVALID_ESCAPE_SEQ) {
+ fwuSignalFailure(fwu, FWU_RSP_RX_INVALID_ESCAPE_SEQ);
+ return;
+ }
+
+ switch (fwu->privateCommandState) {
+ case FWU_CS_IDLE:
+ // Ready and waiting for a transmission request.
+ if (fwu->privateCommandRequest == FWU_CR_SEND ||
+ fwu->privateCommandRequest == FWU_CR_SENDONLY) {
+ fwu->privateCommandSendOnly =
+ fwu->privateCommandRequest == FWU_CR_SENDONLY ? 1 : 0;
+ fwu->privateCommandRequest = FWU_CR_NONE;
+ fwu->privateCommandState = FWU_CS_SEND;
+ fwu->privateCommandTimeoutRemainingMillisec =
+ fwu->responseTimeoutMillisec;
+ }
+ break;
+ case FWU_CS_SEND:
+ // Continue sending data until the entire request has been sent.
+ toSend = fwu->privateRequestLen - fwu->privateRequestIx;
+ if (toSend == 0) {
+ if (fwu->privateCommandSendOnly) {
+ // This was a fire-and-forget request; we don't expect a response.
+ fwu->privateProcessRequest = FWU_PR_REQUEST_SENT;
+ fwu->privateCommandState = FWU_CS_DONE;
+ } else {
+ // The request has been sent; wait for response.
+ fwu->privateCommandState = FWU_CS_RECEIVE;
+ }
+ } else if (fwu->privateSendBufSpace > 0) {
+ uint8_t n = fwu->privateSendBufSpace;
+ if (n > toSend) {
+ n = toSend;
+ }
+ fwu->txFunction(fwu, &fwu->privateRequestBuf[fwu->privateRequestIx], n);
+ fwu->privateRequestIx += n;
+ }
+ break;
+ case FWU_CS_RECEIVE:
+ // Continue receiving data until the end-of-message marker has been
+ // received.
+ if (fwu->privateCommandRequest == FWU_CR_EOM_RECEIVED) {
+ fwu->privateCommandRequest = FWU_CR_NONE;
+ EFwuResponseStatus responseStatus = fwuTestReceivedPacketValid(fwu);
+ if (responseStatus == FWU_RSP_OK) {
+ // Inform the process state machine that command reception has
+ // completed.
+ fwu->privateProcessRequest = FWU_PR_RECEIVED_RESPONSE;
+ fwu->privateCommandState = FWU_CS_DONE;
+ } else {
+ fwu->responseStatus = responseStatus;
+ fwu->privateCommandState = FWU_CS_FAIL;
+ }
+ }
+ break;
+ default:
+ fwu->privateCommandState = FWU_CS_FAIL;
+ break;
+ }
+}
+
+static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu) {
+ // 60 C0
+ if (fwu->privateResponseLen < 4) {
+ return FWU_RSP_TOO_SHORT;
+ }
+ if (fwu->privateResponseBuf[0] != FWU_RESPONSE_START) {
+ return FWU_RSP_START_MARKER_MISSING;
+ }
+ if (fwu->privateResponseBuf[1] != fwu->privateRequestBuf[0]) {
+ return FWU_RSP_REQUEST_REFERENCE_INVALID;
+ }
+ if (fwu->privateResponseBuf[2] != FWU_RESPONSE_SUCCESS) {
+ return FWU_RSP_ERROR_RESPONSE;
+ }
+ if (fwu->privateResponseBuf[fwu->privateResponseLen - 1] != FWU_EOM) {
+ return FWU_RSP_END_MARKER_MISSING;
+ }
+ return FWU_RSP_OK;
+}
+
+static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode) {
+ uint16_t bytesTodo = fwu->privateObjectLen - fwu->privateObjectIx;
+ uint16_t bufSpace = FWU_REQUEST_BUF_SIZE - 2;
+
+ uint16_t i;
+ uint8_t *p = &fwu->privateRequestBuf[0];
+
+ *p++ = requestCode;
+ fwu->privateRequestLen = 2; // including requestCode and FWU_EOM
+ fwu->privateRequestIx = 0;
+
+ if (bytesTodo > 32) {
+ bytesTodo = 32;
+ }
+
+ for (i = 0; i < bytesTodo && bufSpace >= 2; i++) {
+ uint8_t b = fwu->privateObjectBuf[fwu->privateObjectIx];
+ // SLIP escape characters: C0->DBDC, DB->DBDD
+ if (b == 0xC0 || b == 0xDB) {
+ *p++ = 0xDB;
+ *p++ = (b == 0xC0) ? 0xDC : 0xDD;
+ fwu->privateRequestLen += 2;
+ bufSpace -= 2;
+ } else {
+ *p++ = b;
+ fwu->privateRequestLen++;
+ bufSpace--;
+ }
+ updateCrc(fwu, b);
+ fwu->privateObjectIx++;
+ }
+
+ *p = FWU_EOM;
+ fwu->privateCommandRequest = FWU_CR_SENDONLY;
+}
+
+static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len) {
+ // TODO assert privateCommandState == FWU_CS_IDLE | _DONE | _FAIL
+ // TODO assert len <= FWU_REQUEST_BUF_SIZE
+
+ uint8_t i;
+ uint8_t *p = &fwu->privateRequestBuf[0];
+
+ fwu->privateRequestIx = 0;
+ fwu->privateRequestLen = len + 1;
+ fwu->privateResponseLen = 0;
+
+ // Copy the data into our internal buffer.
+ for (i = 0; i < len; i++) {
+ *p++ = *data++;
+ }
+
+ // Add the end-of-message marker.
+ *p = FWU_EOM;
+
+ // Ready to send!
+ fwu->privateCommandRequest = FWU_CR_SEND;
+}
+
+static void updateCrc(TFwu *fwu, uint8_t b) {
+ uint8_t i;
+ uint32_t crc = fwu->privateObjectCrc;
+ crc ^= b;
+ for (i = 0; i < 8; i++) {
+ uint32_t m = (crc & 1) ? 0xffffffff : 0;
+ crc = (crc >> 1) ^ (0xedb88320u & m);
+ }
+ fwu->privateObjectCrc = crc;
+}
+
+static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason) {
+ fwu->responseStatus = reason;
+ fwu->privateCommandState = FWU_CS_FAIL;
+ // Signal failure to process state machine
+ fwu->privateProcessRequest = FWU_PR_REQUEST_FAILED;
+}
+
+static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes) {
+ return bytes[0] | ((uint16_t)bytes[1] << 8);
+}
+
+static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes) {
+ return bytes[0] | ((uint16_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) |
+ ((uint32_t)bytes[3] << 24);
+}
+
+static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes) {
+ uint8_t i;
+ for (i = 0; i < 4; i++) {
+ *bytes++ = v & 0xff;
+ v = v >> 8;
+ }
+}
diff --git a/core/embed/lib/ble/fwu.h b/core/embed/lib/ble/fwu.h
new file mode 100644
index 000000000..a380b40c1
--- /dev/null
+++ b/core/embed/lib/ble/fwu.h
@@ -0,0 +1,128 @@
+//
+// fwu.h
+// nrf52-dfu
+//
+// C library for the Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#ifndef __FWU_H__
+#define __FWU_H__ 1
+
+#include
+#include
+
+struct SFwu;
+
+#define FWU_REQUEST_BUF_SIZE 67
+#define FWU_RESPONSE_BUF_SIZE 16
+
+typedef enum {
+ FWU_STATUS_UNDEFINED = 0,
+ FWU_STATUS_FAILURE = 1,
+ FWU_STATUS_COMPLETION = 2,
+} EFwuProcessStatus;
+
+typedef enum {
+ FWU_RSP_OK = 0,
+ FWU_RSP_TOO_SHORT = 1,
+ FWU_RSP_START_MARKER_MISSING = 2,
+ FWU_RSP_END_MARKER_MISSING = 3,
+ FWU_RSP_REQUEST_REFERENCE_INVALID = 4,
+ FWU_RSP_ERROR_RESPONSE = 5,
+ FWU_RSP_TIMEOUT = 6,
+ FWU_RSP_PING_ID_MISMATCH = 7,
+ FWU_RSP_RX_OVERFLOW = 8,
+ FWU_RSP_INIT_COMMAND_TOO_LARGE = 9,
+ FWU_RSP_CHECKSUM_ERROR = 10,
+ FWU_RSP_DATA_OBJECT_TOO_LARGE = 11,
+ FWU_RSP_RX_INVALID_ESCAPE_SEQ = 12,
+} EFwuResponseStatus;
+
+typedef void (*FTxFunction)(struct SFwu *fwu, uint8_t *buf, uint8_t len);
+
+typedef struct SFwu {
+ // --- public - define these before calling fwuInit ---
+ // .dat
+ uint8_t *commandObject;
+ uint32_t commandObjectLen;
+ // .bin
+ uint8_t *dataObject;
+ uint32_t dataObjectLen;
+ // Sending bytes to the target
+ FTxFunction txFunction;
+ // Timeout when waiting for a response from the target
+ uint32_t responseTimeoutMillisec;
+ // --- public - result codes
+ // Overall process status code
+ EFwuProcessStatus processStatus;
+ // Response status code
+ EFwuResponseStatus responseStatus;
+ // --- private, don't modify ---
+ uint32_t privateDataObjectOffset;
+ uint32_t privateDataObjectSize;
+ uint32_t privateDataObjectMaxSize;
+ uint8_t privateProcessState;
+ uint8_t privateCommandState;
+ uint8_t privateCommandSendOnly;
+ uint32_t privateCommandTimeoutRemainingMillisec;
+ uint8_t privateRequestBuf[FWU_REQUEST_BUF_SIZE + 1];
+ uint8_t privateRequestLen;
+ uint8_t privateRequestIx;
+ uint8_t privateResponseBuf[FWU_RESPONSE_BUF_SIZE];
+ uint8_t privateResponseEscapeCharacter;
+ uint8_t privateResponseLen;
+ uint32_t privateResponseTimeElapsedMillisec;
+ uint8_t privateSendBufSpace;
+ uint8_t privateProcessRequest;
+ uint8_t privateCommandRequest;
+ uint16_t privateMtuSize;
+ // sending a large object buffer
+ uint8_t *privateObjectBuf;
+ uint32_t privateObjectLen;
+ uint32_t privateObjectIx;
+ uint32_t privateObjectCrc;
+} TFwu;
+
+// First function to call to set up the internal state in the FWU structure.
+void fwuInit(TFwu *fwu);
+
+// Execute the firmware update.
+void fwuExec(TFwu *fwu);
+
+// Call regularly to allow asynchronous processing to continue.
+EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec);
+
+// Call after data from the target has been received.
+void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len);
+
+// Inform the FWU module that it may send maxLen bytes of data to the target.
+void fwuCanSendData(TFwu *fwu, uint8_t maxLen);
+
+// Call to send a chunk of the data object to the target.
+void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len);
+
+// Call to check if a chunk of the data object can be sent to the target.
+bool fwuIsReadyForChunk(TFwu *fwu);
+
+#endif // __FWU_H__
diff --git a/core/embed/lib/ble/int_comm_defs.h b/core/embed/lib/ble/int_comm_defs.h
new file mode 100644
index 000000000..ea5d90fc2
--- /dev/null
+++ b/core/embed/lib/ble/int_comm_defs.h
@@ -0,0 +1,48 @@
+#ifndef __INT_COMM_DEFS__
+#define __INT_COMM_DEFS__
+
+#define BLE_PACKET_SIZE (244)
+#define USB_DATA_SIZE (64)
+
+#define COMM_HEADER_SIZE (3)
+#define COMM_FOOTER_SIZE (1)
+#define OVERHEAD_SIZE (COMM_HEADER_SIZE + COMM_FOOTER_SIZE)
+#define UART_PACKET_SIZE (USB_DATA_SIZE + OVERHEAD_SIZE)
+
+#define EOM (0x55)
+#define INTERNAL_EVENT (0xA2)
+#define EXTERNAL_MESSAGE (0xA1)
+#define INTERNAL_MESSAGE (0xA0)
+
+typedef struct {
+ uint8_t msg_id;
+ uint8_t connected;
+ uint8_t advertising;
+ uint8_t advertising_whitelist;
+
+ uint8_t peer_count;
+ uint8_t reserved[2];
+ uint8_t sd_version_number;
+
+ uint16_t sd_company_id;
+ uint16_t sd_subversion_number;
+
+ uint32_t app_version;
+ uint32_t bld_version;
+} event_status_msg_t;
+
+typedef enum {
+ INTERNAL_EVENT_STATUS = 0x01,
+ INTERNAL_EVENT_SUCCESS = 0x02,
+ INTERNAL_EVENT_FAILURE = 0x03,
+} InternalEvent_t;
+
+typedef enum {
+ INTERNAL_CMD_SEND_STATE = 0x00,
+ INTERNAL_CMD_ADVERTISING_ON = 0x01,
+ INTERNAL_CMD_ADVERTISING_OFF = 0x02,
+ INTERNAL_CMD_ERASE_BONDS = 0x03,
+ INTERNAL_CMD_DISCONNECT = 0x04,
+ INTERNAL_CMD_ACK = 0x05,
+} InternalCmd_t;
+#endif
diff --git a/core/embed/lib/ble/messages.c b/core/embed/lib/ble/messages.c
new file mode 100644
index 000000000..a91bdc449
--- /dev/null
+++ b/core/embed/lib/ble/messages.c
@@ -0,0 +1,127 @@
+
+#include
+#include
+
+#include "ble_hal.h"
+#include "common.h"
+#include "int_comm_defs.h"
+#include "messages.h"
+#include "state.h"
+
+void process_poll(uint8_t *data, uint32_t len) {
+ uint8_t cmd = data[0];
+
+ switch (cmd) {
+ case INTERNAL_EVENT_STATUS: {
+ event_status_msg_t *msg = (event_status_msg_t *)data;
+ set_status(msg);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+bool wait_for_answer(void) {
+ uint8_t buf[64] = {0};
+
+ uint32_t ticks_start = hal_ticks_ms();
+ int len = 0;
+
+ while (len == 0) {
+ if (hal_ticks_ms() - ticks_start > 1000) {
+ // timeout
+ return false;
+ }
+
+ len = ble_int_event_receive(buf, sizeof(buf));
+
+ if (len > 0) {
+ process_poll(buf, len);
+ }
+ }
+
+ return true;
+}
+
+bool ble_initialize(void) {
+ if (!ble_firmware_running()) {
+ return false;
+ }
+ send_state_request();
+
+ return wait_for_answer();
+}
+
+void send_state_request(void) {
+ uint8_t cmd = INTERNAL_CMD_SEND_STATE;
+ ble_int_comm_send(&cmd, sizeof(cmd), INTERNAL_EVENT);
+}
+
+void send_advertising_on(bool whitelist) {
+ uint8_t data[2];
+ data[0] = INTERNAL_CMD_ADVERTISING_ON;
+ data[1] = whitelist ? 1 : 0;
+ ble_int_comm_send(data, sizeof(data), INTERNAL_EVENT);
+}
+
+void send_advertising_off(void) {
+ uint8_t cmd = INTERNAL_CMD_ADVERTISING_OFF;
+ ble_int_comm_send(&cmd, sizeof(cmd), INTERNAL_EVENT);
+}
+
+bool send_erase_bonds(void) {
+ if (!ble_firmware_running()) {
+ return false;
+ }
+ uint8_t cmd = INTERNAL_CMD_ERASE_BONDS;
+ ble_int_comm_send(&cmd, sizeof(cmd), INTERNAL_EVENT);
+
+ uint8_t buf[64] = {0};
+
+ uint32_t ticks_start = hal_ticks_ms();
+ int len = 0;
+
+ while (len == 0) {
+ len = ble_int_event_receive(buf, sizeof(buf));
+
+ if (hal_ticks_ms() - ticks_start > 1000) {
+ // timeout
+ return false;
+ }
+ }
+
+ if (buf[0] == INTERNAL_EVENT_SUCCESS) {
+ return true;
+ }
+
+ return false;
+}
+
+bool send_disconnect(void) {
+ if (!ble_firmware_running()) {
+ return false;
+ }
+ uint8_t cmd = INTERNAL_CMD_DISCONNECT;
+ ble_int_comm_send(&cmd, sizeof(cmd), INTERNAL_EVENT);
+
+ uint8_t buf[64] = {0};
+
+ uint32_t ticks_start = hal_ticks_ms();
+ int len = 0;
+
+ while (len == 0) {
+ len = ble_int_event_receive(buf, sizeof(buf));
+
+ if (hal_ticks_ms() - ticks_start > 1000) {
+ // timeout
+ return false;
+ }
+ }
+
+ if (buf[0] == INTERNAL_EVENT_SUCCESS) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/core/embed/lib/ble/messages.h b/core/embed/lib/ble/messages.h
new file mode 100644
index 000000000..a7a8a167c
--- /dev/null
+++ b/core/embed/lib/ble/messages.h
@@ -0,0 +1,22 @@
+#ifndef __BLE_MESSAGES__
+#define __BLE_MESSAGES__
+
+#include
+
+bool ble_initialize(void);
+
+bool wait_for_answer(void);
+
+void process_poll(uint8_t *data, uint32_t len);
+
+void send_state_request(void);
+
+void send_advertising_on(bool whitelist);
+
+void send_advertising_off(void);
+
+bool send_erase_bonds(void);
+
+bool send_disconnect(void);
+
+#endif
diff --git a/core/embed/lib/ble/state.c b/core/embed/lib/ble/state.c
new file mode 100644
index 000000000..a2d1f916a
--- /dev/null
+++ b/core/embed/lib/ble/state.c
@@ -0,0 +1,70 @@
+#include "ble/state.h"
+#include "ble_hal.h"
+#include "messages.h"
+
+static bool ble_state_connected = false;
+static bool ble_state_initialized = false;
+static bool ble_advertising_wanted = false;
+static bool ble_advertising_wl_wanted = false;
+static bool ble_advertising = false;
+static bool ble_advertising_wl = false;
+static bool ble_dfu_mode = false;
+static uint8_t ble_peer_count = 0;
+
+bool ble_connected(void) {
+ return ble_state_connected && ble_firmware_running();
+}
+
+void set_connected(bool connected) {}
+
+static void configure_ble(bool advertising, bool whitelist) {
+ if (ble_advertising != advertising || (ble_advertising_wl != whitelist)) {
+ if (advertising) {
+ send_advertising_on(whitelist);
+ }
+ if (!advertising && ble_advertising) {
+ send_advertising_off();
+ }
+ }
+
+ ble_advertising_wanted = advertising;
+ ble_advertising_wl_wanted = whitelist;
+}
+
+void set_status(event_status_msg_t *msg) {
+ ble_state_connected = msg->connected;
+ ble_peer_count = msg->peer_count;
+ ble_advertising = msg->advertising;
+ ble_advertising_wl = msg->advertising_whitelist;
+
+ set_initialized(true);
+
+ configure_ble(ble_advertising_wanted, ble_advertising_wl_wanted);
+}
+
+void set_initialized(bool initialized) { ble_state_initialized = initialized; }
+
+bool ble_initialized(void) {
+ return ble_state_initialized && ble_firmware_running();
+}
+
+void start_advertising(bool whitelist) { configure_ble(true, whitelist); }
+
+void auto_start_advertising(void) {
+ if (ble_peer_count > 0) {
+ configure_ble(true, true);
+ } else {
+ configure_ble(false, false);
+ }
+}
+
+void stop_advertising(void) { configure_ble(false, false); }
+
+void ble_set_dfu_mode(bool dfu) { ble_dfu_mode = dfu; }
+
+bool is_ble_dfu_mode(void) { return ble_dfu_mode; }
+
+void ble_stop_all_comm(void) {
+ stop_advertising();
+ ble_comm_stop();
+}
diff --git a/core/embed/lib/ble/state.h b/core/embed/lib/ble/state.h
new file mode 100644
index 000000000..ae21b383a
--- /dev/null
+++ b/core/embed/lib/ble/state.h
@@ -0,0 +1,29 @@
+#ifndef __BLE_STATE__
+#define __BLE_STATE__
+
+#include
+#include
+
+#include "int_comm_defs.h"
+
+bool ble_initialized(void);
+
+void set_initialized(bool initialized);
+
+bool ble_connected(void);
+
+void set_status(event_status_msg_t *msg);
+
+void start_advertising(bool whitelist);
+
+void auto_start_advertising(void);
+
+void stop_advertising(void);
+
+void ble_set_dfu_mode(bool dfu);
+
+bool is_ble_dfu_mode(void);
+
+void ble_stop_all_comm(void);
+
+#endif
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index 4e7ca7bdc..92e022b76 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -25,6 +25,7 @@ clippy = []
jpeg = []
disp_i8080_8bit_dw = [] # write pixels directly to peripheral
disp_i8080_16bit_dw = [] # write pixels directly to peripheral
+ble = []
debug = ["ui_debug"]
sbu = []
sd_card = []
diff --git a/core/embed/trezorhal/ble_hal.h b/core/embed/trezorhal/ble_hal.h
new file mode 100644
index 000000000..b3c22e752
--- /dev/null
+++ b/core/embed/trezorhal/ble_hal.h
@@ -0,0 +1,32 @@
+#ifndef __BLE_COMM_H__
+#define __BLE_COMM_H__
+
+#include
+#include
+#include "ble/int_comm_defs.h"
+
+void ble_comm_init(void);
+
+void ble_comm_start(void);
+void ble_comm_stop(void);
+bool ble_comm_running(void);
+
+void ble_comm_send(uint8_t *data, uint32_t len);
+uint32_t ble_comm_receive(uint8_t *data, uint32_t len);
+
+void ble_int_comm_send(uint8_t *data, uint32_t len, uint8_t message_type);
+uint32_t ble_int_event_receive(uint8_t *data, uint32_t len);
+uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len);
+uint32_t ble_ext_comm_receive(uint8_t *data, uint32_t len);
+
+void ble_event_poll(void);
+
+bool ble_firmware_running(void);
+
+bool ble_reset_to_bootloader(void);
+
+bool ble_reset(void);
+void ble_signal_running(void);
+void ble_signal_off(void);
+
+#endif
diff --git a/core/embed/trezorhal/unix/ble.c b/core/embed/trezorhal/unix/ble.c
new file mode 100644
index 000000000..5c1237e0b
--- /dev/null
+++ b/core/embed/trezorhal/unix/ble.c
@@ -0,0 +1,32 @@
+#include "ble_hal.h"
+
+// TODO: send data over UDP
+
+static bool firmware_running = true;
+
+void ble_comm_init(void) {}
+void ble_comm_start(void) {}
+void ble_comm_stop(void) {}
+
+void ble_comm_send(uint8_t *data, uint32_t len) {}
+
+uint32_t ble_comm_receive(uint8_t *data, uint32_t len) { return 0; }
+
+void ble_int_comm_send(uint8_t *data, uint32_t len, uint8_t message_type) {}
+uint32_t ble_int_event_receive(uint8_t *data, uint32_t len) { return 0; }
+uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len) { return 0; }
+uint32_t ble_ext_comm_receive(uint8_t *data, uint32_t len) { return 0; }
+
+void ble_event_poll(void) {}
+
+bool ble_firmware_running(void) { return firmware_running; }
+
+bool ble_reset_to_bootloader(void) {
+ firmware_running = false;
+ return true;
+}
+
+bool ble_reset(void) {
+ firmware_running = true;
+ return true;
+}
diff --git a/core/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi
index e84de507c..c0b42a5d7 100644
--- a/core/mocks/generated/trezorio/__init__.pyi
+++ b/core/mocks/generated/trezorio/__init__.pyi
@@ -190,7 +190,7 @@ class WebUSB:
"""
Sends message using USB WebUSB (device) or UDP (emulator).
"""
-from . import fatfs, sdcard
+from . import fatfs, sdcard, ble
POLL_READ: int # wait until interface is readable and return read data
POLL_WRITE: int # wait until interface is writable
TOUCH: int # interface id of the touch events
@@ -203,4 +203,5 @@ BUTTON_RELEASED: int # button up event
BUTTON_LEFT: int # button number of left button
BUTTON_RIGHT: int # button number of right button
USB_CHECK: int # interface id for check of USB data connection
-WireInterface = Union[HID, WebUSB]
+BLE_CHECK: int # interface id for check of BLE data connection
+WireInterface = Union[HID, WebUSB, BleInterface]
diff --git a/core/mocks/generated/trezorio/ble.pyi b/core/mocks/generated/trezorio/ble.pyi
new file mode 100644
index 000000000..f810c6270
--- /dev/null
+++ b/core/mocks/generated/trezorio/ble.pyi
@@ -0,0 +1,69 @@
+from typing import *
+INTERNAL: int # interface id for internal (stm<->nrf) connection
+EXTERNAL: int # interface id for ble client connection
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def update_init(data: bytes, binsize: int) -> bool:
+ """
+ Initializes the BLE firmware update. Returns true if the update finished
+ with only the initial chunk. False means calling `update_chunk` is
+ expected.
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def update_chunk(chunk: bytes) -> bool:
+ """
+ Writes next chunk of BLE firmware update. Returns true if the update is
+ finished, or false if more chunks are expected.
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def write_int(self, msg: bytes) -> int:
+ """
+ Sends internal message to NRF.
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def write_ext(self, msg: bytes) -> int:
+ """
+ Sends message over BLE
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def erase_bonds() -> None:
+ """
+ Erases all BLE bonds
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def start_comm() -> None:
+ """
+ Start communication with BLE chip
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def start_advertising(whitelist: bool) -> None:
+ """
+ Start advertising
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def stop_advertising(whitelist: bool) -> None:
+ """
+ Stop advertising
+ """
+
+
+# extmod/modtrezorio/modtrezorio-ble.h
+def disconnect() -> None:
+ """
+ Disconnect BLE
+ """
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index 73d9bda80..9308aab98 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -112,6 +112,7 @@ VERSION_MINOR: int
"""Minor version."""
VERSION_PATCH: int
"""Patch version."""
+USE_BLE: bool
USE_SD_CARD: bool
"""Whether the hardware supports SD card."""
USE_BACKLIGHT: bool
diff --git a/core/site_scons/site_tools/micropython/__init__.py b/core/site_scons/site_tools/micropython/__init__.py
index 80a03ca6b..2e5510fc0 100644
--- a/core/site_scons/site_tools/micropython/__init__.py
+++ b/core/site_scons/site_tools/micropython/__init__.py
@@ -39,6 +39,7 @@ def generate(env):
is_t2b1 = env["TREZOR_MODEL"] == "R"
backlight = env["backlight"]
optiga = env["optiga"]
+ ble = env["ble"]
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
interim = f"{target[:-4]}.i" # replace .mpy with .i
@@ -48,6 +49,7 @@ def generate(env):
rf"-e 's/utils\.BITCOIN_ONLY/{btc_only}/g'",
rf"-e 's/utils\.USE_BACKLIGHT/{backlight}/g'",
rf"-e 's/utils\.USE_OPTIGA/{optiga}/g'",
+ rf"-e 's/utils\.USE_BLE/{ble}/g'",
rf"-e 's/utils\.UI_LAYOUT == \"TT\"/{layout_tt}/g'",
rf"-e 's/utils\.UI_LAYOUT == \"TR\"/{layout_tr}/g'",
r"-e 's/if TYPE_CHECKING/if False/'",
diff --git a/core/src/all_modules.py b/core/src/all_modules.py
index aba9d6a11..241c56450 100644
--- a/core/src/all_modules.py
+++ b/core/src/all_modules.py
@@ -321,6 +321,20 @@ apps.management.backup_device
import apps.management.backup_device
apps.management.backup_types
import apps.management.backup_types
+apps.management.ble
+import apps.management.ble
+apps.management.ble.comparison_request
+import apps.management.ble.comparison_request
+apps.management.ble.disconnect
+import apps.management.ble.disconnect
+apps.management.ble.erase_bonds
+import apps.management.ble.erase_bonds
+apps.management.ble.pairing_request
+import apps.management.ble.pairing_request
+apps.management.ble.repair_request
+import apps.management.ble.repair_request
+apps.management.ble.upload_firmware_init
+import apps.management.ble.upload_firmware_init
apps.management.change_pin
import apps.management.change_pin
apps.management.change_wipe_code
diff --git a/core/src/apps/management/ble/__init__.py b/core/src/apps/management/ble/__init__.py
new file mode 100644
index 000000000..f7af657f0
--- /dev/null
+++ b/core/src/apps/management/ble/__init__.py
@@ -0,0 +1,35 @@
+from micropython import const
+from trezorio import ble
+
+from trezor import wire
+
+_PROTOBUF_BUFFER_SIZE_INTERNAL = const(256)
+_WIRE_BUFFER_INTERNAL = bytearray(_PROTOBUF_BUFFER_SIZE_INTERNAL)
+
+
+class BleInterfaceInternal:
+ IS_BLE_INTERNAL = True
+
+ def iface_num(self) -> int:
+ return ble.INTERNAL
+
+ def write(self, msg: bytes) -> int:
+ return ble.write_int(self, msg)
+
+
+class BleInterfaceExternal:
+ def iface_num(self) -> int:
+ return ble.EXTERNAL
+
+ def write(self, msg: bytes) -> int:
+ return ble.write_ext(self, msg)
+
+
+# interface used for trezor wire protocol
+iface_ble_int = BleInterfaceInternal()
+iface_ble_ext = BleInterfaceExternal()
+
+
+def boot() -> None:
+ wire.setup(iface_ble_int, buffer=_WIRE_BUFFER_INTERNAL)
+ wire.setup(iface_ble_ext)
diff --git a/core/src/apps/management/ble/comparison_request.py b/core/src/apps/management/ble/comparison_request.py
new file mode 100644
index 000000000..16872ea94
--- /dev/null
+++ b/core/src/apps/management/ble/comparison_request.py
@@ -0,0 +1,19 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLEComparisonRequest, Success
+
+
+async def comparison_request(msg: BLEComparisonRequest) -> Success:
+ from trezor.messages import Success
+ from trezor.ui.layouts import confirm_action
+ from trezor.wire import context
+
+ await context.with_context(
+ None,
+ confirm_action(
+ "", "DO THE NUMBERS MATCH?", description=msg.key.decode("utf-8")
+ ),
+ )
+
+ return Success()
diff --git a/core/src/apps/management/ble/disconnect.py b/core/src/apps/management/ble/disconnect.py
new file mode 100644
index 000000000..b24a88052
--- /dev/null
+++ b/core/src/apps/management/ble/disconnect.py
@@ -0,0 +1,16 @@
+from trezorio import ble
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLEDisconnect, Success
+
+
+async def disconnect(_msg: BLEDisconnect) -> Success:
+ from trezor.messages import Success
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action("ble_disconnect", "DISCONNECT")
+
+ ble.disconnect()
+
+ return Success()
diff --git a/core/src/apps/management/ble/erase_bonds.py b/core/src/apps/management/ble/erase_bonds.py
new file mode 100644
index 000000000..3161d9228
--- /dev/null
+++ b/core/src/apps/management/ble/erase_bonds.py
@@ -0,0 +1,16 @@
+from trezorio import ble
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLEEraseBonds, Success
+
+
+async def erase_bonds(_msg: BLEEraseBonds) -> Success:
+ from trezor.messages import Success
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action("ble_erase_bonds", "ERASE BONDS")
+
+ ble.erase_bonds()
+
+ return Success()
diff --git a/core/src/apps/management/ble/pairing_request.py b/core/src/apps/management/ble/pairing_request.py
new file mode 100644
index 000000000..c0d1f0852
--- /dev/null
+++ b/core/src/apps/management/ble/pairing_request.py
@@ -0,0 +1,19 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLEAuthKey, BLEPairingRequest
+
+
+async def pairing_request(_msg: BLEPairingRequest) -> BLEAuthKey:
+ from trezor.messages import BLEAuthKey
+ from trezor.ui.layouts import request_pin_on_device
+ from trezor.wire import context
+
+ pin = await context.with_context(
+ None, request_pin_on_device("PAIRING", None, True, False)
+ )
+
+ if len(pin) != 6:
+ pin = "000000"
+
+ return BLEAuthKey(key=pin.encode())
diff --git a/core/src/apps/management/ble/repair_request.py b/core/src/apps/management/ble/repair_request.py
new file mode 100644
index 000000000..b6c4b40ae
--- /dev/null
+++ b/core/src/apps/management/ble/repair_request.py
@@ -0,0 +1,14 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLERepairRequest, Success
+
+
+async def repair_request(_msg: BLERepairRequest) -> Success:
+ from trezor.messages import Success
+ from trezor.ui.layouts import confirm_action
+ from trezor.wire import context
+
+ await context.with_context(None, confirm_action("", "RE-PAIR DEVICE"))
+
+ return Success()
diff --git a/core/src/apps/management/ble/upload_firmware_init.py b/core/src/apps/management/ble/upload_firmware_init.py
new file mode 100644
index 000000000..7ab1234da
--- /dev/null
+++ b/core/src/apps/management/ble/upload_firmware_init.py
@@ -0,0 +1,64 @@
+from trezorio import ble
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import BLEUploadFirmwareChunk, BLEUploadFirmwareInit, Success
+
+
+async def upload_firmware_chunk(msg: BLEUploadFirmwareChunk) -> int:
+ result = ble.update_chunk(msg.data)
+
+ return result
+
+
+async def upload_firmware_init(msg: BLEUploadFirmwareInit) -> Success:
+ from trezor.enums import ButtonRequestType
+ from trezor.messages import (
+ BLEUploadFirmwareChunk,
+ BLEUploadFirmwareNextChunk,
+ Success,
+ )
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action(
+ "confirm_upload_ble_firmware",
+ "Upload BLE firmware",
+ "",
+ "Update BLE FW?\n",
+ reverse=True,
+ verb="Confirm",
+ br_code=ButtonRequestType.Other,
+ )
+
+ from trezor.enums import MessageType
+ from trezor.ui.layouts import progress
+ from trezor.wire.context import get_context
+
+ ctx = get_context()
+
+ progress_layout = progress.progress("Uploading...")
+
+ upload_progress = 0
+
+ p = int(1000 * upload_progress / msg.binsize)
+ progress_layout.report(p)
+
+ finished = ble.update_init(msg.init_data, msg.binsize)
+ while not finished:
+ await ctx.write(BLEUploadFirmwareNextChunk())
+
+ received_msg = await ctx.read(
+ (MessageType.BLEUploadFirmwareChunk,),
+ BLEUploadFirmwareChunk,
+ )
+
+ finished = await upload_firmware_chunk(received_msg)
+
+ upload_progress += len(received_msg.data)
+ p = int(1000 * upload_progress / msg.binsize)
+ progress_layout.report(p)
+ del received_msg
+
+ progress_layout.report(1000)
+
+ return Success(message="BLE firmware update successful")
diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py
index 8b373c4c4..9df081cb5 100644
--- a/core/src/apps/workflow_handlers.py
+++ b/core/src/apps/workflow_handlers.py
@@ -14,7 +14,7 @@ def register(wire_type: int, handler: Handler[Msg]) -> None:
workflow_handlers[wire_type] = handler
-def _find_message_handler_module(msg_type: int) -> str:
+def _find_message_handler_module(msg_type: int, iface: WireInterface) -> str:
"""Statically find the appropriate workflow handler.
For now, new messages must be registered by hand in the if-elif manner below.
@@ -27,6 +27,24 @@ def _find_message_handler_module(msg_type: int) -> str:
from trezor import utils
from trezor.enums import MessageType
+ if utils.USE_BLE:
+ if getattr(iface, "IS_BLE_INTERNAL", False):
+ if msg_type == MessageType.BLEPairingRequest:
+ return "apps.management.ble.pairing_request"
+ if msg_type == MessageType.BLERepairRequest:
+ return "apps.management.ble.repair_request"
+ if msg_type == MessageType.BLEComparisonRequest:
+ return "apps.management.ble.comparison_request"
+ # only the messages above are accepted internally
+ raise ValueError
+
+ if msg_type == MessageType.BLEUploadFirmwareInit:
+ return "apps.management.ble.upload_firmware_init"
+ if msg_type == MessageType.BLEEraseBonds:
+ return "apps.management.ble.erase_bonds"
+ if msg_type == MessageType.BLEDisconnect:
+ return "apps.management.ble.disconnect"
+
# debug
if __debug__ and msg_type == MessageType.LoadDevice:
return "apps.debug.load_device"
@@ -207,7 +225,7 @@ def find_registered_handler(iface: WireInterface, msg_type: int) -> Handler | No
return workflow_handlers[msg_type]
try:
- modname = _find_message_handler_module(msg_type)
+ modname = _find_message_handler_module(msg_type, iface)
handler_name = modname[modname.rfind(".") + 1 :]
module = __import__(modname, None, None, (handler_name,), 0)
return getattr(module, handler_name)
diff --git a/core/src/main.py b/core/src/main.py
index f0c9a54b0..96992560e 100644
--- a/core/src/main.py
+++ b/core/src/main.py
@@ -49,6 +49,10 @@ import storage.device
usb.bus.open(storage.device.get_device_id())
+if utils.USE_BLE:
+ from trezorio import ble
+ ble.start_comm()
+
# run the endless loop
while True:
with unimport_manager:
diff --git a/core/src/session.py b/core/src/session.py
index 4f161c36c..4826269c2 100644
--- a/core/src/session.py
+++ b/core/src/session.py
@@ -25,6 +25,12 @@ wire.setup(usb.iface_wire)
if __debug__:
wire.setup(usb.iface_debug, is_debug_session=True)
+if utils.USE_BLE:
+ from apps.management import ble
+
+ ble.boot()
+
+
loop.run()
if __debug__:
diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py
index eb90879f1..528a1f9ef 100644
--- a/core/src/trezor/utils.py
+++ b/core/src/trezor/utils.py
@@ -9,6 +9,7 @@ from trezorutils import ( # noqa: F401
SCM_REVISION,
UI_LAYOUT,
USE_BACKLIGHT,
+ USE_BLE,
USE_OPTIGA,
USE_SD_CARD,
VERSION_MAJOR,
diff --git a/core/src/trezor/wire/__init__.py b/core/src/trezor/wire/__init__.py
index e18755fe5..8c8352f35 100644
--- a/core/src/trezor/wire/__init__.py
+++ b/core/src/trezor/wire/__init__.py
@@ -65,9 +65,13 @@ if TYPE_CHECKING:
EXPERIMENTAL_ENABLED = False
-def setup(iface: WireInterface, is_debug_session: bool = False) -> None:
+def setup(
+ iface: WireInterface,
+ is_debug_session: bool = False,
+ buffer: bytearray | None = None,
+) -> None:
"""Initialize the wire stack on passed USB interface."""
- loop.schedule(handle_session(iface, codec_v1.SESSION_ID, is_debug_session))
+ loop.schedule(handle_session(iface, codec_v1.SESSION_ID, is_debug_session, buffer))
def wrap_protobuf_load(
@@ -93,6 +97,7 @@ def wrap_protobuf_load(
_PROTOBUF_BUFFER_SIZE = const(8192)
WIRE_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE)
+WIRE_BUFFER_LOCK = context.BufferLock()
if __debug__:
PROTOBUF_BUFFER_SIZE_DEBUG = 1024
@@ -205,14 +210,20 @@ async def _handle_single_message(
async def handle_session(
- iface: WireInterface, session_id: int, is_debug_session: bool = False
+ iface: WireInterface,
+ session_id: int,
+ is_debug_session: bool = False,
+ buffer: bytearray | None = None,
) -> None:
- if __debug__ and is_debug_session:
- ctx_buffer = WIRE_BUFFER_DEBUG
- else:
- ctx_buffer = WIRE_BUFFER
+ buffer_lock = None
+ if buffer is None:
+ if __debug__ and is_debug_session:
+ buffer = WIRE_BUFFER_DEBUG
+ else:
+ buffer = WIRE_BUFFER
+ buffer_lock = WIRE_BUFFER_LOCK
- ctx = context.Context(iface, session_id, ctx_buffer)
+ ctx = context.Context(iface, session_id, buffer, buffer_lock)
next_msg: codec_v1.Message | None = None
if __debug__ and is_debug_session:
diff --git a/core/src/trezor/wire/context.py b/core/src/trezor/wire/context.py
index 9df347354..29f46fdb9 100644
--- a/core/src/trezor/wire/context.py
+++ b/core/src/trezor/wire/context.py
@@ -39,6 +39,27 @@ if TYPE_CHECKING:
LoadedMessageType = TypeVar("LoadedMessageType", bound=protobuf.MessageType)
+class BufferLock:
+ def __init__(self) -> None:
+ self.in_use = False
+
+ def __enter__(self) -> None:
+ if self.in_use:
+ raise RuntimeError("wire buffer already used by another context")
+ self.in_use = True
+
+ def __exit__(self, exc_type: Any, value: Any, traceback: Any) -> None:
+ self.in_use = False
+
+
+class DummyLock:
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, exc_type: Any, value: Any, traceback: Any) -> None:
+ pass
+
+
class UnexpectedMessage(Exception):
"""A message was received that is not part of the current workflow.
@@ -58,14 +79,18 @@ class Context:
(i.e., wire, debug, single BT connection, etc.)
"""
- def __init__(self, iface: WireInterface, sid: int, buffer: bytearray) -> None:
+ def __init__(
+ self, iface: WireInterface, sid: int, buffer: bytearray, buffer_lock: Any = None
+ ) -> None:
self.iface = iface
self.sid = sid
self.buffer = buffer
+ self.buffer_lock = buffer_lock or DummyLock()
def read_from_wire(self) -> Awaitable[codec_v1.Message]:
"""Read a whole message from the wire without parsing it."""
- return codec_v1.read_message(self.iface, self.buffer)
+ with self.buffer_lock:
+ return codec_v1.read_message(self.iface, self.buffer)
if TYPE_CHECKING:
@@ -147,13 +172,14 @@ class Context:
# message is too big, we need to allocate a new buffer
buffer = bytearray(msg_size)
- msg_size = protobuf.encode(buffer, msg)
+ with self.buffer_lock:
+ msg_size = protobuf.encode(buffer, msg)
- await codec_v1.write_message(
- self.iface,
- msg.MESSAGE_WIRE_TYPE,
- memoryview(buffer)[:msg_size],
- )
+ await codec_v1.write_message(
+ self.iface,
+ msg.MESSAGE_WIRE_TYPE,
+ memoryview(buffer)[:msg_size],
+ )
CURRENT_CONTEXT: Context | None = None
@@ -235,7 +261,7 @@ def get_context() -> Context:
return CURRENT_CONTEXT
-def with_context(ctx: Context, workflow: loop.Task) -> Generator:
+def with_context(ctx: Context | None, workflow: loop.Task) -> Generator:
"""Run a workflow in a particular context.
Stores the context in a closure and installs it into the global variable every time