mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-30 18:38:27 +00:00
feat(core): upload BLE firmware through STM
This commit is contained in:
parent
a8e889afd2
commit
d94bfde415
@ -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 $@
|
||||
|
41
common/protob/messages-ble.proto
Normal file
41
common/protob/messages-ble.proto
Normal file
@ -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;
|
||||
}
|
@ -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];
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
80
core/embed/extmod/modtrezorio/modtrezorio-ble.h
Normal file
80
core/embed/extmod/modtrezorio/modtrezorio-ble.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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};
|
@ -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)},
|
||||
|
@ -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
|
||||
|
176
core/embed/trezorhal/dfu/dfu.c
Normal file
176
core/embed/trezorhal/dfu/dfu.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
17
core/embed/trezorhal/dfu/dfu.h
Normal file
17
core/embed/trezorhal/dfu/dfu.h
Normal file
@ -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
|
664
core/embed/trezorhal/dfu/fwu.c
Normal file
664
core/embed/trezorhal/dfu/fwu.c
Normal file
@ -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 <stdbool.h>
|
||||
|
||||
// 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 <cmd> <ok> 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;
|
||||
}
|
||||
}
|
128
core/embed/trezorhal/dfu/fwu.h
Normal file
128
core/embed/trezorhal/dfu/fwu.h
Normal file
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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__
|
56
core/embed/unix/dfu/dfu.c
Normal file
56
core/embed/unix/dfu/dfu.c
Normal file
@ -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 <stdint.h>
|
||||
|
||||
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;
|
||||
}
|
19
core/embed/unix/dfu/dfu.h
Normal file
19
core/embed/unix/dfu/dfu.h
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
#ifndef __DFU_H__
|
||||
#define __DFU_H__
|
||||
|
||||
#include <stdint.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
|
@ -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
|
||||
|
@ -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
|
||||
|
78
core/src/apps/management/ble/upload_ble_firmware_init.py
Normal file
78
core/src/apps/management/ble/upload_ble_firmware_init.py
Normal file
@ -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")
|
@ -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"
|
||||
|
@ -50,6 +50,9 @@ FirmwareErase = 6
|
||||
FirmwareUpload = 7
|
||||
FirmwareRequest = 8
|
||||
SelfTest = 32
|
||||
UploadBLEFirmwareInit = 8000
|
||||
UploadBLEFirmwareNextChunk = 8001
|
||||
UploadBLEFirmwareChunk = 8002
|
||||
GetPublicKey = 11
|
||||
PublicKey = 12
|
||||
SignTx = 15
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
35
core/tools/convert_ble_firmware.py
Normal file
35
core/tools/convert_ble_firmware.py
Normal file
@ -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()
|
34
python/src/trezorlib/ble/__init__.py
Normal file
34
python/src/trezorlib/ble/__init__.py
Normal file
@ -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}")
|
62
python/src/trezorlib/cli/ble.py
Normal file
62
python/src/trezorlib/cli/ble.py
Normal file
@ -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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
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)
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
Loading…
Reference in New Issue
Block a user