diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 27a2d0dd4e..7bc639113b 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -29,7 +29,7 @@ FEATURE_FLAGS = { "AES_GCM": BENCHMARK, } -FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"] +FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic", "ble"] if DISABLE_OPTIGA and PYOPT == '0': FEATURES_WANTED.remove("optiga") if NEW_RENDERING: diff --git a/core/SConscript.kernel b/core/SConscript.kernel index af58880b68..156a5f71d1 100644 --- a/core/SConscript.kernel +++ b/core/SConscript.kernel @@ -25,7 +25,7 @@ FEATURE_FLAGS = { "AES_GCM": False, } -FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"] +FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic", "ble"] if DISABLE_OPTIGA and PYOPT == '0': FEATURES_WANTED.remove("optiga") if NEW_RENDERING: diff --git a/core/embed/kernel/main.c b/core/embed/kernel/main.c index b140a3c87c..81c955c04e 100644 --- a/core/embed/kernel/main.c +++ b/core/embed/kernel/main.c @@ -21,6 +21,7 @@ #include "applet.h" #include "bl_check.h" +#include "ble.h" #include "board_capabilities.h" #include "bootutils.h" #include "button.h" @@ -146,6 +147,10 @@ void drivers_init() { haptic_init(); #endif +#ifdef USE_BLE + ble_init(); +#endif + #ifdef USE_OPTIGA #if !PYOPT diff --git a/core/embed/trezorhal/ble.h b/core/embed/trezorhal/ble.h new file mode 100644 index 0000000000..894a42b8ef --- /dev/null +++ b/core/embed/trezorhal/ble.h @@ -0,0 +1,121 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_BLE_H +#define TREZORHAL_BLE_H + +// This module provides interface to BLE (Bluetooth Low Energy) functionality. +// It allows the device to advertise itself, connect to other devices, and +// exchange data over BLE. + +#include + +#define BLE_RX_PACKET_SIZE 244 +#define BLE_TX_PACKET_SIZE 64 + +typedef enum { + BLE_SWITCH_OFF = 0, // Turn off BLE advertising, disconnect + BLE_SWITCH_ON = 1, // Turn on BLE advertising + BLE_PAIRING_MODE = 2, // Enter pairing mode + BLE_DISCONNECT = 3, // Disconnect from the connected device + BLE_ERASE_BONDS = 4, // Erase all bonding information + BLE_ALLOW_PAIRING = 5, // Accept pairing request + BLE_REJECT_PAIRING = 6, // Reject pairing request +} ble_command_t; + +typedef enum { + BLE_NONE = 0, // No event + BLE_CONNECTED = 1, // Connected to a device + BLE_DISCONNECTED = 2, // Disconnected from a device + BLE_PAIRING_REQUEST = 3, // Pairing request received +} ble_event_type_t; + +typedef struct { + ble_event_type_t type; + int connection_id; + uint8_t data_len; + uint8_t data[6]; +} ble_event_t; + +typedef struct { + bool connected; + uint8_t peer_count; +} ble_state_t; + +// Initializes the BLE module +// +// Sets up the BLE hardware and software resources, +// preparing the module for operation. +// The function has no effect if the module was already initialized. +void ble_init(void); + +// Deinitializes the BLE module +// +// Releases resources allocated during initialization +// and shuts down the BLE module. +void ble_deinit(void); + +// Starts BLE operations +// +// Enables reception of messages over BLE +void ble_start(void); + +// Stops BLE operations +// +// Disables reception of messages over BLE +// Flushes any queued messages +void ble_stop(void); + +// Issues a command to the BLE module +// +// Sends a specific command to the BLE module for execution. +// +// Returns `true` if the command was successfully issued. +bool ble_issue_command(ble_command_t command); + +// Reads an event from the BLE module +// +// Retrieves the next event from the BLE module's event queue. +// +// Returns `true` if an event was successfully read, `false` if no event is +// available. +bool ble_read_event(ble_event_t *event); + +// Retrieves the current state of the BLE module +// +// Obtains the current operational state of the BLE module. +void ble_get_state(ble_state_t *state); + +// Check if write is possible +bool ble_can_write(void); + +// Writes data to a connected BLE device +// +// Sends data over an established BLE connection. +bool ble_write(const uint8_t *data, uint16_t len); + +// Reads data from a connected BLE device +// +// max_len indicates the maximum number of bytes to read. Rest of the data +// will be discarded. +// +// Returns the number of bytes actually read. +uint32_t ble_read(uint8_t *data, uint16_t max_len); + +#endif diff --git a/core/embed/trezorhal/stm32f4/ble/ble.c b/core/embed/trezorhal/stm32f4/ble/ble.c new file mode 100644 index 0000000000..8bd8a1d15f --- /dev/null +++ b/core/embed/trezorhal/stm32f4/ble/ble.c @@ -0,0 +1,486 @@ +/* + * 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 . + */ + +#ifdef KERNEL_MODE + +#include + +#include "systimer.h" + +#include "ble.h" +#include "ble_comm_defs.h" +#include "nrf/nrf.h" +#include "tsqueue/tsqueue.h" + +typedef enum { + BLE_MODE_OFF, + BLE_MODE_CONNECTABLE, + BLE_MODE_PAIRING, + BLE_MODE_DFU, +} ble_mode_t; + +#define EVENT_QUEUE_LEN 4 +#define DATA_QUEUE_LEN 16 +#define SEND_QUEUE_LEN 1 +#define LOOP_PERIOD_MS 20 +#define PING_PERIOD 100 + +typedef struct { + ble_mode_t mode_requested; + ble_mode_t mode_current; + bool connected; + uint8_t peer_count; + bool initialized; + bool status_valid; + bool accept_msgs; + ble_event_t event_buffers[EVENT_QUEUE_LEN]; + tsqueue_entry_t event_queue_entries[EVENT_QUEUE_LEN]; + tsqueue_t event_queue; + + uint8_t data_buffers[DATA_QUEUE_LEN][BLE_RX_PACKET_SIZE]; + tsqueue_entry_t data_queue_entries[DATA_QUEUE_LEN]; + tsqueue_t data_queue; + + uint8_t send_buffer[NRF_MAX_TX_DATA_SIZE]; + tsqueue_entry_t send_queue_entries[DATA_QUEUE_LEN]; + tsqueue_t send_queue; + + uint16_t ping_cntr; +} ble_driver_t; + +static ble_driver_t g_ble_driver = {0}; + +static void ble_send_state_request(void) { + uint8_t cmd = INTERNAL_CMD_PING; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); +} + +static void ble_send_advertising_on(bool whitelist) { + uint8_t data[2]; + data[0] = INTERNAL_CMD_ADVERTISING_ON; + data[1] = whitelist ? 1 : 0; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, data, sizeof(data), NULL, NULL); +} + +static void ble_send_advertising_off(void) { + uint8_t cmd = INTERNAL_CMD_ADVERTISING_OFF; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); +} + +static bool ble_send_erase_bonds(void) { + if (!nrf_is_running()) { + return false; + } + uint8_t cmd = INTERNAL_CMD_ERASE_BONDS; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); + + return true; +} + +static bool ble_send_disconnect(void) { + if (!nrf_is_running()) { + return false; + } + uint8_t cmd = INTERNAL_CMD_DISCONNECT; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); + + return true; +} + +static void ble_send_pairing_reject(void) { + uint8_t cmd = INTERNAL_CMD_REJECT_PAIRING; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); +} + +static void ble_send_pairing_accept(void) { + uint8_t cmd = INTERNAL_CMD_ALLOW_PAIRING; + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); +} + +static void ble_process_rx_msg_status(const uint8_t *data, uint32_t len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + if (len < sizeof(event_status_msg_t)) { + // insufficient data length + return; + } + + event_status_msg_t msg; + memcpy(&msg, data, sizeof(event_status_msg_t)); + + if (!drv->status_valid) { + if (msg.peer_count > 0) { + drv->mode_requested = BLE_MODE_CONNECTABLE; + } else { + drv->mode_requested = BLE_MODE_OFF; + } + } + + if (drv->connected != msg.connected) { + if (msg.connected) { + // new connection + + ble_event_t event = {.type = BLE_CONNECTED}; + tsqueue_insert(&drv->event_queue, (uint8_t *)&event, sizeof(event), NULL); + + } else { + // connection lost + ble_event_t event = {.type = BLE_DISCONNECTED}; + tsqueue_insert(&drv->event_queue, (uint8_t *)&event, sizeof(event), NULL); + + if (drv->mode_current == BLE_MODE_PAIRING) { + drv->mode_requested = BLE_MODE_CONNECTABLE; + } + } + + drv->connected = msg.connected; + } + + if (msg.advertising && !msg.advertising_whitelist) { + drv->mode_current = BLE_MODE_PAIRING; + } else if (msg.advertising) { + drv->mode_current = BLE_MODE_CONNECTABLE; + } else { + drv->mode_current = BLE_MODE_OFF; + } + + drv->peer_count = msg.peer_count; + + drv->status_valid = true; +} + +static void ble_process_rx_msg_pairing_request(const uint8_t *data, + uint32_t len) { + ble_driver_t *drv = &g_ble_driver; + if (!drv->initialized) { + return; + } + + if (len < 7) { + // insufficient data length + return; + } + + if (drv->mode_requested != BLE_MODE_PAIRING || + drv->mode_current != BLE_MODE_PAIRING) { + ble_send_pairing_reject(); + return; + } + + ble_event_t event = {.type = BLE_PAIRING_REQUEST, .data_len = 6}; + memcpy(event.data, &data[1], 6); + tsqueue_insert(&drv->event_queue, (uint8_t *)&event, sizeof(event), NULL); +} + +static void ble_process_rx_msg(const uint8_t *data, uint32_t len) { + switch (data[0]) { + case INTERNAL_EVENT_STATUS: + ble_process_rx_msg_status(data, len); + break; + case INTERNAL_EVENT_PAIRING_REQUEST: + ble_process_rx_msg_pairing_request(data, len); + break; + default: + break; + } +} + +static void ble_process_data(const uint8_t *data, uint32_t len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + if (len != BLE_RX_PACKET_SIZE) { + return; + } + + uint8_t *buffer = tsqueue_allocate(&drv->data_queue, NULL); + + if (buffer == NULL) { + return; + } + + memcpy(buffer, data, len); + + tsqueue_finalize(&drv->data_queue, buffer, len); +} + +// background loop, called from systimer every 10ms +static void ble_loop(void *context) { + (void)context; + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + if (nrf_is_running()) { + if (!drv->status_valid) { + ble_send_state_request(); + } + + if (drv->ping_cntr++ > (PING_PERIOD / LOOP_PERIOD_MS)) { + ble_send_state_request(); + drv->ping_cntr = 0; + } + + uint8_t data[NRF_MAX_TX_DATA_SIZE] = {0}; + if (tsqueue_read(&drv->send_queue, data, NRF_MAX_TX_DATA_SIZE, NULL)) { + if (!nrf_send_msg(NRF_SERVICE_BLE, data, NRF_MAX_TX_DATA_SIZE, NULL, + NULL)) { + tsqueue_insert(&drv->send_queue, data, NRF_MAX_TX_DATA_SIZE, NULL); + } + } + + if (drv->mode_current != drv->mode_requested) { + if (drv->mode_requested == BLE_MODE_OFF) { + ble_send_advertising_off(); + // if (drv->connected) { + // nrf_send_disconnect(); + // } + } else if (drv->mode_requested == BLE_MODE_CONNECTABLE) { + ble_send_advertising_on(true); + } else if (drv->mode_requested == BLE_MODE_PAIRING) { + ble_send_advertising_on(false); + } + } + } else { + drv->status_valid = false; + } +} + +void ble_init(void) { + ble_driver_t *drv = &g_ble_driver; + + if (drv->initialized) { + return; + } + + memset(drv, 0, sizeof(ble_driver_t)); + + tsqueue_init(&drv->event_queue, drv->event_queue_entries, + (uint8_t *)drv->event_buffers, sizeof(ble_event_t), + EVENT_QUEUE_LEN); + + tsqueue_init(&drv->data_queue, drv->data_queue_entries, + (uint8_t *)drv->data_buffers, BLE_RX_PACKET_SIZE, + DATA_QUEUE_LEN); + + tsqueue_init(&drv->send_queue, drv->send_queue_entries, + (uint8_t *)drv->send_buffer, NRF_MAX_TX_DATA_SIZE, + SEND_QUEUE_LEN); + + systimer_t *timer = systimer_create(ble_loop, NULL); + + systimer_set_periodic(timer, LOOP_PERIOD_MS); + + nrf_init(); + nrf_register_listener(NRF_SERVICE_BLE_MANAGER, ble_process_rx_msg); + nrf_register_listener(NRF_SERVICE_BLE, ble_process_data); + + drv->initialized = true; +} + +void ble_deinit(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + tsqueue_reset(&drv->event_queue); + tsqueue_reset(&drv->data_queue); + tsqueue_reset(&drv->send_queue); + + nrf_unregister_listener(NRF_SERVICE_BLE); + nrf_unregister_listener(NRF_SERVICE_BLE_MANAGER); + + drv->initialized = false; +} + +bool ble_connected(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + return drv->connected && nrf_is_running(); +} + +void ble_start(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + drv->accept_msgs = true; +} + +void ble_stop(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + drv->accept_msgs = false; + tsqueue_reset(&drv->data_queue); +} + +bool ble_can_write(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + if (!drv->connected || !drv->accept_msgs) { + return false; + } + + return !tsqueue_full(&drv->send_queue); +} + +bool ble_write(const uint8_t *data, uint16_t len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + if (!drv->connected || !drv->accept_msgs) { + return false; + } + + bool sent = nrf_send_msg(NRF_SERVICE_BLE, data, len, NULL, NULL); + + if (!sent) { + bool queued = tsqueue_insert(&drv->send_queue, data, len, NULL); + return queued; + } + + return true; +} + +uint32_t ble_read(uint8_t *data, uint16_t max_len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return 0; + } + + tsqueue_t *queue = &drv->data_queue; + + uint16_t read_len = 0; + + bool received = tsqueue_read(queue, data, max_len, &read_len); + + if (!received) { + return 0; + } + + return max_len; +} + +bool ble_issue_command(ble_command_t command) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + switch (command) { + case BLE_SWITCH_OFF: + drv->mode_requested = BLE_MODE_OFF; + break; + case BLE_SWITCH_ON: + drv->mode_requested = BLE_MODE_CONNECTABLE; + break; + case BLE_PAIRING_MODE: + drv->mode_requested = BLE_MODE_PAIRING; + break; + case BLE_DISCONNECT: + ble_send_disconnect(); + break; + case BLE_ERASE_BONDS: + ble_send_erase_bonds(); + break; + case BLE_ALLOW_PAIRING: + ble_send_pairing_accept(); + break; + case BLE_REJECT_PAIRING: + ble_send_pairing_reject(); + break; + default: + // unknown command + return false; + } + + return true; +} + +bool ble_read_event(ble_event_t *event) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + ble_event_t tmp_event = {0}; + uint16_t len = 0; + bool read = tsqueue_read(&drv->event_queue, (uint8_t *)&tmp_event, + sizeof(tmp_event), &len); + + if (!read) { + return false; + } + + if (len != sizeof(ble_event_t)) { + return false; + } + + memcpy(event, &tmp_event, sizeof(ble_event_t)); + + return true; +} + +void ble_get_state(ble_state_t *state) { + const ble_driver_t *drv = &g_ble_driver; + + if (state == NULL) { + return; + } + + if (!drv->initialized) { + memset(state, 0, sizeof(ble_state_t)); + return; + } + + state->connected = drv->connected; + state->peer_count = drv->peer_count; +} + +#endif diff --git a/core/embed/trezorhal/stm32f4/ble/ble_comm_defs.h b/core/embed/trezorhal/stm32f4/ble/ble_comm_defs.h new file mode 100644 index 0000000000..4adbe21784 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/ble/ble_comm_defs.h @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_BLE_COMM_DEFS_H_ +#define TREZORHAL_BLE_COMM_DEFS_H_ + +#include + +typedef struct { + uint8_t msg_id; + uint8_t connected; + uint8_t advertising; + uint8_t advertising_whitelist; + + uint8_t peer_count; + uint8_t reserved[2]; + uint8_t sd_version_number; + + uint16_t sd_company_id; + uint16_t sd_subversion_number; + + uint32_t app_version; + uint32_t bld_version; + +} event_status_msg_t; + +typedef enum { + INTERNAL_EVENT_STATUS = 0x01, + INTERNAL_EVENT_PAIRING_REQUEST = 0x04, +} InternalEvent_t; + +typedef enum { + INTERNAL_CMD_PING = 0x00, + INTERNAL_CMD_ADVERTISING_ON = 0x01, + INTERNAL_CMD_ADVERTISING_OFF = 0x02, + INTERNAL_CMD_ERASE_BONDS = 0x03, + INTERNAL_CMD_DISCONNECT = 0x04, + INTERNAL_CMD_ACK = 0x05, + INTERNAL_CMD_ALLOW_PAIRING = 0x06, + INTERNAL_CMD_REJECT_PAIRING = 0x07, +} InternalCmd_t; +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/crc8.c b/core/embed/trezorhal/stm32f4/nrf/crc8.c new file mode 100644 index 0000000000..6cfae59194 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/crc8.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Intel Corporation + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015 Runtime Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "crc8.h" + +static const uint8_t crc8_ccitt_small_table[16] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, + 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d}; + +uint8_t crc8_ccitt(uint8_t val, const void *buf, size_t cnt) { + size_t i; + const uint8_t *p = buf; + + for (i = 0; i < cnt; i++) { + val ^= p[i]; + val = (val << 4) ^ crc8_ccitt_small_table[val >> 4]; + val = (val << 4) ^ crc8_ccitt_small_table[val >> 4]; + } + return val; +} + +uint8_t crc8(const uint8_t *src, size_t len, uint8_t polynomial, + uint8_t initial_value, bool reversed) { + uint8_t crc = initial_value; + size_t i, j; + + for (i = 0; i < len; i++) { + crc ^= src[i]; + + for (j = 0; j < 8; j++) { + if (reversed) { + if (crc & 0x01) { + crc = (crc >> 1) ^ polynomial; + } else { + crc >>= 1; + } + } else { + if (crc & 0x80) { + crc = (crc << 1) ^ polynomial; + } else { + crc <<= 1; + } + } + } + } + + return crc; +} diff --git a/core/embed/trezorhal/stm32f4/nrf/crc8.h b/core/embed/trezorhal/stm32f4/nrf/crc8.h new file mode 100644 index 0000000000..f26e33730a --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/crc8.h @@ -0,0 +1,28 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_NRF_CRC8_H +#define TREZORHAL_NRF_CRC8_H + +#include + +uint8_t crc8(const uint8_t *src, size_t len, uint8_t polynomial, + uint8_t initial_value, bool reversed); + +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/dfu.c b/core/embed/trezorhal/stm32f4/nrf/dfu.c new file mode 100644 index 0000000000..c534d691e8 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/dfu.c @@ -0,0 +1,138 @@ +/* + * 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 . + */ + +#ifdef KERNEL_MODE + +#include "dfu.h" +#include "common.h" +#include "fwu.h" +#include "nrf.h" +#include "nrf_internal.h" + +static TFwu sFwu; + +static uint32_t tick_start = 0; + +void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len); +static uint8_t readData(uint8_t *data, int maxLen); + +void dfu_init(void) {} + +dfu_result_t dfu_update_process(void) { + while (1) { + // Can send 4 chars... + // (On a microcontroller, you'd use the TX Empty interrupt or test a + // register.) + + fwuCanSendData(&sFwu, 4); + + // Data available? Get up to 4 bytes... + // (On a microcontroller, you'd use the RX Available interrupt or test a + // register.) + uint8_t rxBuf[4]; + uint8_t rxLen = readData(rxBuf, 4); + if (rxLen > 0) { + fwuDidReceiveData(&sFwu, rxBuf, rxLen); + } + + // Give the firmware update module a timeslot to continue the process. + EFwuProcessStatus status = fwuYield(&sFwu, 0); + + if (status == FWU_STATUS_COMPLETION) { + nrf_reboot(); + return DFU_SUCCESS; + } + + if (status == FWU_STATUS_FAILURE) { + return DFU_FAIL; + } + + if (hal_ticks_ms() - 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; + + if (!nrf_reboot_to_bootloader()) { + return DFU_FAIL; + } + + tick_start = hal_ticks_ms(); + + // Prepare the firmware update process. + fwuInit(&sFwu); + + // Start the firmware update process. + fwuExec(&sFwu); + + return dfu_update_process(); +} + +dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) { + tick_start = hal_ticks_ms(); + + fwuSendChunk(&sFwu, data, len); + + return dfu_update_process(); +} + +dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len, + uint8_t *binfile, uint32_t binfile_len) { + uint32_t chunk_offset = 0; + uint32_t rem_data = binfile_len; + + dfu_result_t res = dfu_update_init(datfile, datfile_len, binfile_len); + + while (res == DFU_NEXT_CHUNK) { + // Send the next chunk of the data object. + uint32_t chunk_size = 4096; + if (rem_data < 4096) { + chunk_size = rem_data; + rem_data = 0; + } else { + rem_data -= 4096; + } + res = dfu_update_chunk(&binfile[chunk_offset], chunk_size); + chunk_offset += chunk_size; + } + + return res; +} + +void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len) { + nrf_dfu_comm_send(buf, len); +} + +static uint8_t readData(uint8_t *data, int maxLen) { + return nrf_dfu_comm_receive(data, maxLen); +} + +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/dfu.h b/core/embed/trezorhal/stm32f4/nrf/dfu.h new file mode 100644 index 0000000000..dadd058c02 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/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/embed/trezorhal/stm32f4/nrf/fwu.c b/core/embed/trezorhal/stm32f4/nrf/fwu.c new file mode 100644 index 0000000000..a2446fd40a --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/fwu.c @@ -0,0 +1,669 @@ +// +// 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. +// + +#ifdef KERNEL_MODE + +#include + +#include "fwu.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 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; + } +} + +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/fwu.h b/core/embed/trezorhal/stm32f4/nrf/fwu.h new file mode 100644 index 0000000000..2a31fac413 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/fwu.h @@ -0,0 +1,127 @@ +// +// 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 + +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/trezorhal/stm32f4/nrf/nrf.c b/core/embed/trezorhal/stm32f4/nrf/nrf.c new file mode 100644 index 0000000000..8d31d900c0 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/nrf.c @@ -0,0 +1,719 @@ +/* + * 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 . + */ + +#ifdef KERNEL_MODE + +#include +#include + +#include "tsqueue/tsqueue.h" + +#include "crc8.h" +#include "irq.h" +#include "mpu.h" +#include "nrf.h" +#include "nrf_internal.h" + +#define MAX_SPI_DATA_SIZE (244) + +typedef struct { + uint8_t service_id; +} spi_header_t; + +typedef struct { + uint8_t crc; +} spi_footer_t; + +#define SPI_HEADER_SIZE (sizeof(spi_header_t)) +#define SPI_FOOTER_SIZE (sizeof(spi_footer_t)) +#define SPI_OVERHEAD_SIZE (SPI_HEADER_SIZE + SPI_FOOTER_SIZE) +#define SPI_PACKET_SIZE (MAX_SPI_DATA_SIZE + SPI_OVERHEAD_SIZE) + +typedef struct { + uint8_t service_id; + uint8_t msg_len; +} uart_header_t; + +typedef struct { + uint8_t crc; +} uart_footer_t; + +#define UART_HEADER_SIZE (sizeof(uart_header_t)) +#define UART_FOOTER_SIZE (sizeof(uart_footer_t)) +#define UART_OVERHEAD_SIZE (UART_HEADER_SIZE + UART_FOOTER_SIZE) +#define UART_PACKET_SIZE (NRF_MAX_TX_DATA_SIZE + UART_OVERHEAD_SIZE) +#define UART_QUEUE_SIZE (8) + +#define START_BYTE (0xA0) + +typedef struct { + uint8_t data[UART_PACKET_SIZE]; + uint8_t len; + void (*callback)(nrf_status_t status, void *context); + void *context; +} nrf_uart_tx_data_t; + +typedef struct { + UART_HandleTypeDef urt; + DMA_HandleTypeDef urt_tx_dma; + + uint8_t urt_tx_buffers[UART_QUEUE_SIZE][sizeof(nrf_uart_tx_data_t)]; + tsqueue_entry_t urt_tx_queue_entries[UART_QUEUE_SIZE]; + tsqueue_t urt_tx_queue; + + uint8_t urt_rx_buf[UART_PACKET_SIZE]; + uint8_t urt_rx_len; + uint8_t urt_rx_byte; + uint16_t urt_rx_idx; + + SPI_HandleTypeDef spi; + DMA_HandleTypeDef spi_dma; + uint8_t spi_buffer[SPI_PACKET_SIZE]; + + bool urt_tx_running; + bool spi_rx_running; + bool comm_running; + + bool initialized; + + void (*service_listeners[NRF_SERVICE_CNT])(const uint8_t *data, uint32_t len); + +} nrf_driver_t; + +__attribute__((section(".buf"))) static nrf_driver_t g_nrf_driver = {0}; + +static void nrf_start(void) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + HAL_SPI_Receive_DMA(&drv->spi, drv->spi_buffer, SPI_PACKET_SIZE); + + tsqueue_reset(&drv->urt_tx_queue); + HAL_UART_Receive_IT(&drv->urt, &drv->urt_rx_byte, 1); + + drv->spi_rx_running = true; + drv->comm_running = true; + + nrf_signal_running(); +} + +static void nrf_stop(void) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + nrf_signal_off(); + irq_key_t key = irq_lock(); + drv->comm_running = false; + HAL_SPI_DMAStop(&drv->spi); + tsqueue_reset(&drv->urt_tx_queue); + irq_unlock(key); +} + +void nrf_init(void) { + nrf_driver_t *drv = &g_nrf_driver; + + if (drv->initialized) { + return; + } + + __HAL_RCC_USART1_CLK_ENABLE(); + __HAL_RCC_DMA1_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); + __HAL_RCC_SPI2_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + + memset(drv, 0, sizeof(*drv)); + tsqueue_init(&drv->urt_tx_queue, drv->urt_tx_queue_entries, + (uint8_t *)drv->urt_tx_buffers, sizeof(nrf_uart_tx_data_t), + UART_QUEUE_SIZE); + + GPIO_InitTypeDef GPIO_InitStructure; + + // synchronization signals + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_PIN_12; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); + + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_1_PIN; + HAL_GPIO_Init(GPIO_1_PORT, &GPIO_InitStructure); + + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_2_PIN; + HAL_GPIO_Init(GPIO_2_PORT, &GPIO_InitStructure); + + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_3_PIN; + HAL_GPIO_Init(GPIO_3_PORT, &GPIO_InitStructure); + + 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); + + drv->urt.Init.Mode = UART_MODE_TX_RX; + drv->urt.Init.BaudRate = 1000000; + drv->urt.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; + drv->urt.Init.OverSampling = UART_OVERSAMPLING_16; + drv->urt.Init.Parity = UART_PARITY_NONE; + drv->urt.Init.StopBits = UART_STOPBITS_1; + drv->urt.Init.WordLength = UART_WORDLENGTH_8B; + drv->urt.Instance = USART1; + drv->urt.hdmatx = &drv->urt_tx_dma; + + drv->urt_tx_dma.Init.Channel = DMA_CHANNEL_4; + drv->urt_tx_dma.Init.Direction = DMA_MEMORY_TO_PERIPH; + drv->urt_tx_dma.Init.PeriphInc = DMA_PINC_DISABLE; + drv->urt_tx_dma.Init.MemInc = DMA_MINC_ENABLE; + drv->urt_tx_dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + drv->urt_tx_dma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + drv->urt_tx_dma.Init.Mode = DMA_NORMAL; + drv->urt_tx_dma.Init.Priority = DMA_PRIORITY_LOW; + drv->urt_tx_dma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + drv->urt_tx_dma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; + drv->urt_tx_dma.Init.MemBurst = DMA_MBURST_SINGLE; + drv->urt_tx_dma.Init.PeriphBurst = DMA_PBURST_SINGLE; + drv->urt_tx_dma.Instance = DMA2_Stream7; + drv->urt_tx_dma.Parent = &drv->urt; + HAL_DMA_Init(&drv->urt_tx_dma); + + HAL_UART_Init(&drv->urt); + + NVIC_SetPriority(DMA2_Stream7_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(DMA2_Stream7_IRQn); + NVIC_SetPriority(USART1_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(USART1_IRQn); + + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Alternate = GPIO_AF5_SPI2; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM; + GPIO_InitStructure.Pin = GPIO_PIN_2 | GPIO_PIN_3; + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + GPIO_InitStructure.Pin = GPIO_PIN_9; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + GPIO_InitStructure.Pin = GPIO_PIN_3; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + + drv->spi_dma.Init.Channel = DMA_CHANNEL_0; + drv->spi_dma.Init.Direction = DMA_PERIPH_TO_MEMORY; + drv->spi_dma.Init.PeriphInc = DMA_PINC_DISABLE; + drv->spi_dma.Init.MemInc = DMA_MINC_ENABLE; + drv->spi_dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + drv->spi_dma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + drv->spi_dma.Init.Mode = DMA_NORMAL; + drv->spi_dma.Init.Priority = DMA_PRIORITY_LOW; + drv->spi_dma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + drv->spi_dma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; + drv->spi_dma.Init.MemBurst = DMA_MBURST_SINGLE; + drv->spi_dma.Init.PeriphBurst = DMA_PBURST_SINGLE; + drv->spi_dma.Instance = DMA1_Stream3; + HAL_DMA_Init(&drv->spi_dma); + + drv->spi.Instance = SPI2; + drv->spi.Init.Mode = SPI_MODE_SLAVE; + drv->spi.Init.Direction = SPI_DIRECTION_2LINES_RXONLY; + drv->spi.Init.DataSize = SPI_DATASIZE_8BIT; + drv->spi.Init.CLKPolarity = SPI_POLARITY_LOW; + drv->spi.Init.CLKPhase = SPI_PHASE_1EDGE; + drv->spi.Init.NSS = SPI_NSS_HARD_INPUT; + drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; + drv->spi.Init.FirstBit = SPI_FIRSTBIT_MSB; + drv->spi.Init.TIMode = SPI_TIMODE_DISABLE; + drv->spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + drv->spi.Init.CRCPolynomial = 0; + drv->spi.hdmarx = &drv->spi_dma; + + drv->spi_dma.Parent = &drv->spi; + + HAL_SPI_Init(&drv->spi); + + NVIC_SetPriority(DMA1_Stream3_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(DMA1_Stream3_IRQn); + + drv->initialized = true; + + nrf_start(); +} + +void nrf_deinit(void) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + nrf_stop(); + + drv->initialized = false; +} + +void nrf_register_listener(nrf_service_id_t service, + void (*listener)(const uint8_t *data, + uint32_t len)) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + if (service < NRF_SERVICE_CNT) { + drv->service_listeners[service] = listener; + } +} + +void nrf_unregister_listener(nrf_service_id_t service) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + if (service < NRF_SERVICE_CNT) { + drv->service_listeners[service] = NULL; + } +} + +static void nrf_process_msg(nrf_driver_t *drv, const uint8_t *data, + uint32_t len, nrf_service_id_t service, + uint8_t header_size, uint8_t overhead_size) { + const uint8_t *service_data = data + header_size; + uint32_t service_data_len = len - overhead_size; + if (drv->service_listeners[service] != NULL) { + drv->service_listeners[service](service_data, service_data_len); + } +} + +/// DFU communication +/// ---------------------------------------------------------- + +void nrf_dfu_comm_send(const uint8_t *data, uint32_t len) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return; + } + + HAL_UART_Transmit(&drv->urt, (uint8_t *)data, len, 30); +} + +uint32_t nrf_dfu_comm_receive(uint8_t *data, uint32_t len) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return 0; + } + + if (__HAL_UART_GET_FLAG(&drv->urt, UART_FLAG_RXNE)) { + HAL_StatusTypeDef result = HAL_UART_Receive(&drv->urt, data, len, 30); + + if (result == HAL_OK) { + return len; + } + + if (drv->urt.RxXferCount == len) { + return 0; + } + + return len - drv->urt.RxXferCount - 1; + } + + return 0; +} + +/// UART communication +/// --------------------------------------------------------- + +uint32_t nrf_send_msg(nrf_service_id_t service, const uint8_t *data, + uint32_t len, + void (*callback)(nrf_status_t status, void *context), + void *context) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return 0; + } + + if (len > NRF_MAX_TX_DATA_SIZE) { + return 0; + } + + if (service > NRF_SERVICE_CNT) { + return 0; + } + + uint32_t id = 0; + + nrf_uart_tx_data_t *buffer = + (nrf_uart_tx_data_t *)tsqueue_allocate(&drv->urt_tx_queue, &id); + + if (buffer == NULL) { + return 0; + } + + buffer->callback = callback; + buffer->context = context; + buffer->len = len + UART_OVERHEAD_SIZE; + + uart_header_t header = { + .service_id = 0xA0 | (uint8_t)service, + .msg_len = len + UART_OVERHEAD_SIZE, + }; + memcpy(buffer->data, &header, UART_HEADER_SIZE); + + memcpy(&buffer->data[UART_HEADER_SIZE], data, len); + + uart_footer_t footer = { + .crc = crc8(buffer->data, len + UART_HEADER_SIZE, 0x07, 0x00, false), + }; + memcpy(&buffer->data[UART_HEADER_SIZE + len], &footer, UART_FOOTER_SIZE); + + tsqueue_finalize(&drv->urt_tx_queue, (uint8_t *)buffer, + sizeof(nrf_uart_tx_data_t)); + + irq_key_t key = irq_lock(); + if (!drv->urt_tx_running) { + nrf_uart_tx_data_t *send_buffer = + (nrf_uart_tx_data_t *)tsqueue_process(&drv->urt_tx_queue, NULL); + if (send_buffer != NULL) { + HAL_UART_Transmit_DMA(&drv->urt, send_buffer->data, send_buffer->len); + } + drv->urt_tx_running = true; + } + irq_unlock(key); + + return id; +} + +bool nrf_abort_msg(uint32_t id) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return false; + } + + bool aborted = tsqueue_abort(&drv->urt_tx_queue, id, NULL, 0, NULL); + + if (!aborted) { + return false; + } + + return true; +} + +static bool nrf_is_valid_startbyte(uint8_t val) { + if ((val & 0xF0) != 0xA0) { + return false; + } + + if ((val & 0x0F) >= NRF_SERVICE_CNT) { + return false; + } + + return true; +} + +void HAL_UART_RxCpltCallback(UART_HandleTypeDef *urt) { + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized && urt == &drv->urt) { + if (drv->urt_rx_idx == 0) { + // received first byte: START BYTE + if (nrf_is_valid_startbyte(drv->urt_rx_byte)) { + drv->urt_rx_buf[0] = drv->urt_rx_byte; + drv->urt_rx_idx++; + } else { + // bad message, flush the line + drv->urt_rx_idx = 0; + } + } else if (drv->urt_rx_idx == 1) { + // received second byte: LEN + + drv->urt_rx_buf[1] = drv->urt_rx_byte; + drv->urt_rx_len = drv->urt_rx_byte; + + if (drv->urt_rx_len > UART_PACKET_SIZE) { + drv->urt_rx_len = 0; + } else { + drv->urt_rx_idx++; + } + } else if (drv->urt_rx_idx >= UART_HEADER_SIZE && + drv->urt_rx_idx < (drv->urt_rx_len - 1)) { + // receive the rest of the message + + drv->urt_rx_buf[drv->urt_rx_idx] = drv->urt_rx_byte; + drv->urt_rx_idx++; + + if (drv->urt_rx_idx >= NRF_MAX_TX_DATA_SIZE) { + // message is too long, flush the line + drv->urt_rx_idx = 0; + drv->urt_rx_len = 0; + } + + } else if (drv->urt_rx_idx == (drv->urt_rx_len - 1)) { + // received last byte: CRC + + uint8_t crc = + crc8(drv->urt_rx_buf, drv->urt_rx_len - 1, 0x07, 0x00, false); + + if (drv->urt_rx_byte == crc) { + uart_header_t *header = (uart_header_t *)drv->urt_rx_buf; + nrf_process_msg(drv, drv->urt_rx_buf, drv->urt_rx_len, + header->service_id & 0x0F, UART_HEADER_SIZE, + UART_OVERHEAD_SIZE); + } + + drv->urt_rx_idx = 0; + drv->urt_rx_len = 0; + + } else { + // bad message, flush the line + drv->urt_rx_idx = 0; + drv->urt_rx_len = 0; + } + } + + // receive the rest of the message, or new message in any case. + HAL_UART_Receive_IT(&drv->urt, &drv->urt_rx_byte, 1); +} + +void HAL_UART_ErrorCallback(UART_HandleTypeDef *urt) { + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized && urt == &drv->urt) { + HAL_UART_AbortReceive(urt); + HAL_UART_AbortTransmit(urt); + + tsqueue_reset(&drv->urt_tx_queue); + + drv->urt_rx_idx = 0; + drv->urt_rx_len = 0; + + HAL_UART_Receive_IT(&drv->urt, &drv->urt_rx_byte, 1); + } +} + +void HAL_UART_TxCpltCallback(UART_HandleTypeDef *urt) { + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized && urt == &drv->urt) { + nrf_uart_tx_data_t sent; + bool aborted = false; + if (tsqueue_process_done(&drv->urt_tx_queue, (uint8_t *)&sent, sizeof(sent), + NULL, &aborted)) { + if (!aborted && sent.callback != NULL) { + sent.callback(NRF_STATUS_OK, sent.context); + } + } + + nrf_uart_tx_data_t *send_buffer = + (nrf_uart_tx_data_t *)tsqueue_process(&drv->urt_tx_queue, NULL); + if (send_buffer != NULL) { + HAL_UART_Transmit_DMA(&drv->urt, send_buffer->data, send_buffer->len); + drv->urt_tx_running = true; + } else { + drv->urt_tx_running = false; + } + } +} + +void USART1_IRQHandler(void) { + IRQ_ENTER(USART1_IRQn); + + mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT); + + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized) { + HAL_UART_IRQHandler(&drv->urt); + } + + mpu_restore(mpu_mode); + + IRQ_EXIT(USART1_IRQn); +} + +void DMA2_Stream7_IRQHandler(void) { + IRQ_ENTER(DMA2_Stream7_IRQn); + + mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT); + + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized) { + HAL_DMA_IRQHandler(&drv->urt_tx_dma); + } + + mpu_restore(mpu_mode); + + IRQ_EXIT(DMA2_Stream7_IRQn); +} + +/// SPI communication +/// ---------------------------------------------------------- + +static bool start_spi_dma(nrf_driver_t *drv) { + if (!drv->comm_running) { + return false; + } + + HAL_SPI_Receive_DMA(&drv->spi, drv->spi_buffer, SPI_PACKET_SIZE); + drv->spi_rx_running = true; + + return true; +} + +void DMA1_Stream3_IRQHandler(void) { + IRQ_ENTER(DMA1_Stream3_IRQn); + + mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT); + + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized) { + HAL_DMA_IRQHandler(&drv->spi_dma); + } + + mpu_restore(mpu_mode); + + IRQ_EXIT(DMA1_Stream3_IRQn); +} + +void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { + nrf_driver_t *drv = &g_nrf_driver; + + if (!drv->initialized) { + return; + } + + if (hspi != &drv->spi) { + return; + } + + spi_header_t *header = (spi_header_t *)drv->spi_buffer; + spi_footer_t *footer = + (spi_footer_t *)(drv->spi_buffer + SPI_PACKET_SIZE - SPI_FOOTER_SIZE); + + uint8_t crc = crc8(drv->spi_buffer, SPI_PACKET_SIZE - SPI_FOOTER_SIZE, 0x07, + 0x00, false); + + if ((header->service_id & 0xF0) != START_BYTE || footer->crc != crc) { + HAL_SPI_Abort(&drv->spi); + HAL_SPI_Receive_DMA(&drv->spi, drv->spi_buffer, SPI_PACKET_SIZE); + return; + } + + nrf_process_msg(drv, drv->spi_buffer, SPI_PACKET_SIZE, + header->service_id & 0x0F, SPI_HEADER_SIZE, + SPI_OVERHEAD_SIZE); + drv->spi_rx_running = false; + start_spi_dma(drv); +} + +/// GPIO communication +/// --------------------------------------------------------- + +bool nrf_reboot_to_bootloader(void) { + uint32_t tick_start = 0; + + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); + + HAL_Delay(10); + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); + + tick_start = HAL_GetTick(); + + while (HAL_GPIO_ReadPin(GPIO_1_PORT, GPIO_1_PIN) == GPIO_PIN_RESET) { + if (HAL_GetTick() - tick_start > 4000) { + return false; + } + } + + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); + + HAL_Delay(1000); + + return true; +} + +bool nrf_reboot(void) { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + HAL_Delay(50); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); + return true; +} + +void nrf_signal_running(void) { + HAL_GPIO_WritePin(GPIO_3_PORT, GPIO_3_PIN, GPIO_PIN_SET); +} + +void nrf_signal_off(void) { + HAL_GPIO_WritePin(GPIO_3_PORT, GPIO_3_PIN, GPIO_PIN_RESET); +} + +bool nrf_firmware_running(void) { + return HAL_GPIO_ReadPin(GPIO_2_PORT, GPIO_2_PIN) != 0; +} + +bool nrf_is_running(void) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return false; + } + + if (!nrf_firmware_running()) { + return false; + } + + return drv->comm_running; +} + +void nrf_set_dfu_mode(void) { + nrf_driver_t *drv = &g_nrf_driver; + + if (!drv->initialized) { + return; + } + + // TODO + // if (nrf_reboot_to_bootloader()) { + // drv->mode_current = BLE_MODE_DFU; + // } else { + // drv->status_valid = false; + // } +} + +bool nrf_is_dfu_mode(void) { + nrf_driver_t *drv = &g_nrf_driver; + + if (!drv->initialized) { + return false; + } + + return true; + // TODO +} + +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/nrf.h b/core/embed/trezorhal/stm32f4/nrf/nrf.h new file mode 100644 index 0000000000..d839faec40 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/nrf.h @@ -0,0 +1,81 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_NRF_H +#define TREZORHAL_NRF_H + +#include + +// maximum data size allowed to be sent +#define NRF_MAX_TX_DATA_SIZE (64) + +typedef enum { + NRF_SERVICE_BLE = 0, + NRF_SERVICE_BLE_MANAGER = 1, + + NRF_SERVICE_CNT // Number of services +} nrf_service_id_t; + +typedef enum { + NRF_STATUS_OK = 0, // Packet completed successfully + NRF_STATUS_TIMEOUT = 1, // Timeout occurred + NRF_STATUS_ERROR = 2, // General error + NRF_STATUS_ABORTED = 3, // Packet was aborted +} nrf_status_t; + +typedef struct { + const uint8_t *data; + uint32_t len; + +} nrf_packet_t; + +// Initialize the NRF driver +void nrf_init(void); + +// Deinitialize the NRF driver +void nrf_deinit(void); + +// Check that NRF is running +bool nrf_is_running(void); + +// Register listener for a service +// The listener will be called when a message is received for the service +// The listener will be called from an interrupt context +void nrf_register_listener(nrf_service_id_t service, + void (*listener)(const uint8_t *data, uint32_t len)); + +// Unregister listener for a service +void nrf_unregister_listener(nrf_service_id_t service); + +// Send a message to a service +// The message will be queued and sent as soon as possible +// If the queue is full, the message will be dropped +// returns ID of the message if it was successfully queued, otherwise 0 +uint32_t nrf_send_msg(nrf_service_id_t service, const uint8_t *data, + uint32_t len, + void (*callback)(nrf_status_t status, void *context), + void *context); + +// Abort a message by ID +// If the message is already sent or the id is not found, it does nothing and +// returns false If the message is queued, it will be removed from the queue If +// the message is being sent, it will be sent. The callback will not be called. +bool nrf_abort_msg(uint32_t id); + +#endif diff --git a/core/embed/trezorhal/stm32f4/nrf/nrf_internal.h b/core/embed/trezorhal/stm32f4/nrf/nrf_internal.h new file mode 100644 index 0000000000..c6bdc2b1b5 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/nrf/nrf_internal.h @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_NRF_INTERNAL_H +#define TREZORHAL_NRF_INTERNAL_H + +#include + +void nrf_dfu_comm_send(const uint8_t *data, uint32_t len); +uint32_t nrf_dfu_comm_receive(uint8_t *data, uint32_t len); + +void nrf_int_send(const uint8_t *data, uint32_t len); +uint32_t nrf_int_receive(uint8_t *data, uint32_t len); + +bool nrf_firmware_running(void); + +bool nrf_reboot(void); +bool nrf_reboot_to_bootloader(void); + +void nrf_signal_running(void); +void nrf_signal_off(void); + +#endif diff --git a/core/embed/trezorhal/stm32f4/syscall_dispatch.c b/core/embed/trezorhal/stm32f4/syscall_dispatch.c index 81fc736c73..07e9dfe4a8 100644 --- a/core/embed/trezorhal/stm32f4/syscall_dispatch.c +++ b/core/embed/trezorhal/stm32f4/syscall_dispatch.c @@ -21,6 +21,7 @@ #include "syscall.h" +#include "ble.h" #include "bootutils.h" #include "button.h" #include "display.h" @@ -643,6 +644,43 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args, firmware_hash_callback_wrapper, callback_context); } break; +#ifdef USE_BLE + case SYSCALL_BLE_START: { + ble_start(); + } break; + + case SYSCALL_BLE_ISSUE_COMMAND: { + ble_command_t command = args[0]; + ble_issue_command(command); + } break; + + case SYSCALL_BLE_GET_STATE: { + ble_state_t *state = (ble_state_t *)args[0]; + ble_get_state__verified(state); + } break; + + case SYSCALL_BLE_READ_EVENT: { + ble_event_t *event = (ble_event_t *)args[0]; + args[0] = ble_read_event__verified(event); + } break; + + case SYSCALL_BLE_CAN_WRITE: { + args[0] = ble_can_write(); + } break; + + case SYSCALL_BLE_WRITE: { + uint8_t *data = (uint8_t *)args[0]; + size_t len = args[1]; + args[0] = ble_write__verified(data, len); + } break; + + case SYSCALL_BLE_READ: { + uint8_t *data = (uint8_t *)args[0]; + size_t len = args[1]; + args[0] = ble_read__verified(data, len); + } break; +#endif + default: args[0] = 0xffffffff; break; diff --git a/core/embed/trezorhal/stm32f4/syscall_numbers.h b/core/embed/trezorhal/stm32f4/syscall_numbers.h index 0f0c1ad666..500bcbd630 100644 --- a/core/embed/trezorhal/stm32f4/syscall_numbers.h +++ b/core/embed/trezorhal/stm32f4/syscall_numbers.h @@ -136,6 +136,14 @@ typedef enum { SYSCALL_FIRMWARE_GET_VENDOR, SYSCALL_FIRMWARE_CALC_HASH, + SYSCALL_BLE_START, + SYSCALL_BLE_ISSUE_COMMAND, + SYSCALL_BLE_READ_EVENT, + SYSCALL_BLE_GET_STATE, + SYSCALL_BLE_CAN_WRITE, + SYSCALL_BLE_WRITE, + SYSCALL_BLE_READ, + } syscall_number_t; #endif // SYSCALL_NUMBERS_H diff --git a/core/embed/trezorhal/stm32f4/syscall_stubs.c b/core/embed/trezorhal/stm32f4/syscall_stubs.c index f20aeb7db8..4322178320 100644 --- a/core/embed/trezorhal/stm32f4/syscall_stubs.c +++ b/core/embed/trezorhal/stm32f4/syscall_stubs.c @@ -605,6 +605,42 @@ secbool firmware_calc_hash(const uint8_t *challenge, size_t challenge_len, return syscall_invoke6((uint32_t)challenge, challenge_len, (uint32_t)hash, hash_len, (uint32_t)firmware_hash_callback_wrapper, (uint32_t)callback_context, + SYSCALL_FIRMWARE_CALC_HASH); } + +#ifdef USE_BLE + +// ============================================================================= +// ble.h +// ============================================================================= + +#include "ble.h" + +void ble_start(void) { syscall_invoke0(SYSCALL_BLE_START); } + +bool ble_issue_command(ble_command_t command) { + return (bool)syscall_invoke1((uint32_t)command, SYSCALL_BLE_ISSUE_COMMAND); +} + +bool ble_read_event(ble_event_t *event) { + return (bool)syscall_invoke1((uint32_t)event, SYSCALL_BLE_READ_EVENT); +} + +void ble_get_state(ble_state_t *state) { + syscall_invoke1((uint32_t)state, SYSCALL_BLE_GET_STATE); +} + +bool ble_can_write(void) { return syscall_invoke0(SYSCALL_BLE_CAN_WRITE); } + +bool ble_write(const uint8_t *data, uint16_t len) { + return syscall_invoke2((uint32_t)data, len, SYSCALL_BLE_WRITE); +} + +uint32_t ble_read(uint8_t *data, uint16_t len) { + return (uint32_t)syscall_invoke2((uint32_t)data, len, SYSCALL_BLE_READ); +} + +#endif + #endif diff --git a/core/embed/trezorhal/stm32f4/syscall_verifiers.c b/core/embed/trezorhal/stm32f4/syscall_verifiers.c index 371fdea8ea..b09f885ba4 100644 --- a/core/embed/trezorhal/stm32f4/syscall_verifiers.c +++ b/core/embed/trezorhal/stm32f4/syscall_verifiers.c @@ -694,4 +694,54 @@ access_violation: return secfalse; } +// --------------------------------------------------------------------- + +void ble_get_state__verified(ble_state_t *state) { + if (!probe_write_access(state, sizeof(*state))) { + goto access_violation; + } + + ble_get_state(state); + return; + +access_violation: + apptask_access_violation(); +} + +bool ble_read_event__verified(ble_event_t *event) { + if (!probe_write_access(event, sizeof(*event))) { + goto access_violation; + } + + return ble_read_event(event); + +access_violation: + apptask_access_violation(); + return false; +} + +bool ble_write__verified(const uint8_t *data, size_t len) { + if (!probe_read_access(data, len)) { + goto access_violation; + } + + return ble_write(data, len); + +access_violation: + apptask_access_violation(); + return false; +} + +uint32_t ble_read__verified(uint8_t *data, size_t len) { + if (!probe_write_access(data, len)) { + goto access_violation; + } + + return ble_read(data, len); + +access_violation: + apptask_access_violation(); + return 0; +} + #endif // SYSCALL_DISPATCH diff --git a/core/embed/trezorhal/stm32f4/syscall_verifiers.h b/core/embed/trezorhal/stm32f4/syscall_verifiers.h index 11e2846e00..7cbabe6882 100644 --- a/core/embed/trezorhal/stm32f4/syscall_verifiers.h +++ b/core/embed/trezorhal/stm32f4/syscall_verifiers.h @@ -173,6 +173,16 @@ secbool firmware_calc_hash__verified(const uint8_t *challenge, secbool firmware_get_vendor__verified(char *buff, size_t buff_size); +// --------------------------------------------------------------------- +#include "ble.h" +void ble_get_state__verified(ble_state_t *state); + +bool ble_read_event__verified(ble_event_t *event); + +bool ble_write__verified(const uint8_t *data, size_t len); + +secbool ble_read__verified(uint8_t *data, size_t len); + #endif // SYSCALL_DISPATCH #endif // TREZORHAL_SYSCALL_VERIFIERS_H diff --git a/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.c b/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.c new file mode 100644 index 0000000000..0b235d4852 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.c @@ -0,0 +1,301 @@ +/* + * 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 + +#include "irq.h" +#include "tsqueue.h" + +// Initialize the queue +void tsqueue_init(tsqueue_t *queue, tsqueue_entry_t *entries, + uint8_t *buffer_mem, uint16_t size, int qlen) { + irq_key_t key = irq_lock(); + queue->entries = entries; + queue->rix = 0; + queue->fix = 0; + queue->pix = 0; + queue->wix = 0; + queue->qlen = qlen; + queue->size = size; + queue->overrun = false; + queue->overrun_count = 0; + queue->next_id = 1; + + for (int i = 0; i < qlen; i++) { + if (buffer_mem != NULL) { + queue->entries[i].buffer = buffer_mem + i * size; + memset(queue->entries[i].buffer, 0, size); + } + queue->entries[i].state = TSQUEUE_ENTRY_EMPTY; + queue->entries[i].len = 0; + } + + irq_unlock(key); +} + +static void tsqueue_entry_reset(tsqueue_entry_t *entry, uint32_t data_size) { + entry->state = TSQUEUE_ENTRY_EMPTY; + entry->len = 0; + entry->aborted = false; + entry->id = 0; + memset(entry->buffer, 0, data_size); +} + +void tsqueue_reset(tsqueue_t *queue) { + irq_key_t key = irq_lock(); + queue->rix = 0; + queue->fix = 0; + queue->pix = 0; + queue->wix = 0; + queue->overrun = false; + queue->overrun_count = 0; + queue->next_id = 1; + + for (int i = 0; i < queue->qlen; i++) { + tsqueue_entry_reset(&queue->entries[i], queue->size); + } + + irq_unlock(key); +} + +// Insert data into the queue +bool tsqueue_insert(tsqueue_t *queue, const uint8_t *data, uint16_t len, + uint32_t *id) { + irq_key_t key = irq_lock(); + + if (queue->entries[queue->wix].state != TSQUEUE_ENTRY_EMPTY) { + irq_unlock(key); + return false; + } + + if (len > queue->size) { + irq_unlock(key); + return false; + } + + if (queue->fix != queue->wix) { + // Some item is already allocated, return false + irq_unlock(key); + return false; + } + + memcpy(queue->entries[queue->wix].buffer, data, len); + queue->entries[queue->wix].state = TSQUEUE_ENTRY_FULL; + queue->entries[queue->wix].len = len; + queue->entries[queue->wix].id = queue->next_id++; + if (id != NULL) { + *id = queue->entries[queue->wix].id; + } + queue->wix = (queue->wix + 1) % queue->qlen; + queue->fix = queue->wix; + + irq_unlock(key); + return true; +} + +// Allocate an entry in the queue +uint8_t *tsqueue_allocate(tsqueue_t *queue, uint32_t *id) { + irq_key_t key = irq_lock(); + + if (queue->entries[queue->wix].state != TSQUEUE_ENTRY_EMPTY) { + queue->overrun = true; + queue->overrun_count++; + irq_unlock(key); + return NULL; + } + + if (queue->fix != queue->wix) { + // Some item is already allocated, return NULL + irq_unlock(key); + return NULL; + } + + queue->entries[queue->wix].state = TSQUEUE_ENTRY_ALLOCATED; + queue->entries[queue->wix].id = queue->next_id++; + if (id != NULL) { + *id = queue->entries[queue->wix].id; + } + uint8_t *buffer = queue->entries[queue->wix].buffer; + queue->fix = queue->wix; + queue->wix = (queue->wix + 1) % queue->qlen; + + irq_unlock(key); + return buffer; +} + +// Finalize an allocated entry +bool tsqueue_finalize(tsqueue_t *queue, const uint8_t *buffer, uint16_t len) { + irq_key_t key = irq_lock(); + + if (queue->entries[queue->fix].state != TSQUEUE_ENTRY_ALLOCATED) { + irq_unlock(key); + return false; + } + if (queue->entries[queue->fix].buffer != buffer) { + irq_unlock(key); + return false; + } + + queue->entries[queue->fix].len = len; + queue->entries[queue->fix].state = TSQUEUE_ENTRY_FULL; + queue->fix = (queue->fix + 1) % queue->qlen; + + irq_unlock(key); + return true; +} + +static void tsqueue_discard_aborted(tsqueue_t *queue) { + while (queue->entries[queue->rix].aborted) { + tsqueue_entry_reset(&queue->entries[queue->rix], queue->size); + queue->rix = (queue->rix + 1) % queue->qlen; + queue->pix = queue->rix; + } +} + +// Read data from the queue +bool tsqueue_read(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len) { + irq_key_t key = irq_lock(); + + tsqueue_discard_aborted(queue); + + if (queue->entries[queue->rix].state != TSQUEUE_ENTRY_FULL) { + irq_unlock(key); + return false; + } + + if (queue->rix != queue->pix) { + // Some item is being processed, return false + irq_unlock(key); + return false; + } + + if (len != NULL) { + *len = queue->entries[queue->rix].len; + } + + memcpy(data, queue->entries[queue->rix].buffer, + MIN(queue->entries[queue->rix].len, max_len)); + tsqueue_entry_reset(queue->entries + queue->rix, queue->size); + queue->rix = (queue->rix + 1) % queue->qlen; + queue->pix = queue->rix; + + tsqueue_discard_aborted(queue); + + irq_unlock(key); + return true; +} + +// Process an entry in the queue +uint8_t *tsqueue_process(tsqueue_t *queue, uint16_t *len) { + irq_key_t key = irq_lock(); + + tsqueue_discard_aborted(queue); + + if (queue->entries[queue->rix].state == TSQUEUE_ENTRY_FULL) { + queue->entries[queue->rix].state = TSQUEUE_ENTRY_PROCESSING; + } else { + irq_unlock(key); + return NULL; + } + + if (queue->pix != queue->rix) { + // Some item is already being processed, return NULL + irq_unlock(key); + return NULL; + } + + queue->pix = queue->rix; + queue->rix = (queue->rix + 1) % queue->qlen; + if (len != NULL) { + *len = queue->entries[queue->pix].len; + } + + irq_unlock(key); + return queue->entries[queue->pix].buffer; +} + +// Mark processing as done +bool tsqueue_process_done(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len, bool *aborted) { + irq_key_t key = irq_lock(); + + if (queue->entries[queue->pix].state != TSQUEUE_ENTRY_PROCESSING) { + irq_unlock(key); + return false; + } + + if (len != NULL) { + *len = queue->entries[queue->pix].len; + } + + if (aborted != NULL) { + *aborted = queue->entries[queue->pix].aborted; + } + + memcpy(data, queue->entries[queue->pix].buffer, + MIN(queue->entries[queue->pix].len, max_len)); + + tsqueue_entry_reset(queue->entries + queue->pix, queue->size); + + queue->pix = (queue->pix + 1) % queue->qlen; + + tsqueue_discard_aborted(queue); + + irq_unlock(key); + + return true; +} + +// Check if the queue is full +bool tsqueue_full(tsqueue_t *queue) { + irq_key_t key = irq_lock(); + + tsqueue_discard_aborted(queue); + + bool full = queue->entries[queue->wix].state != TSQUEUE_ENTRY_EMPTY; + irq_unlock(key); + return full; +} + +bool tsqueue_abort(tsqueue_t *queue, uint32_t id, uint8_t *data, + uint16_t max_len, uint16_t *len) { + bool found = false; + irq_key_t key = irq_lock(); + + for (int i = 0; i < queue->qlen; i++) { + if (queue->entries[i].state != TSQUEUE_ENTRY_EMPTY && + queue->entries[i].id == id) { + queue->entries[i].aborted = true; + if (len != NULL) { + *len = queue->entries[i].len; + } + + if (data != NULL) { + memcpy(data, queue->entries[i].buffer, + MIN(queue->entries[i].len, max_len)); + } + + found = true; + } + } + + irq_unlock(key); + return found; +} diff --git a/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.h b/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.h new file mode 100644 index 0000000000..0d7fc2945e --- /dev/null +++ b/core/embed/trezorhal/stm32f4/tsqueue/tsqueue.h @@ -0,0 +1,94 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_TSQUEUE_H +#define TREZORHAL_TSQUEUE_H + +#include + +typedef enum { + TSQUEUE_ENTRY_EMPTY = 0, + TSQUEUE_ENTRY_ALLOCATED = 1, + TSQUEUE_ENTRY_FULL = 2, + TSQUEUE_ENTRY_PROCESSING = 3, +} tsqueue_entry_state_t; + +typedef struct { + uint8_t *buffer; // Pointer to the data buffer + tsqueue_entry_state_t state; // State of the queue entry + uint16_t len; // Length of data in the buffer + uint32_t id; // ID of the entry + bool aborted; // Aborted flag +} tsqueue_entry_t; + +typedef struct { + tsqueue_entry_t *entries; // Array of queue entries + int rix; // Read index + int fix; // Finalize index + int pix; // Process index + int wix; // Write index + int qlen; // Queue length + bool overrun; // Overrun flag + uint16_t overrun_count; // Overrun counter + uint16_t size; // Size of each buffer + uint32_t next_id; // ID of the next item +} tsqueue_t; + +// Initialize the queue +void tsqueue_init(tsqueue_t *queue, tsqueue_entry_t *entries, + uint8_t *buffer_mem, uint16_t size, int qlen); + +void tsqueue_reset(tsqueue_t *queue); + +// Insert data into the queue +bool tsqueue_insert(tsqueue_t *queue, const uint8_t *data, uint16_t len, + uint32_t *id); + +// Allocate an entry in the queue +// Returns a pointer to the allocated buffer +// Fails if some item is already allocated, NULL +// To be used instead of insert function, in conjunction with tsqueue_finalize +uint8_t *tsqueue_allocate(tsqueue_t *queue, uint32_t *id); + +// Finalize an allocated entry +bool tsqueue_finalize(tsqueue_t *queue, const uint8_t *buffer, uint16_t len); + +// Read data from the queue +bool tsqueue_read(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len); + +// Process an entry in the queue +// Returns a pointer to the buffer to be processed +// Fails if some item is already being processed, returns NULL +// To be used in conjunction with tsqueue_process_done +uint8_t *tsqueue_process(tsqueue_t *queue, uint16_t *len); + +// Mark processing as done +bool tsqueue_process_done(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len, bool *aborted); + +// Checks if the queue is full +bool tsqueue_full(tsqueue_t *queue); + +// Aborts item in the queue +// The space in the queue is not freed until the item is attempted to be read +bool tsqueue_abort(tsqueue_t *queue, uint32_t id, uint8_t *data, + uint16_t max_len, uint16_t *len); + +#endif diff --git a/core/site_scons/models/stm32f4_common.py b/core/site_scons/models/stm32f4_common.py index 400595207f..b702a9ee12 100644 --- a/core/site_scons/models/stm32f4_common.py +++ b/core/site_scons/models/stm32f4_common.py @@ -67,6 +67,7 @@ def stm32f4_common_files(env, defines, sources, paths): "embed/trezorhal/stm32f4/systick.c", "embed/trezorhal/stm32f4/systimer.c", "embed/trezorhal/stm32f4/time_estimate.c", + "embed/trezorhal/stm32f4/tsqueue/tsqueue.c", "embed/trezorhal/stm32f4/unit_properties.c", "embed/trezorhal/stm32f4/vectortable.S", ]