diff --git a/common/protob/Makefile b/common/protob/Makefile index f8df2d2d5d..c2feff5a84 100644 --- a/common/protob/Makefile +++ b/common/protob/Makefile @@ -1,4 +1,4 @@ -check: messages.pb messages-binance.pb messages-bitcoin.pb messages-bootloader.pb messages-cardano.pb messages-common.pb messages-crypto.pb messages-debug.pb messages-ethereum.pb messages-management.pb messages-monero.pb messages-nem.pb messages-ripple.pb messages-stellar.pb messages-tezos.pb messages-eos.pb +check: messages.pb messages-binance.pb messages-bitcoin.pb messages-ble.pb messages-bootloader.pb messages-cardano.pb messages-common.pb messages-crypto.pb messages-debug.pb messages-ethereum.pb messages-management.pb messages-monero.pb messages-nem.pb messages-ripple.pb messages-stellar.pb messages-tezos.pb messages-eos.pb %.pb: %.proto protoc -I/usr/include -I. $< -o $@ diff --git a/common/protob/messages-ble.proto b/common/protob/messages-ble.proto new file mode 100644 index 0000000000..724482efe0 --- /dev/null +++ b/common/protob/messages-ble.proto @@ -0,0 +1,41 @@ +syntax = "proto2"; +package hw.trezor.messages.ble; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessageBLE"; + +option (include_in_bitcoin_only) = true; + +import "messages.proto"; + + +/** + * Request: initializes upload of a new ble firmware im + * @start + * @next UploadBLEFirmwareNextChunk + * @next Failure + */ +message UploadBLEFirmwareInit { + required bytes init_data = 1; + required uint32 binsize = 2; +} + + +/** + * Response: Requests next chunk of a new ble firmware im + * @next UploadBLEFirmwareChunk + */ +message UploadBLEFirmwareNextChunk { + required uint32 offset = 1; +} + +/** + * Request: sends next chunk of a new ble firmware im + * @next UploadBLEFirmwareNextChunk + * @next Success + * @next Failure + */ +message UploadBLEFirmwareChunk { + required bytes data = 1; +} diff --git a/common/protob/messages.proto b/common/protob/messages.proto index c0ea0ff753..344d64bf48 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -136,6 +136,11 @@ enum MessageType { MessageType_FirmwareRequest = 8 [(bitcoin_only) = true, (wire_out) = true, (wire_bootloader) = true]; MessageType_SelfTest = 32 [(bitcoin_only) = true, (wire_in) = true, (wire_bootloader) = true]; + // BLE + MessageType_UploadBLEFirmwareInit = 8000 [(bitcoin_only) = true, (wire_in) = true]; + MessageType_UploadBLEFirmwareNextChunk = 8001 [(bitcoin_only) = true, (wire_out) = true]; + MessageType_UploadBLEFirmwareChunk = 8002 [(bitcoin_only) = true, (wire_in) = true]; + // Bitcoin MessageType_GetPublicKey = 11 [(bitcoin_only) = true, (wire_in) = true]; MessageType_PublicKey = 12 [(bitcoin_only) = true, (wire_out) = true]; diff --git a/core/SConscript.firmware b/core/SConscript.firmware index dbb15c17e9..5a7e40357a 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -344,6 +344,7 @@ SOURCE_STMHAL = [ 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_sdram.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_fmc.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_sdmmc.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c', @@ -378,6 +379,8 @@ SOURCE_TREZORHAL = [ 'embed/trezorhal/usbd_ioreq.c', 'embed/trezorhal/util.s', 'embed/trezorhal/vectortable.s', + 'embed/trezorhal/dfu/dfu.c', + 'embed/trezorhal/dfu/fwu.c', ] @@ -470,6 +473,7 @@ env.Replace( 'embed/rust', 'embed/firmware', 'embed/lib', + 'embed/firmware/dfu', 'embed/trezorhal', 'embed/extmod/modtrezorui', 'vendor/micropython', diff --git a/core/SConscript.unix b/core/SConscript.unix index ad2bc03a9f..8b1a469311 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -348,6 +348,7 @@ SOURCE_MICROPYTHON = [ SOURCE_UNIX = [ 'embed/unix/common.c', + 'embed/unix/dfu/dfu.c', 'embed/unix/display-unix.c', 'embed/unix/flash.c', 'embed/unix/main.c', diff --git a/core/embed/extmod/modtrezorio/modtrezorio-ble.h b/core/embed/extmod/modtrezorio/modtrezorio-ble.h new file mode 100644 index 0000000000..08bffb9e72 --- /dev/null +++ b/core/embed/extmod/modtrezorio/modtrezorio-ble.h @@ -0,0 +1,80 @@ +/* + * 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 "dfu/dfu.h" + +/// package: trezorio.ble + +/// def update_init(data: bytes, binsize: int) -> int: +/// """ +/// Initializes the BLE firmware update +/// """ +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); + + dfu_result_t result = dfu_update_init(buffer.buf, buffer.len, binsize_int); + if (result == DFU_NEXT_CHUNK) { + return mp_obj_new_int(0); + } else if (result == DFU_SUCCESS) { + return mp_obj_new_int(1); + } else { + 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) -> int: +/// """ +/// Writes next chunk of BLE firmware update +/// """ +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_obj_new_int(0); + } else if (result == DFU_SUCCESS) { + return mp_obj_new_int(1); + } else { + 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); + +STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = { + {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)}, +}; +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.c b/core/embed/extmod/modtrezorio/modtrezorio.c index 0874f166bc..9d8588707e 100644 --- a/core/embed/extmod/modtrezorio/modtrezorio.c +++ b/core/embed/extmod/modtrezorio/modtrezorio.c @@ -48,6 +48,7 @@ bool usb_connected_previously = true; #include "modtrezorio-webusb.h" #include "modtrezorio-usb.h" // clang-format on +#include "modtrezorio-ble.h" #ifdef USE_SBU #include "modtrezorio-sbu.h" #endif @@ -57,7 +58,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 @@ -88,6 +89,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_fatfs), MP_ROM_PTR(&mod_trezorio_fatfs_module)}, {MP_ROM_QSTR(MP_QSTR_sdcard), MP_ROM_PTR(&mod_trezorio_sdcard_module)}, #endif + {MP_ROM_QSTR(MP_QSTR_ble), MP_ROM_PTR(&mod_trezorio_BLE_module)}, #ifdef USE_TOUCH {MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)}, diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index b788f7b99b..db21539f16 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -72,6 +72,9 @@ #include "supervise.h" #ifdef USE_SECP256K1_ZKP #include "zkp_context.h" + +#include "dfu/dfu.h" + #endif // from util.s @@ -143,6 +146,8 @@ int main(void) { sdcard_init(); #endif + dfu_init(); + #if !defined TREZOR_MODEL_1 // jump to unprivileged mode // http://infocenter.arm.com/help/topic/com.arm.doc.dui0552a/CHDBIBGJ.html diff --git a/core/embed/trezorhal/dfu/dfu.c b/core/embed/trezorhal/dfu/dfu.c new file mode 100644 index 0000000000..5cf26ec497 --- /dev/null +++ b/core/embed/trezorhal/dfu/dfu.c @@ -0,0 +1,176 @@ +// +// main.c +// nrf52-dfu +// +// Sample host application to demonstrate the usage of our 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 STM32_HAL_H +#include "dfu.h" +#include "fwu.h" + +static TFwu sFwu; +static UART_HandleTypeDef urt; + +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) { + GPIO_InitTypeDef GPIO_InitStructure; + + __HAL_RCC_USART1_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + + GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Alternate = GPIO_AF7_USART1; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); + + urt.Init.Mode = UART_MODE_TX_RX; + urt.Init.BaudRate = 115200; + urt.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; + urt.Init.OverSampling = UART_OVERSAMPLING_16; + urt.Init.Parity = UART_PARITY_NONE, urt.Init.StopBits = UART_STOPBITS_1; + urt.Init.WordLength = UART_WORDLENGTH_8B; + urt.Instance = USART1; + + HAL_UART_Init(&urt); + + // sFwu.commandObject = datfile; + // sFwu.commandObjectLen = sizeof(datfile); + // sFwu.dataObject = NULL; + // sFwu.dataObjectLen = sizeof(binfile); + // sFwu.txFunction = txFunction; + // sFwu.responseTimeoutMillisec = 5000; +} + +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) { + return DFU_SUCCESS; + } + + if (status == FWU_STATUS_FAILURE) { + return DFU_FAIL; + } + + if (HAL_GetTick() - tick_start > 2000) { + 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 = 2000; + + tick_start = HAL_GetTick(); + + // 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_GetTick(); + + 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) { + HAL_UART_Transmit(&urt, buf, len, 10); +} + +static uint8_t readData(uint8_t *data, int maxLen) { + HAL_StatusTypeDef result = HAL_UART_Receive(&urt, data, maxLen, 0); + + if (result == HAL_OK) { + return maxLen; + } else { + if (urt.RxXferCount == maxLen) { + return 0; + } + return maxLen - urt.RxXferCount - 1; + } +} diff --git a/core/embed/trezorhal/dfu/dfu.h b/core/embed/trezorhal/dfu/dfu.h new file mode 100644 index 0000000000..fcd6eeb36b --- /dev/null +++ b/core/embed/trezorhal/dfu/dfu.h @@ -0,0 +1,17 @@ + +#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/trezorhal/dfu/fwu.c b/core/embed/trezorhal/dfu/fwu.c new file mode 100644 index 0000000000..4937aed4e9 --- /dev/null +++ b/core/embed/trezorhal/dfu/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/trezorhal/dfu/fwu.h b/core/embed/trezorhal/dfu/fwu.h new file mode 100644 index 0000000000..a380b40c1e --- /dev/null +++ b/core/embed/trezorhal/dfu/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/unix/dfu/dfu.c b/core/embed/unix/dfu/dfu.c new file mode 100644 index 0000000000..2c67420ed2 --- /dev/null +++ b/core/embed/unix/dfu/dfu.c @@ -0,0 +1,56 @@ +// +// main.c +// nrf52-dfu +// +// Sample host application to demonstrate the usage of our 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 "dfu.h" +#include + +static uint32_t binsize = 0; +static uint32_t uploaded_total = 0; + +void dfu_init(void) {} + +dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) { + binsize = binary_len; + uploaded_total = 0; + return DFU_NEXT_CHUNK; +} + +dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) { + uploaded_total += len; + if (uploaded_total >= binsize) { + return DFU_SUCCESS; + } else { + return DFU_NEXT_CHUNK; + } +} + +dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len, + uint8_t *binfile, uint32_t binfile_len) { + return DFU_SUCCESS; +} diff --git a/core/embed/unix/dfu/dfu.h b/core/embed/unix/dfu/dfu.h new file mode 100644 index 0000000000..8a7ed268de --- /dev/null +++ b/core/embed/unix/dfu/dfu.h @@ -0,0 +1,19 @@ + +#ifndef __DFU_H__ +#define __DFU_H__ + +#include + +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/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi index e84de507c0..c2bd1793ea 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 diff --git a/core/src/all_modules.py b/core/src/all_modules.py index c9f28bd33c..b08ae0c12a 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -299,6 +299,8 @@ apps.management.backup_device import apps.management.backup_device apps.management.backup_types import apps.management.backup_types +apps.management.ble.upload_ble_firmware_init +import apps.management.ble.upload_ble_firmware_init apps.management.change_pin import apps.management.change_pin apps.management.change_wipe_code diff --git a/core/src/apps/management/ble/upload_ble_firmware_init.py b/core/src/apps/management/ble/upload_ble_firmware_init.py new file mode 100644 index 0000000000..4c81aac3ab --- /dev/null +++ b/core/src/apps/management/ble/upload_ble_firmware_init.py @@ -0,0 +1,78 @@ +from trezorio import ble +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from trezor.wire import GenericContext + from trezor.messages import ( + UploadBLEFirmwareInit, + UploadBLEFirmwareChunk, + Success, + ) + + +async def upload_ble_firmware_chunk( + ctx: GenericContext, msg: UploadBLEFirmwareChunk +) -> int: + result = ble.update_chunk(msg.data) + + return result + + +async def upload_ble_firmware_init( + ctx: GenericContext, msg: UploadBLEFirmwareInit +) -> Success: + from trezor.enums import ButtonRequestType + from trezor.messages import ( + UploadBLEFirmwareNextChunk, + UploadBLEFirmwareChunk, + Success, + ) + from trezor.ui.layouts import confirm_action + + await confirm_action( + ctx, + "confirm_upload_ble_firmware", + "Upload BLE firmware", + "", + "Update BLE FW?\n", + reverse=True, + verb="Confirm", + br_code=ButtonRequestType.Other, + ) + + from trezor.ui.layouts import progress + + progress_layout = progress("Uploading...") + + upload_progress = 0 + + p = int(1000 * upload_progress / msg.binsize) + progress_layout.report(p) + + res = ble.update_init(msg.init_data, msg.binsize) + + await ctx.write(UploadBLEFirmwareNextChunk(offset=0)) + + if res == 0: + + while True: + + received_msg = await ctx.read(UploadBLEFirmwareChunk) + + result = await upload_ble_firmware_chunk(ctx, received_msg) + + upload_progress += len(received_msg.data) + p = int(1000 * upload_progress / msg.binsize) + progress_layout.report(p) + + if result == 0: + result_msg = UploadBLEFirmwareNextChunk(offset=0) + await ctx.write(result_msg) + del (result_msg, received_msg) + else: + del received_msg + break + + 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 057450b293..2841c1a6fd 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -55,6 +55,10 @@ def _find_message_handler_module(msg_type: int) -> str: if utils.USE_SD_CARD and msg_type == MessageType.SdProtect: return "apps.management.sd_protect" + # BLE + if msg_type == MessageType.UploadBLEFirmwareInit: + return "apps.management.ble.upload_ble_firmware_init" + # bitcoin if msg_type == MessageType.AuthorizeCoinJoin: return "apps.bitcoin.authorize_coinjoin" diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index c245a903b2..a068363574 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -50,6 +50,9 @@ FirmwareErase = 6 FirmwareUpload = 7 FirmwareRequest = 8 SelfTest = 32 +UploadBLEFirmwareInit = 8000 +UploadBLEFirmwareNextChunk = 8001 +UploadBLEFirmwareChunk = 8002 GetPublicKey = 11 PublicKey = 12 SignTx = 15 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index eead516c86..f48f4e7085 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -72,6 +72,9 @@ if TYPE_CHECKING: FirmwareUpload = 7 FirmwareRequest = 8 SelfTest = 32 + UploadBLEFirmwareInit = 8000 + UploadBLEFirmwareNextChunk = 8001 + UploadBLEFirmwareChunk = 8002 GetPublicKey = 11 PublicKey = 12 SignTx = 15 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index c37a6e0233..18ce1f100f 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -1177,6 +1177,50 @@ if TYPE_CHECKING: def is_type_of(cls, msg: Any) -> TypeGuard["TxAckPrevExtraDataWrapper"]: return isinstance(msg, cls) + class UploadBLEFirmwareInit(protobuf.MessageType): + init_data: "bytes" + binsize: "int" + + def __init__( + self, + *, + init_data: "bytes", + binsize: "int", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareInit"]: + return isinstance(msg, cls) + + class UploadBLEFirmwareNextChunk(protobuf.MessageType): + offset: "int" + + def __init__( + self, + *, + offset: "int", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareNextChunk"]: + return isinstance(msg, cls) + + class UploadBLEFirmwareChunk(protobuf.MessageType): + data: "bytes" + + def __init__( + self, + *, + data: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareChunk"]: + return isinstance(msg, cls) + class CardanoBlockchainPointerType(protobuf.MessageType): block_index: "int" tx_index: "int" diff --git a/core/tools/convert_ble_firmware.py b/core/tools/convert_ble_firmware.py new file mode 100644 index 0000000000..c7509ce3a9 --- /dev/null +++ b/core/tools/convert_ble_firmware.py @@ -0,0 +1,35 @@ +import click +import zipfile + + +def convert_file(archive, infile, outfile, name): + data = archive.read(infile) + with open(outfile, "w") as outfile: + outfile.write("// Firmware BLOB - automatically generated\n") + outfile.write("\n") + outfile.write(f"#ifndef __FW_BLOB_{name}_H__\n") + outfile.write(f"#define __FW_BLOB_{name}_H__ 1\n") + outfile.write("\n") + + outfile.write(f"uint8_t {name}[] = " + "{") + + for i, byte in enumerate(data): + if i % 16 == 0: + outfile.write("\n ") + outfile.write("0x{:02x}, ".format(byte)) + + outfile.write("\n};\n") + outfile.write("\n") + outfile.write("#endif\n") + + +@click.command() +@click.argument("infile", type=click.File("rb")) +def convert(infile): + with zipfile.ZipFile(infile) as archive: + convert_file(archive, "ble_firmware.bin", "./embed/firmware/dfu/ble_firmware_bin.h", "binfile") + convert_file(archive, "ble_firmware.dat", "./embed/firmware/dfu/ble_firmware_dat.h", "datfile") + + +if __name__ == "__main__": + convert() diff --git a/python/src/trezorlib/ble/__init__.py b/python/src/trezorlib/ble/__init__.py new file mode 100644 index 0000000000..d57fd66d11 --- /dev/null +++ b/python/src/trezorlib/ble/__init__.py @@ -0,0 +1,34 @@ +import typing as t + +from .. import messages +from ..tools import session + +if t.TYPE_CHECKING: + from ..client import TrezorClient + + +@session +def update( + client: "TrezorClient", + datfile: bytes, + binfile: bytes, + progress_update: t.Callable[[int], t.Any] = lambda _: None, +): + chunk_len = 4096 + offset = 0 + + resp = client.call( + messages.UploadBLEFirmwareInit(init_data=datfile, binsize=len(binfile)) + ) + + while isinstance(resp, messages.UploadBLEFirmwareNextChunk): + + payload = binfile[offset : offset + chunk_len] + resp = client.call(messages.UploadBLEFirmwareChunk(data=payload)) + progress_update(chunk_len) + offset += chunk_len + + if isinstance(resp, messages.Success): + return + else: + raise RuntimeError(f"Unexpected message {resp}") diff --git a/python/src/trezorlib/cli/ble.py b/python/src/trezorlib/cli/ble.py new file mode 100644 index 0000000000..a6ac7dadc1 --- /dev/null +++ b/python/src/trezorlib/cli/ble.py @@ -0,0 +1,62 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2022 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import sys +import zipfile +from typing import TYPE_CHECKING, BinaryIO + +import click + +from .. import ble, exceptions +from . import with_client + +if TYPE_CHECKING: + from ..client import TrezorClient + + +@click.group(name="ble") +def cli() -> None: + """BLE commands.""" + + +@cli.command() +# fmt: off +@click.argument("package", type=click.File("rb")) +# fmt: on +@with_client +def update( + client: "TrezorClient", + package: BinaryIO, +) -> None: + """Upload new BLE firmware to device.""" + + with zipfile.ZipFile(package) as archive: + binfile = archive.read("ble_firmware.bin") + datfile = archive.read("ble_firmware.dat") + + """Perform the final act of loading the firmware into Trezor.""" + try: + click.echo("Uploading...\r", nl=False) + with click.progressbar( + label="Uploading", length=len(binfile), show_eta=False + ) as bar: + ble.update(client, datfile, binfile, bar.update) + click.echo("Update successful.") + except exceptions.Cancelled: + click.echo("Update aborted on device.") + except exceptions.TrezorException as e: + click.echo(f"Update failed: {e}") + sys.exit(3) diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index b820f28d96..ff0fe7c6c6 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -32,6 +32,7 @@ from . import ( AliasedGroup, TrezorConnection, binance, + ble, btc, cardano, cosi, @@ -86,6 +87,7 @@ COMMAND_ALIASES = { "upgrade-firmware": firmware.update, "firmware-upgrade": firmware.update, "firmware-update": firmware.update, + "ble-update": ble.update, } @@ -415,6 +417,7 @@ cli.add_command(tezos.cli) cli.add_command(firmware.cli) cli.add_command(debug.cli) +cli.add_command(ble.cli) # # Main diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index c0e47c271e..7e48907ba5 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -80,6 +80,9 @@ class MessageType(IntEnum): FirmwareUpload = 7 FirmwareRequest = 8 SelfTest = 32 + UploadBLEFirmwareInit = 8000 + UploadBLEFirmwareNextChunk = 8001 + UploadBLEFirmwareChunk = 8002 GetPublicKey = 11 PublicKey = 12 SignTx = 15 @@ -2040,6 +2043,51 @@ class TxAckPrevExtraDataWrapper(protobuf.MessageType): self.extra_data_chunk = extra_data_chunk +class UploadBLEFirmwareInit(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 8000 + FIELDS = { + 1: protobuf.Field("init_data", "bytes", repeated=False, required=True), + 2: protobuf.Field("binsize", "uint32", repeated=False, required=True), + } + + def __init__( + self, + *, + init_data: "bytes", + binsize: "int", + ) -> None: + self.init_data = init_data + self.binsize = binsize + + +class UploadBLEFirmwareNextChunk(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 8001 + FIELDS = { + 1: protobuf.Field("offset", "uint32", repeated=False, required=True), + } + + def __init__( + self, + *, + offset: "int", + ) -> None: + self.offset = offset + + +class UploadBLEFirmwareChunk(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 8002 + FIELDS = { + 1: protobuf.Field("data", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + data: "bytes", + ) -> None: + self.data = data + + class FirmwareErase(protobuf.MessageType): MESSAGE_WIRE_TYPE = 6 FIELDS = {