diff --git a/core/SConscript.firmware b/core/SConscript.firmware index ce0b3bcdfab..b4c9564f717 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -38,7 +38,7 @@ FEATURE_FLAGS = { "AES_GCM": BENCHMARK or THP, } -FEATURES_WANTED = ["input", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"] +FEATURES_WANTED = ["input", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic", "ble"] if DISABLE_OPTIGA: if PYOPT != '0': raise RuntimeError("DISABLE_OPTIGA requires PYOPT=0") diff --git a/core/SConscript.kernel b/core/SConscript.kernel index 56815449436..6cdf868ec91 100644 --- a/core/SConscript.kernel +++ b/core/SConscript.kernel @@ -30,7 +30,7 @@ FEATURE_FLAGS = { "AES_GCM": False, } -FEATURES_WANTED = ["input", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"] +FEATURES_WANTED = ["input", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic", "ble"] if DISABLE_OPTIGA: # TODO use PYOPT instead of PRODUCTION, same as in firmware, blocked on #4253 if PRODUCTION: diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h new file mode 100644 index 00000000000..26d9006a043 --- /dev/null +++ b/core/embed/io/ble/inc/io/ble.h @@ -0,0 +1,128 @@ +/* + * 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_PAIRING_CANCELLED = 4, // Pairing was cancelled by host +} 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; + bool connectable; + bool pairing; + bool pairing_requested; + 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. +bool 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_get_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); + +// Check if read is possible +bool ble_can_read(void); + +// 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/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c new file mode 100644 index 00000000000..68c2f57c03a --- /dev/null +++ b/core/embed/io/ble/stm32/ble.c @@ -0,0 +1,588 @@ +/* + * 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 +#include +#include +#include + +#include "ble_comm_defs.h" + +typedef enum { + BLE_MODE_OFF, + BLE_MODE_CONNECTABLE, + BLE_MODE_PAIRING, + BLE_MODE_DFU, +} ble_mode_t; + +// changing value of TX_QUEUE_LEN is not allowed +// as it might result in order of messages being changed +#define TX_QUEUE_LEN 1 +#define EVENT_QUEUE_LEN 4 +#define RX_QUEUE_LEN 16 +#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; + bool pairing_requested; + ble_event_t event_queue_buffers[EVENT_QUEUE_LEN]; + tsqueue_entry_t event_queue_entries[EVENT_QUEUE_LEN]; + tsqueue_t event_queue; + + uint8_t rx_queue_buffers[RX_QUEUE_LEN][BLE_RX_PACKET_SIZE]; + tsqueue_entry_t rx_queue_entries[RX_QUEUE_LEN]; + tsqueue_t rx_queue; + + uint8_t tx_queue_buffers[TX_QUEUE_LEN][NRF_MAX_TX_DATA_SIZE]; + tsqueue_entry_t ts_queue_entries[TX_QUEUE_LEN]; + tsqueue_t tx_queue; + + systimer_t *timer; + uint16_t ping_cntr; +} ble_driver_t; + +static ble_driver_t g_ble_driver = {0}; + +static bool ble_send_state_request(ble_driver_t *drv) { + (void)drv; + uint8_t cmd = INTERNAL_CMD_PING; + return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL) >= + 0; +} + +static bool ble_send_advertising_on(ble_driver_t *drv, bool whitelist) { + (void)drv; + uint8_t data[2]; + data[0] = INTERNAL_CMD_ADVERTISING_ON; + data[1] = whitelist ? 1 : 0; + return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, data, sizeof(data), NULL, + NULL) >= 0; +} + +static bool ble_send_advertising_off(ble_driver_t *drv) { + (void)drv; + uint8_t cmd = INTERNAL_CMD_ADVERTISING_OFF; + return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL) >= + 0; +} + +static bool ble_send_erase_bonds(ble_driver_t *drv) { + (void)drv; + uint8_t cmd = INTERNAL_CMD_ERASE_BONDS; + return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL) >= + 0; +} + +static bool ble_send_disconnect(ble_driver_t *drv) { + (void)drv; + uint8_t cmd = INTERNAL_CMD_DISCONNECT; + return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL) >= + 0; +} + +static bool ble_send_pairing_reject(ble_driver_t *drv) { + uint8_t cmd = INTERNAL_CMD_REJECT_PAIRING; + bool result = + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); + + if (result) { + drv->pairing_requested = false; + } + + return result; +} + +static bool ble_send_pairing_accept(ble_driver_t *drv) { + uint8_t cmd = INTERNAL_CMD_ALLOW_PAIRING; + bool result = + nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL); + + if (result) { + drv->pairing_requested = false; + } + + return result; +} + +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_enqueue(&drv->event_queue, (uint8_t *)&event, sizeof(event), + NULL); + + } else { + // connection lost + ble_event_t event = {.type = BLE_DISCONNECTED}; + tsqueue_enqueue(&drv->event_queue, (uint8_t *)&event, sizeof(event), + NULL); + + if (drv->mode_current == BLE_MODE_PAIRING) { + drv->mode_requested = BLE_MODE_CONNECTABLE; + } + + drv->pairing_requested = false; + } + + 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(drv); + return; + } + + ble_event_t event = {.type = BLE_PAIRING_REQUEST, .data_len = 6}; + memcpy(event.data, &data[1], 6); + if (!tsqueue_enqueue(&drv->event_queue, (uint8_t *)&event, sizeof(event), + NULL)) { + ble_send_pairing_reject(drv); + } else { + drv->pairing_requested = true; + } +} + +static void ble_process_rx_msg_pairing_cancelled(const uint8_t *data, + uint32_t len) { + ble_driver_t *drv = &g_ble_driver; + if (!drv->initialized) { + return; + } + + ble_event_t event = {.type = BLE_PAIRING_CANCELLED, .data_len = 0}; + tsqueue_enqueue(&drv->event_queue, (uint8_t *)&event, sizeof(event), NULL); + drv->pairing_requested = false; +} + +static void ble_process_rx_msg(const uint8_t *data, uint32_t len) { + if (len < 1) { + return; + } + + 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; + case INTERNAL_EVENT_PAIRING_CANCELLED: + ble_process_rx_msg_pairing_cancelled(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; + } + + tsqueue_enqueue(&drv->rx_queue, data, len, NULL); +} + +// background loop, called from systimer every 10ms +static void ble_loop(void *context) { + ble_driver_t *drv = (ble_driver_t *)context; + + if (!drv->initialized) { + return; + } + + if (nrf_is_running()) { + if (drv->ping_cntr == 0) { + ble_send_state_request(drv); + } + + drv->ping_cntr++; + if (drv->ping_cntr >= PING_PERIOD / LOOP_PERIOD_MS) { + drv->ping_cntr = 0; + } + + uint8_t data[NRF_MAX_TX_DATA_SIZE] = {0}; + if (tsqueue_dequeue(&drv->tx_queue, data, NRF_MAX_TX_DATA_SIZE, NULL, + NULL)) { + if (!nrf_send_msg(NRF_SERVICE_BLE, data, NRF_MAX_TX_DATA_SIZE, NULL, + NULL)) { + tsqueue_enqueue(&drv->tx_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(drv); + // if (drv->connected) { + // nrf_send_disconnect(); + // } + } else if (drv->mode_requested == BLE_MODE_CONNECTABLE) { + ble_send_advertising_on(drv, true); + } else if (drv->mode_requested == BLE_MODE_PAIRING) { + ble_send_advertising_on(drv, false); + } + } + } else { + drv->status_valid = false; + } +} + +bool ble_init(void) { + ble_driver_t *drv = &g_ble_driver; + + if (drv->initialized) { + return true; + } + + memset(drv, 0, sizeof(ble_driver_t)); + + tsqueue_init(&drv->event_queue, drv->event_queue_entries, + (uint8_t *)drv->event_queue_buffers, sizeof(ble_event_t), + EVENT_QUEUE_LEN); + + tsqueue_init(&drv->rx_queue, drv->rx_queue_entries, + (uint8_t *)drv->rx_queue_buffers, BLE_RX_PACKET_SIZE, + RX_QUEUE_LEN); + + tsqueue_init(&drv->tx_queue, drv->ts_queue_entries, + (uint8_t *)drv->tx_queue_buffers, NRF_MAX_TX_DATA_SIZE, + TX_QUEUE_LEN); + + drv->timer = systimer_create(ble_loop, drv); + + if (drv->timer == NULL) { + goto cleanup; + } + + systimer_set_periodic(drv->timer, LOOP_PERIOD_MS); + + nrf_init(); + if (!nrf_register_listener(NRF_SERVICE_BLE_MANAGER, ble_process_rx_msg)) { + goto cleanup; + } + if (!nrf_register_listener(NRF_SERVICE_BLE, ble_process_data)) { + goto cleanup; + } + + drv->initialized = true; + return true; + +cleanup: + if (drv->timer != NULL) { + systimer_delete(drv->timer); + } + nrf_deinit(); + memset(drv, 0, sizeof(ble_driver_t)); + return false; +} + +void ble_deinit(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + nrf_unregister_listener(NRF_SERVICE_BLE); + nrf_unregister_listener(NRF_SERVICE_BLE_MANAGER); + + systimer_delete(drv->timer); + + tsqueue_reset(&drv->event_queue); + tsqueue_reset(&drv->rx_queue); + tsqueue_reset(&drv->tx_queue); + + nrf_deinit(); + + drv->initialized = false; +} + +bool ble_connected(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + bool connected = drv->connected && nrf_is_running(); + + irq_unlock(key); + + return connected; +} + +void ble_start(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + irq_key_t key = irq_lock(); + + drv->accept_msgs = true; + + irq_unlock(key); +} + +void ble_stop(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return; + } + + irq_key_t key = irq_lock(); + + drv->accept_msgs = false; + tsqueue_reset(&drv->rx_queue); + + irq_unlock(key); +} + +bool ble_can_write(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + if (!drv->connected || !drv->accept_msgs) { + irq_unlock(key); + return false; + } + + bool full = !tsqueue_full(&drv->tx_queue); + + irq_unlock(key); + + return full; +} + +bool ble_write(const uint8_t *data, uint16_t len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + if (!drv->connected || !drv->accept_msgs) { + irq_unlock(key); + return false; + } + + bool sent = nrf_send_msg(NRF_SERVICE_BLE, data, len, NULL, NULL); + + if (!sent) { + bool queued = tsqueue_enqueue(&drv->tx_queue, data, len, NULL); + irq_unlock(key); + return queued; + } + + irq_unlock(key); + return true; +} + +bool ble_can_read(void) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + bool result = !tsqueue_empty(&drv->rx_queue); + + irq_unlock(key); + + return result; +} + +uint32_t ble_read(uint8_t *data, uint16_t max_len) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return 0; + } + + irq_key_t key = irq_lock(); + + tsqueue_t *queue = &drv->rx_queue; + + uint16_t read_len = 0; + + tsqueue_dequeue(queue, data, max_len, &read_len, NULL); + + irq_unlock(key); + + return read_len; +} + +bool ble_issue_command(ble_command_t command) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + bool result = 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: + result = ble_send_disconnect(drv); + break; + case BLE_ERASE_BONDS: + result = ble_send_erase_bonds(drv); + break; + case BLE_ALLOW_PAIRING: + result = ble_send_pairing_accept(drv); + break; + case BLE_REJECT_PAIRING: + result = ble_send_pairing_reject(drv); + break; + default: + break; + } + + irq_unlock(key); + + return result; +} + +bool ble_get_event(ble_event_t *event) { + ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t key = irq_lock(); + + bool result = tsqueue_dequeue(&drv->event_queue, (uint8_t *)event, + sizeof(*event), NULL, NULL); + + irq_unlock(key); + + return result; +} + +void ble_get_state(ble_state_t *state) { + const ble_driver_t *drv = &g_ble_driver; + + if (!drv->initialized) { + memset(state, 0, sizeof(ble_state_t)); + return; + } + + irq_key_t key = irq_lock(); + + state->connected = drv->connected; + state->peer_count = drv->peer_count; + state->pairing = drv->mode_current == BLE_MODE_PAIRING; + state->connectable = drv->mode_current == BLE_MODE_CONNECTABLE; + state->pairing_requested = drv->pairing_requested; + + irq_unlock(key); +} + +#endif diff --git a/core/embed/io/ble/stm32/ble_comm_defs.h b/core/embed/io/ble/stm32/ble_comm_defs.h new file mode 100644 index 00000000000..b32c7b4497b --- /dev/null +++ b/core/embed/io/ble/stm32/ble_comm_defs.h @@ -0,0 +1,59 @@ +/* + * 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, + INTERNAL_EVENT_PAIRING_CANCELLED = 0x05, +} internal_event_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, +} internal_cmd_t; +#endif diff --git a/core/embed/io/nrf/crc8.c b/core/embed/io/nrf/crc8.c new file mode 100644 index 00000000000..6cfae591947 --- /dev/null +++ b/core/embed/io/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/io/nrf/crc8.h b/core/embed/io/nrf/crc8.h new file mode 100644 index 00000000000..f26e33730a1 --- /dev/null +++ b/core/embed/io/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/io/nrf/inc/io/nrf.h b/core/embed/io/nrf/inc/io/nrf.h new file mode 100644 index 00000000000..73c4c044398 --- /dev/null +++ b/core/embed/io/nrf/inc/io/nrf.h @@ -0,0 +1,77 @@ +/* + * 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 void (*nrf_rx_callback_t)(const uint8_t *data, uint32_t len); +typedef void (*nrf_tx_callback_t)(nrf_status_t status, void *context); + +// 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 +// Returns false if a listener for the service is already registered +bool nrf_register_listener(nrf_service_id_t service, + nrf_rx_callback_t callback); + +// 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 -1 +int32_t nrf_send_msg(nrf_service_id_t service, const uint8_t *data, + uint32_t len, nrf_tx_callback_t callback, 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(int32_t id); + +#endif diff --git a/core/embed/io/nrf/nrf_internal.h b/core/embed/io/nrf/nrf_internal.h new file mode 100644 index 00000000000..c6bdc2b1b51 --- /dev/null +++ b/core/embed/io/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/io/nrf/stm32u5/nrf.c b/core/embed/io/nrf/stm32u5/nrf.c new file mode 100644 index 00000000000..90dc4c7f9d2 --- /dev/null +++ b/core/embed/io/nrf/stm32u5/nrf.c @@ -0,0 +1,764 @@ +/* + * 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 +#include +#include +#include +#include + +#include "../crc8.h" +#include "../nrf_internal.h" + +#define MAX_SPI_DATA_SIZE (244) + +typedef struct { + uint8_t service_id; + uint8_t data[MAX_SPI_DATA_SIZE]; + uint8_t crc; +} spi_packet_t; + +typedef struct { + uint8_t service_id; + uint8_t msg_len; + uint8_t data[NRF_MAX_TX_DATA_SIZE + 1]; + // uint8_t crc; part of data, as it has variable position +} uart_packet_t; + +#define UART_OVERHEAD_SIZE (sizeof(uart_packet_t) - NRF_MAX_TX_DATA_SIZE) +#define UART_HEADER_SIZE (UART_OVERHEAD_SIZE - 1) + +#define TX_QUEUE_SIZE (8) + +#define START_BYTE (0xA0) + +typedef struct { + uart_packet_t packet; + nrf_tx_callback_t callback; + void *context; +} nrf_tx_request_t; + +typedef struct { + UART_HandleTypeDef urt; + DMA_HandleTypeDef urt_tx_dma; + + uint8_t tx_buffers[TX_QUEUE_SIZE][sizeof(nrf_tx_request_t)]; + tsqueue_entry_t tx_queue_entries[TX_QUEUE_SIZE]; + tsqueue_t tx_queue; + nrf_tx_request_t tx_request; + int32_t tx_request_id; + + uart_packet_t rx_buffer; + uint8_t rx_len; + uint8_t rx_byte; + uint16_t rx_idx; + + SPI_HandleTypeDef spi; + DMA_HandleTypeDef spi_dma; + spi_packet_t long_rx_buffer; + + bool comm_running; + bool initialized; + + nrf_rx_callback_t service_listeners[NRF_SERVICE_CNT]; + +} nrf_driver_t; + +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, (uint8_t *)&drv->long_rx_buffer, + sizeof(spi_packet_t)); + + tsqueue_reset(&drv->tx_queue); + HAL_UART_Receive_IT(&drv->urt, &drv->rx_byte, 1); + + drv->comm_running = true; + + nrf_signal_running(); +} + +static void nrf_abort_urt_comm(nrf_driver_t *drv) { + HAL_UART_AbortReceive(&drv->urt); + HAL_UART_AbortTransmit(&drv->urt); + + if (drv->tx_request.callback != NULL) { + drv->tx_request.callback(NRF_STATUS_ERROR, drv->tx_request.context); + } + + drv->rx_idx = 0; + drv->rx_len = 0; + drv->tx_request_id = -1; + + while (tsqueue_dequeue(&drv->tx_queue, (uint8_t *)&drv->tx_request, + sizeof(nrf_tx_request_t), NULL, NULL)) { + if (drv->tx_request.callback != NULL) { + drv->tx_request.callback(NRF_STATUS_ERROR, drv->tx_request.context); + } + } + + memset(&drv->tx_request, 0, sizeof(nrf_tx_request_t)); + + tsqueue_reset(&drv->tx_queue); +} + +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); + nrf_abort_urt_comm(drv); + irq_unlock(key); +} + +void nrf_init(void) { + nrf_driver_t *drv = &g_nrf_driver; + + if (drv->initialized) { + return; + } + + __HAL_RCC_USART3_CLK_ENABLE(); + __HAL_RCC_GPDMA1_CLK_ENABLE(); + __HAL_RCC_SPI1_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + + memset(drv, 0, sizeof(*drv)); + tsqueue_init(&drv->tx_queue, drv->tx_queue_entries, + (uint8_t *)drv->tx_buffers, sizeof(nrf_tx_request_t), + TX_QUEUE_SIZE); + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // synchronization signals + NRF_OUT_RESET_CLK_ENA(); + HAL_GPIO_WritePin(NRF_OUT_RESET_PORT, NRF_OUT_RESET_PIN, GPIO_PIN_SET); + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = NRF_OUT_RESET_PIN; + HAL_GPIO_Init(NRF_OUT_RESET_PORT, &GPIO_InitStructure); + + NRF_IN_GPIO0_CLK_ENA(); + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = NRF_IN_GPIO0_PIN; + HAL_GPIO_Init(NRF_IN_GPIO0_PORT, &GPIO_InitStructure); + + NRF_IN_FW_RUNNING_CLK_ENA(); + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = NRF_IN_FW_RUNNING_PIN; + HAL_GPIO_Init(NRF_IN_FW_RUNNING_PORT, &GPIO_InitStructure); + + NRF_OUT_STAY_IN_BLD_CLK_ENA(); + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = NRF_OUT_STAY_IN_BLD_PIN; + HAL_GPIO_Init(NRF_OUT_STAY_IN_BLD_PORT, &GPIO_InitStructure); + + NRF_OUT_FW_RUNNING_CLK_ENA(); + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = NRF_OUT_FW_RUNNING_PIN; + HAL_GPIO_Init(NRF_OUT_FW_RUNNING_PORT, &GPIO_InitStructure); + + // UART PINS + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Alternate = GPIO_AF7_USART3; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + + GPIO_InitStructure.Pin = GPIO_PIN_5; + HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); + GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_1; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + GPIO_InitStructure.Pin = GPIO_PIN_11; + HAL_GPIO_Init(GPIOD, &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 = USART3; + drv->urt.hdmatx = &drv->urt_tx_dma; + + drv->urt_tx_dma.Init.Direction = DMA_MEMORY_TO_PERIPH; + drv->urt_tx_dma.Init.Mode = DMA_NORMAL; + drv->urt_tx_dma.Instance = GPDMA1_Channel1; + drv->urt_tx_dma.Init.Request = GPDMA1_REQUEST_USART3_TX; + drv->urt_tx_dma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; + drv->urt_tx_dma.Init.SrcInc = DMA_SINC_INCREMENTED; + drv->urt_tx_dma.Init.DestInc = DMA_DINC_FIXED; + drv->urt_tx_dma.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE; + drv->urt_tx_dma.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE; + drv->urt_tx_dma.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT; + drv->urt_tx_dma.Init.SrcBurstLength = 1; + drv->urt_tx_dma.Init.DestBurstLength = 1; + drv->urt_tx_dma.Init.TransferAllocatedPort = + DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0; + drv->urt_tx_dma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER; + + drv->urt_tx_dma.Parent = &drv->urt; + HAL_DMA_Init(&drv->urt_tx_dma); + HAL_DMA_ConfigChannelAttributes( + &drv->urt_tx_dma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | + DMA_CHANNEL_SRC_SEC | DMA_CHANNEL_DEST_SEC); + + HAL_UART_Init(&drv->urt); + + NVIC_SetPriority(GPDMA1_Channel1_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(GPDMA1_Channel1_IRQn); + NVIC_SetPriority(USART3_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(USART3_IRQn); + + // SPI pins + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Alternate = GPIO_AF5_SPI1; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM; + GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); + + drv->spi_dma.Instance = GPDMA1_Channel2; + drv->spi_dma.Init.Direction = DMA_PERIPH_TO_MEMORY; + drv->spi_dma.Init.Mode = DMA_NORMAL; + drv->spi_dma.Init.Request = GPDMA1_REQUEST_SPI1_RX; + drv->spi_dma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; + drv->spi_dma.Init.SrcInc = DMA_SINC_FIXED; + drv->spi_dma.Init.DestInc = DMA_DINC_INCREMENTED; + drv->spi_dma.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE; + drv->spi_dma.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE; + drv->spi_dma.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT; + drv->spi_dma.Init.SrcBurstLength = 1; + drv->spi_dma.Init.DestBurstLength = 1; + drv->spi_dma.Init.TransferAllocatedPort = + DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0; + drv->spi_dma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER; + + HAL_DMA_Init(&drv->spi_dma); + HAL_DMA_ConfigChannelAttributes( + &drv->spi_dma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC | + DMA_CHANNEL_DEST_SEC); + + drv->spi.Instance = SPI1; + 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(GPDMA1_Channel2_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(GPDMA1_Channel2_IRQn); + NVIC_SetPriority(SPI1_IRQn, IRQ_PRI_NORMAL); + NVIC_EnableIRQ(SPI1_IRQn); + + drv->tx_request_id = -1; + drv->initialized = true; + + nrf_start(); +} + +void nrf_deinit(void) { + nrf_driver_t *drv = &g_nrf_driver; + + nrf_stop(); + + NVIC_DisableIRQ(GPDMA1_Channel2_IRQn); + NVIC_DisableIRQ(SPI1_IRQn); + + __HAL_RCC_SPI1_FORCE_RESET(); + __HAL_RCC_SPI1_RELEASE_RESET(); + + __HAL_RCC_USART1_FORCE_RESET(); + __HAL_RCC_USART1_RELEASE_RESET(); + + drv->initialized = false; +} + +bool nrf_register_listener(nrf_service_id_t service, + nrf_rx_callback_t callback) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return false; + } + + if (service >= NRF_SERVICE_CNT) { + return false; + } + + if (drv->service_listeners[service] != NULL) { + return false; + } + + irq_key_t key = irq_lock(); + drv->service_listeners[service] = callback; + irq_unlock(key); + + return true; +} + +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) { + return; + } + + irq_key_t key = irq_lock(); + drv->service_listeners[service] = NULL; + irq_unlock(key); +} + +static void nrf_process_msg(nrf_driver_t *drv, const uint8_t *data, + uint32_t len, nrf_service_id_t service) { + if (drv->service_listeners[service] != NULL) { + drv->service_listeners[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 +/// --------------------------------------------------------- + +int32_t nrf_send_msg(nrf_service_id_t service, const uint8_t *data, + uint32_t len, nrf_tx_callback_t callback, void *context) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return -1; + } + + if (len > NRF_MAX_TX_DATA_SIZE) { + return -1; + } + + if (service > NRF_SERVICE_CNT) { + return -1; + } + + if (!nrf_is_running()) { + return -1; + } + + int32_t id = 0; + + nrf_tx_request_t tx_request = {0}; + + tx_request.callback = callback; + tx_request.context = context; + tx_request.packet.service_id = 0xA0 | (uint8_t)service; + tx_request.packet.msg_len = len + UART_OVERHEAD_SIZE; + memcpy(&tx_request.packet.data, data, len); + tx_request.packet.data[len] = + crc8((uint8_t *)&tx_request.packet, len + UART_OVERHEAD_SIZE - 1, 0x07, + 0x00, false); + + if (!tsqueue_enqueue(&drv->tx_queue, (uint8_t *)&tx_request, + sizeof(nrf_tx_request_t), &id)) { + return -1; + } + + irq_key_t key = irq_lock(); + if (drv->tx_request_id <= 0) { + int32_t tx_id = 0; + if (tsqueue_dequeue(&drv->tx_queue, (uint8_t *)&drv->tx_request, + sizeof(nrf_tx_request_t), NULL, &tx_id)) { + HAL_UART_Transmit_DMA(&drv->urt, (uint8_t *)&drv->tx_request.packet, + drv->tx_request.packet.msg_len); + drv->tx_request_id = tx_id; + } + } + irq_unlock(key); + + return id; +} + +bool nrf_abort_msg(int32_t id) { + nrf_driver_t *drv = &g_nrf_driver; + if (!drv->initialized) { + return false; + } + + bool aborted = tsqueue_abort(&drv->tx_queue, id, NULL, 0, NULL); + + if (aborted) { + return true; + } + + irq_key_t key = irq_lock(); + if (drv->tx_request_id == id) { + drv->tx_request_id = -1; + irq_unlock(key); + return true; + } + + irq_unlock(key); + return false; +} + +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->rx_idx == 0) { + // received first byte: START BYTE + if (nrf_is_valid_startbyte(drv->rx_byte)) { + drv->rx_buffer.service_id = drv->rx_byte; + drv->rx_idx++; + } else { + // bad message, flush the line + drv->rx_idx = 0; + } + } else if (drv->rx_idx == 1) { + // received second byte: LEN + + drv->rx_buffer.msg_len = drv->rx_byte; + drv->rx_len = drv->rx_byte; + + if (drv->rx_len > sizeof(uart_packet_t)) { + drv->rx_len = 0; + drv->rx_idx = 0; + } else { + drv->rx_idx++; + } + } else if (drv->rx_idx >= UART_HEADER_SIZE && + drv->rx_idx < (drv->rx_len - 1)) { + // receive the rest of the message + + drv->rx_buffer.data[drv->rx_idx - UART_HEADER_SIZE] = drv->rx_byte; + drv->rx_idx++; + + if (drv->rx_idx >= NRF_MAX_TX_DATA_SIZE) { + // message is too long, flush the line + drv->rx_idx = 0; + drv->rx_len = 0; + } + + } else if (drv->rx_idx == (drv->rx_len - 1)) { + // received last byte: CRC + + uint8_t crc = + crc8((uint8_t *)&drv->rx_buffer, drv->rx_len - 1, 0x07, 0x00, false); + + if (drv->rx_byte == crc) { + uart_packet_t *packet = &drv->rx_buffer; + nrf_process_msg(drv, drv->rx_buffer.data, + drv->rx_len - UART_OVERHEAD_SIZE, + packet->service_id & 0x0F); + } + + drv->rx_idx = 0; + drv->rx_len = 0; + + } else { + // bad message, flush the line + drv->rx_idx = 0; + drv->rx_len = 0; + } + } + + // receive the rest of the message, or new message in any case. + HAL_UART_Receive_IT(&drv->urt, &drv->rx_byte, 1); +} + +void HAL_UART_ErrorCallback(UART_HandleTypeDef *urt) { + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized && urt == &drv->urt) { + nrf_abort_urt_comm(drv); + + HAL_UART_Receive_IT(&drv->urt, &drv->rx_byte, 1); + } +} + +void HAL_UART_TxCpltCallback(UART_HandleTypeDef *urt) { + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized && urt == &drv->urt) { + if (drv->tx_request.callback != NULL) { + drv->tx_request.callback(NRF_STATUS_OK, drv->tx_request.context); + } + drv->tx_request_id = -1; + memset(&drv->tx_request, 0, sizeof(nrf_tx_request_t)); + + bool msg = + tsqueue_dequeue(&drv->tx_queue, (uint8_t *)&drv->tx_request, + sizeof(nrf_tx_request_t), NULL, &drv->tx_request_id); + if (msg) { + HAL_UART_Transmit_DMA(&drv->urt, (uint8_t *)&drv->tx_request.packet, + drv->tx_request.packet.msg_len); + } + } +} + +void USART3_IRQHandler(void) { + IRQ_LOG_ENTER(); + + 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_LOG_EXIT(); +} + +void GPDMA1_Channel1_IRQHandler(void) { + IRQ_LOG_ENTER(); + + 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_LOG_EXIT(); +} + +/// SPI communication +/// ---------------------------------------------------------- + +void GPDMA1_Channel2_IRQHandler(void) { + IRQ_LOG_ENTER(); + + 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_LOG_EXIT(); +} + +void SPI1_IRQHandler(void) { + IRQ_LOG_ENTER(); + + mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT); + + nrf_driver_t *drv = &g_nrf_driver; + if (drv->initialized) { + HAL_SPI_IRQHandler(&drv->spi); + } + + mpu_restore(mpu_mode); + + IRQ_LOG_EXIT(); +} + +void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { + nrf_driver_t *drv = &g_nrf_driver; + + if (!drv->initialized) { + return; + } + + if (hspi != &drv->spi) { + return; + } + + spi_packet_t *packet = &drv->long_rx_buffer; + + uint8_t crc = crc8((uint8_t *)&drv->long_rx_buffer, sizeof(spi_packet_t) - 1, + 0x07, 0x00, false); + + if ((packet->service_id & 0xF0) != START_BYTE || packet->crc != crc) { + HAL_SPI_Abort(&drv->spi); + HAL_SPI_Receive_DMA(&drv->spi, (uint8_t *)&drv->long_rx_buffer, + sizeof(spi_packet_t)); + return; + } + + nrf_process_msg(drv, drv->long_rx_buffer.data, sizeof(packet->data), + packet->service_id & 0x0F); + + HAL_SPI_Receive_DMA(&drv->spi, (uint8_t *)&drv->long_rx_buffer, + sizeof(spi_packet_t)); +} + +/// GPIO communication +/// --------------------------------------------------------- + +bool nrf_reboot_to_bootloader(void) { + HAL_GPIO_WritePin(NRF_OUT_RESET_PORT, NRF_OUT_RESET_PIN, GPIO_PIN_RESET); + + HAL_GPIO_WritePin(NRF_OUT_STAY_IN_BLD_PORT, NRF_OUT_STAY_IN_BLD_PIN, + GPIO_PIN_SET); + + systick_delay_ms(50); + + HAL_GPIO_WritePin(NRF_OUT_RESET_PORT, NRF_OUT_RESET_PIN, GPIO_PIN_SET); + + systick_delay_ms(1000); + + return true; +} + +bool nrf_reboot(void) { + HAL_GPIO_WritePin(NRF_OUT_RESET_PORT, NRF_OUT_RESET_PIN, GPIO_PIN_SET); + HAL_GPIO_WritePin(NRF_OUT_STAY_IN_BLD_PORT, NRF_OUT_STAY_IN_BLD_PIN, + GPIO_PIN_RESET); + systick_delay_ms(50); + HAL_GPIO_WritePin(NRF_OUT_RESET_PORT, NRF_OUT_RESET_PIN, GPIO_PIN_RESET); + return true; +} + +void nrf_signal_running(void) { + HAL_GPIO_WritePin(NRF_OUT_FW_RUNNING_PORT, NRF_OUT_FW_RUNNING_PIN, + GPIO_PIN_SET); +} + +void nrf_signal_off(void) { + HAL_GPIO_WritePin(NRF_OUT_FW_RUNNING_PORT, NRF_OUT_FW_RUNNING_PIN, + GPIO_PIN_RESET); +} + +bool nrf_firmware_running(void) { + return HAL_GPIO_ReadPin(NRF_IN_FW_RUNNING_PORT, NRF_IN_FW_RUNNING_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/models/T3W1/boards/trezor_t3w1_revA.h b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h index 7ca1dc1547f..daf289636db 100644 --- a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h +++ b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h @@ -150,4 +150,20 @@ #define SBU_2_PORT GPIOC #define SBU_2_CLK_ENA __HAL_RCC_GPIOC_CLK_ENABLE +#define NRF_IN_GPIO0_PIN GPIO_PIN_7 +#define NRF_IN_GPIO0_PORT GPIOE +#define NRF_IN_GPIO0_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_IN_FW_RUNNING_PIN GPIO_PIN_13 +#define NRF_IN_FW_RUNNING_PORT GPIOE +#define NRF_IN_FW_RUNNING_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_OUT_RESET_PIN GPIO_PIN_0 +#define NRF_OUT_RESET_PORT GPIOG +#define NRF_OUT_RESET_CLK_ENA __HAL_RCC_GPIOG_CLK_ENABLE +#define NRF_OUT_STAY_IN_BLD_PIN GPIO_PIN_15 +#define NRF_OUT_STAY_IN_BLD_PORT GPIOE +#define NRF_OUT_STAY_IN_BLD_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_OUT_FW_RUNNING_PIN GPIO_PIN_11 +#define NRF_OUT_FW_RUNNING_PORT GPIOE +#define NRF_OUT_FW_RUNNING_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE + #endif // TREZOR_T3W1_REVA_H_ diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revA0.h b/core/embed/models/T3W1/boards/trezor_t3w1_revA0.h index d5f0a43de43..44bdbfb8805 100644 --- a/core/embed/models/T3W1/boards/trezor_t3w1_revA0.h +++ b/core/embed/models/T3W1/boards/trezor_t3w1_revA0.h @@ -150,4 +150,20 @@ #define SBU_2_PORT GPIOC #define SBU_2_CLK_ENA __HAL_RCC_GPIOC_CLK_ENABLE +#define NRF_IN_GPIO0_PIN GPIO_PIN_7 +#define NRF_IN_GPIO0_PORT GPIOE +#define NRF_IN_GPIO0_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_IN_FW_RUNNING_PIN GPIO_PIN_13 +#define NRF_IN_FW_RUNNING_PORT GPIOE +#define NRF_IN_FW_RUNNING_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_OUT_RESET_PIN GPIO_PIN_0 +#define NRF_OUT_RESET_PORT GPIOG +#define NRF_OUT_RESET_CLK_ENA __HAL_RCC_GPIOG_CLK_ENABLE +#define NRF_OUT_STAY_IN_BLD_PIN GPIO_PIN_15 +#define NRF_OUT_STAY_IN_BLD_PORT GPIOE +#define NRF_OUT_STAY_IN_BLD_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE +#define NRF_OUT_FW_RUNNING_PIN GPIO_PIN_11 +#define NRF_OUT_FW_RUNNING_PORT GPIOE +#define NRF_OUT_FW_RUNNING_CLK_ENA __HAL_RCC_GPIOE_CLK_ENABLE + #endif // TREZOR_T3W1_REVA_H_ diff --git a/core/embed/projects/kernel/main.c b/core/embed/projects/kernel/main.c index ae5584cfecb..e4ff5ed0e7d 100644 --- a/core/embed/projects/kernel/main.c +++ b/core/embed/projects/kernel/main.c @@ -41,6 +41,10 @@ #include #endif +#ifdef USE_BLE +#include +#endif + #ifdef USE_CONSUMPTION_MASK #include #endif @@ -158,6 +162,10 @@ void drivers_init() { haptic_init(); #endif +#ifdef USE_BLE + ble_init(); +#endif + #ifdef USE_OPTIGA optiga_init_and_configure(); #endif diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 47eeb50aa99..984e7e8bd5c 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -40,6 +40,7 @@ rgb_led = [] backlight = [] usb = [] optiga = [] +ble = [] translations = ["crypto"] test = [ "backlight", diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index bd95b4e5c8d..9c20f380d04 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -35,6 +35,7 @@ const DEFAULT_BINDGEN_MACROS_COMMON: &[&str] = &[ "-I../../vendor/micropython/lib/uzlib", "-I../rtl/inc", "-I../gfx/inc", + "-I../io/ble/inc", "-I../io/button/inc", "-I../io/display/inc", "-I../io/haptic/inc", @@ -51,6 +52,7 @@ const DEFAULT_BINDGEN_MACROS_COMMON: &[&str] = &[ "-DUSE_TOUCH", "-DUSE_HAPTIC", "-DUSE_RGB_LED", + "-DUSE_BLE", ]; #[cfg(feature = "layout_bolt")] @@ -412,6 +414,11 @@ fn generate_trezorhal_bindings() { //usb .allowlist_type("usb_event_t") .allowlist_function("usb_get_state") + // ble + .allowlist_function("ble_get_state") + .allowlist_function("ble_issue_command") + .allowlist_type("ble_command_t") + .allowlist_type("ble_state_t") // touch .allowlist_function("touch_get_event") // button diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index a6afb178f02..6b0e7c3e49e 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -120,6 +120,7 @@ static void _librust_qstrs(void) { MP_QSTR_bitcoin__unverified_external_inputs; MP_QSTR_bitcoin__valid_signature; MP_QSTR_bitcoin__voting_rights; + MP_QSTR_ble_event; MP_QSTR_bootscreen; MP_QSTR_br_code; MP_QSTR_br_name; diff --git a/core/embed/rust/src/trezorhal/ble.rs b/core/embed/rust/src/trezorhal/ble.rs new file mode 100644 index 00000000000..3bef99eb252 --- /dev/null +++ b/core/embed/rust/src/trezorhal/ble.rs @@ -0,0 +1,34 @@ +use super::ffi; + +pub fn connected() -> bool { + unsafe { + let mut state = ffi::ble_state_t { + connected: false, + peer_count: 0, + connectable: false, + pairing: false, + pairing_requested: false, + }; + ffi::ble_get_state(&mut state as _); + + state.connected + } +} + +pub fn pairing_mode() { + unsafe { + ffi::ble_issue_command(ffi::ble_command_t_BLE_PAIRING_MODE); + } +} + +pub fn allow_pairing() { + unsafe { + ffi::ble_issue_command(ffi::ble_command_t_BLE_ALLOW_PAIRING); + } +} + +pub fn reject_pairing() { + unsafe { + ffi::ble_issue_command(ffi::ble_command_t_BLE_REJECT_PAIRING); + } +} diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index 228b276e138..51632ea62eb 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -1,4 +1,6 @@ pub mod bip39; +#[cfg(feature = "ble")] +pub mod ble; #[macro_use] #[allow(unused_macros)] pub mod fatal_error; diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index e23b7223eb7..9b24c44fd19 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -11,6 +11,8 @@ use crate::{ }, }; +#[cfg(feature = "ble")] +use crate::ui::event::BLEEvent; #[cfg(feature = "button")] use crate::ui::event::ButtonEvent; use crate::ui::event::USBEvent; @@ -319,6 +321,8 @@ pub enum Event { Button(ButtonEvent), #[cfg(feature = "touch")] Touch(TouchEvent), + #[cfg(feature = "ble")] + BLE(BLEEvent), USB(USBEvent), /// Previously requested timer was triggered. This invalidates the timer /// token (another timer has to be requested). diff --git a/core/embed/rust/src/ui/event/ble.rs b/core/embed/rust/src/ui/event/ble.rs new file mode 100644 index 00000000000..fd0f8d0172e --- /dev/null +++ b/core/embed/rust/src/ui/event/ble.rs @@ -0,0 +1,23 @@ +use crate::error::Error; + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] +pub enum BLEEvent { + Connected, + Disconnected, + PairingRequest(&'static [u8]), + PairingCanceled, +} + +impl BLEEvent { + pub fn new(event: u32, data: &'static [u8]) -> Result { + let result = match event { + 1 => Self::Connected, + 2 => Self::Disconnected, + 3 => Self::PairingRequest(data), + 4 => Self::PairingCanceled, + _ => return Err(Error::OutOfRange), + }; + Ok(result) + } +} diff --git a/core/embed/rust/src/ui/event/mod.rs b/core/embed/rust/src/ui/event/mod.rs index 1b7a3aa2678..661efcadd04 100644 --- a/core/embed/rust/src/ui/event/mod.rs +++ b/core/embed/rust/src/ui/event/mod.rs @@ -5,6 +5,11 @@ pub mod touch; pub mod usb; +#[cfg(feature = "ble")] +mod ble; + +#[cfg(feature = "ble")] +pub use ble::BLEEvent; #[cfg(feature = "button")] pub use button::{ButtonEvent, PhysicalButton}; #[cfg(feature = "touch")] diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 93a63f0a6af..4ec68214a4a 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -4,16 +4,20 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut}, }; + +#[cfg(feature = "touch")] +use crate::ui::{event::TouchEvent, geometry::Direction}; #[cfg(feature = "touch")] use num_traits::{FromPrimitive, ToPrimitive}; +#[cfg(feature = "ble")] +use crate::micropython::buffer::get_buffer; +#[cfg(feature = "ble")] +use crate::ui::event::BLEEvent; + #[cfg(feature = "button")] use crate::ui::event::ButtonEvent; -use crate::ui::{display::Color, shape::render_on_display}; - -#[cfg(feature = "touch")] -use crate::ui::{event::TouchEvent, geometry::Direction}; use crate::{ error::Error, maybe_trace::MaybeTrace, @@ -35,8 +39,9 @@ use crate::{ base::{AttachType, TimerToken}, Component, Event, EventCtx, Never, }, - display, + display::{self, Color}, event::USBEvent, + shape::render_on_display, CommonUI, ModelUI, }, }; @@ -388,6 +393,7 @@ impl LayoutObj { Qstr::MP_QSTR_button_event => obj_fn_var!(3, 3, ui_layout_button_event).as_obj(), Qstr::MP_QSTR_progress_event => obj_fn_var!(3, 3, ui_layout_progress_event).as_obj(), Qstr::MP_QSTR_usb_event => obj_fn_var!(2, 2, ui_layout_usb_event).as_obj(), + Qstr::MP_QSTR_ble_event=> obj_fn_var!(3, 3, ui_layout_ble_event).as_obj(), Qstr::MP_QSTR_timer => obj_fn_2!(ui_layout_timer).as_obj(), Qstr::MP_QSTR_paint => obj_fn_1!(ui_layout_paint).as_obj(), Qstr::MP_QSTR_request_complete_repaint => obj_fn_1!(ui_layout_request_complete_repaint).as_obj(), @@ -522,6 +528,30 @@ extern "C" fn ui_layout_button_event(_n_args: usize, _args: *const Obj) -> Obj { Obj::const_none() } +#[cfg(feature = "ble")] +extern "C" fn ui_layout_ble_event(n_args: usize, args: *const Obj) -> Obj { + let block = |args: &[Obj], _kwargs: &Map| { + if args.len() != 3 { + return Err(Error::TypeError); + } + let this: Gc = args[0].try_into()?; + + let data = unsafe { get_buffer(args[2]) }; + + let data = unwrap!(data); + + let event = BLEEvent::new(args[1].try_into()?, data)?; + let msg = this.inner_mut().obj_event(Event::BLE(event))?; + Ok(msg) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) } +} + +#[cfg(not(feature = "ble"))] +extern "C" fn ui_layout_ble_event(_n_args: usize, _args: *const Obj) -> Obj { + Obj::const_none() +} + extern "C" fn ui_layout_progress_event(n_args: usize, args: *const Obj) -> Obj { let block = |args: &[Obj], _kwargs: &Map| { if args.len() != 3 { diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h index 3267801220a..6e00aac7602 100644 --- a/core/embed/rust/trezorhal.h +++ b/core/embed/rust/trezorhal.h @@ -13,6 +13,10 @@ #include #include "storage.h" +#ifdef USE_BLE +#include +#endif + #ifdef USE_BUTTON #include #endif diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c index 52637cf2927..085bacb72fd 100644 --- a/core/embed/sys/syscall/stm32/syscall_dispatch.c +++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c @@ -39,6 +39,10 @@ #include #include +#ifdef USE_BLE +#include +#endif + #ifdef USE_BUTTON #include #endif @@ -677,6 +681,47 @@ __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_GET_EVENT: { + ble_event_t *event = (ble_event_t *)args[0]; + args[0] = ble_get_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_CAN_READ: { + args[0] = ble_can_read(); + } 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 + #ifdef USE_POWERCTL case SYSCALL_POWERCTL_SUSPEND: { powerctl_suspend(); diff --git a/core/embed/sys/syscall/stm32/syscall_numbers.h b/core/embed/sys/syscall/stm32/syscall_numbers.h index 8f6bed23757..3c283365b7a 100644 --- a/core/embed/sys/syscall/stm32/syscall_numbers.h +++ b/core/embed/sys/syscall/stm32/syscall_numbers.h @@ -139,6 +139,15 @@ typedef enum { SYSCALL_FIRMWARE_GET_VENDOR, SYSCALL_FIRMWARE_CALC_HASH, + SYSCALL_BLE_START, + SYSCALL_BLE_ISSUE_COMMAND, + SYSCALL_BLE_GET_EVENT, + SYSCALL_BLE_GET_STATE, + SYSCALL_BLE_CAN_WRITE, + SYSCALL_BLE_WRITE, + SYSCALL_BLE_CAN_READ, + SYSCALL_BLE_READ, + SYSCALL_POWERCTL_SUSPEND, } syscall_number_t; diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c index af9d059a5ac..2ffa6abf727 100644 --- a/core/embed/sys/syscall/stm32/syscall_stubs.c +++ b/core/embed/sys/syscall/stm32/syscall_stubs.c @@ -645,6 +645,42 @@ secbool firmware_calc_hash(const uint8_t *challenge, size_t challenge_len, SYSCALL_FIRMWARE_CALC_HASH); } +#ifdef USE_BLE + +// ============================================================================= +// ble.h +// ============================================================================= + +#include + +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_get_event(ble_event_t *event) { + return (bool)syscall_invoke1((uint32_t)event, SYSCALL_BLE_GET_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); +} + +bool ble_can_read(void) { return syscall_invoke0(SYSCALL_BLE_CAN_READ); } + +uint32_t ble_read(uint8_t *data, uint16_t len) { + return (uint32_t)syscall_invoke2((uint32_t)data, len, SYSCALL_BLE_READ); +} + +#endif + // ============================================================================= // powerctl.h // ============================================================================= diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c index 588699c2a19..fd82ca4b128 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.c +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c @@ -715,4 +715,58 @@ secbool firmware_get_vendor__verified(char *buff, size_t buff_size) { return secfalse; } +// --------------------------------------------------------------------- + +#ifdef USE_BLE +void ble_get_state__verified(ble_state_t *state) { + if (!probe_write_access(state, sizeof(*state))) { + goto access_violation; + } + + ble_state_t state_copy = {0}; + ble_get_state(&state_copy); + *state = state_copy; + return; + +access_violation: + apptask_access_violation(); +} + +bool ble_get_event__verified(ble_event_t *event) { + if (!probe_write_access(event, sizeof(*event))) { + goto access_violation; + } + + return ble_get_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 + #endif // SYSCALL_DISPATCH diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h index 7cdf477153c..bd63f6200ba 100644 --- a/core/embed/sys/syscall/stm32/syscall_verifiers.h +++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h @@ -186,6 +186,21 @@ secbool firmware_calc_hash__verified(const uint8_t *challenge, secbool firmware_get_vendor__verified(char *buff, size_t buff_size); +// --------------------------------------------------------------------- +#ifdef USE_BLE + +#include + +void ble_get_state__verified(ble_state_t *state); + +bool ble_get_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 + #endif // SYSCALL_DISPATCH #endif // TREZORHAL_SYSCALL_VERIFIERS_H diff --git a/core/embed/upymod/modtrezorio/modtrezorio-ble.h b/core/embed/upymod/modtrezorio/modtrezorio-ble.h new file mode 100644 index 00000000000..b348f9973fe --- /dev/null +++ b/core/embed/upymod/modtrezorio/modtrezorio-ble.h @@ -0,0 +1,208 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// #include "ble/dfu.h" +// #include "ble/messages.h" + +/// package: trezorio.ble + +// /// 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); +// +// ble_set_dfu_mode(true); +// +// dfu_result_t result = dfu_update_init(buffer.buf, buffer.len, binsize_int); +// if (result == DFU_NEXT_CHUNK) { +// return mp_obj_new_int(0); +// } else if (result == DFU_SUCCESS) { +// ble_set_dfu_mode(false); +// return mp_obj_new_int(1); +// } else { +// ble_set_dfu_mode(false); +// mp_raise_msg(&mp_type_RuntimeError, "Upload failed."); +// } +// } +// STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_update_init_obj, +// mod_trezorio_BLE_update_init); +// +// /// def update_chunk(chunk: bytes) -> 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) { +// ble_set_dfu_mode(false); +// return mp_obj_new_int(1); +// } else { +// ble_set_dfu_mode(false); +// mp_raise_msg(&mp_type_RuntimeError, "Upload failed."); +// } +// } +// STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_update_chunk_obj, +// mod_trezorio_BLE_update_chunk); + +/// def write(msg: bytes) -> int: +/// """ +/// Sends message over BLE +/// """ +STATIC mp_obj_t mod_trezorio_BLE_write(mp_obj_t msg) { + mp_buffer_info_t buf = {0}; + mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ); + bool success = ble_write(buf.buf, buf.len); + if (success) { + return MP_OBJ_NEW_SMALL_INT(buf.len); + } else { + return MP_OBJ_NEW_SMALL_INT(-1); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_write_obj, + mod_trezorio_BLE_write); + +/// def read(buf: bytes, offset: int = 0) -> int +/// """ +/// Reads message using BLE (device). +/// """ +STATIC mp_obj_t mod_trezorio_BLE_read(size_t n_args, const mp_obj_t *args) { + mp_buffer_info_t buf = {0}; + mp_get_buffer_raise(args[0], &buf, MP_BUFFER_WRITE); + + int offset = 0; + if (n_args >= 1) { + offset = mp_obj_get_int(args[1]); + } + + if (offset < 0) { + mp_raise_ValueError("Negative offset not allowed"); + } + + uint32_t buffer_space = buf.len - offset; + + if (buffer_space < BLE_RX_PACKET_SIZE) { + mp_raise_ValueError("Buffer too small"); + } + + uint32_t r = ble_read(&((uint8_t *)buf.buf)[offset], BLE_RX_PACKET_SIZE); + + if (r != BLE_RX_PACKET_SIZE) { + mp_raise_msg(&mp_type_RuntimeError, "Unexpected read length"); + } + + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorio_BLE_read_obj, 1, 2, + mod_trezorio_BLE_read); + +/// def erase_bonds() -> None: +/// """ +/// Erases all BLE bonds +/// """ +STATIC mp_obj_t mod_trezorio_BLE_erase_bonds(void) { + ble_issue_command(BLE_ERASE_BONDS); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_erase_bonds_obj, + mod_trezorio_BLE_erase_bonds); + +/// def start_comm() -> None: +/// """ +/// Start communication with BLE chip +/// """ +STATIC mp_obj_t mod_trezorio_BLE_start_comm(void) { + ble_start(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_start_comm_obj, + mod_trezorio_BLE_start_comm); + +/// def start_advertising(whitelist: bool) -> None: +/// """ +/// Start advertising +/// """ +STATIC mp_obj_t mod_trezorio_BLE_start_advertising(mp_obj_t whitelist) { + bool whitelist_bool = mp_obj_is_true(whitelist); + + ble_issue_command(whitelist_bool ? BLE_SWITCH_ON : BLE_PAIRING_MODE); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_start_advertising_obj, + mod_trezorio_BLE_start_advertising); + +/// def stop_advertising(whitelist: bool) -> None: +/// """ +/// Stop advertising +/// """ +STATIC mp_obj_t mod_trezorio_BLE_stop_advertising(void) { + ble_issue_command(BLE_SWITCH_OFF); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_stop_advertising_obj, + mod_trezorio_BLE_stop_advertising); + +/// def disconnect() -> None: +/// """ +/// Disconnect BLE +/// """ +STATIC mp_obj_t mod_trezorio_BLE_disconnect(void) { + ble_issue_command(BLE_DISCONNECT); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_BLE_disconnect_obj, + mod_trezorio_BLE_disconnect); + +STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ble)}, + // {MP_ROM_QSTR(MP_QSTR_update_init), + // MP_ROM_PTR(&mod_trezorio_BLE_update_init_obj)}, + // {MP_ROM_QSTR(MP_QSTR_update_chunk), + // MP_ROM_PTR(&mod_trezorio_BLE_update_chunk_obj)}, + {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_BLE_write_obj)}, + {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mod_trezorio_BLE_read_obj)}, + {MP_ROM_QSTR(MP_QSTR_erase_bonds), + MP_ROM_PTR(&mod_trezorio_BLE_erase_bonds_obj)}, + {MP_ROM_QSTR(MP_QSTR_start_comm), + MP_ROM_PTR(&mod_trezorio_BLE_start_comm_obj)}, + {MP_ROM_QSTR(MP_QSTR_start_advertising), + MP_ROM_PTR(&mod_trezorio_BLE_start_advertising_obj)}, + {MP_ROM_QSTR(MP_QSTR_stop_advertising), + MP_ROM_PTR(&mod_trezorio_BLE_stop_advertising_obj)}, + {MP_ROM_QSTR(MP_QSTR_disconnect), + MP_ROM_PTR(&mod_trezorio_BLE_disconnect_obj)}, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorio_BLE_globals, + mod_trezorio_BLE_globals_table); + +STATIC const mp_obj_module_t mod_trezorio_BLE_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mod_trezorio_BLE_globals}; diff --git a/core/embed/upymod/modtrezorio/modtrezorio-poll.h b/core/embed/upymod/modtrezorio/modtrezorio-poll.h index 6467cd43a49..33e6342b11f 100644 --- a/core/embed/upymod/modtrezorio/modtrezorio-poll.h +++ b/core/embed/upymod/modtrezorio/modtrezorio-poll.h @@ -23,6 +23,10 @@ #include #include +#ifdef USE_BLE +#include +#endif + #ifdef USE_BUTTON #include #endif @@ -33,9 +37,12 @@ #include "SDL.h" #endif +#define BLE_EVENT_IFACE (252) #define USB_EVENT_IFACE (253) #define BUTTON_IFACE (254) #define TOUCH_IFACE (255) +#define USB_RW_IFACE_MAX (15) // 0-15 reserved for USB +#define BLE_IFACE (16) #define POLL_READ (0x0000) #define POLL_WRITE (0x0100) @@ -164,23 +171,53 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, } } #endif - else if (mode == POLL_READ) { - if ((sectrue == usb_hid_can_read(iface)) || - (sectrue == usb_webusb_can_read(iface))) { - ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = MP_OBJ_NEW_SMALL_INT(USB_PACKET_LEN); - return mp_const_true; + else if (iface <= USB_RW_IFACE_MAX) { + if (mode == POLL_READ) { + if ((sectrue == usb_hid_can_read(iface)) || + (sectrue == usb_webusb_can_read(iface))) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = MP_OBJ_NEW_SMALL_INT(USB_PACKET_LEN); + return mp_const_true; + } + } else if (mode == POLL_WRITE) { + if ((sectrue == usb_hid_can_write(iface)) || + (sectrue == usb_webusb_can_write(iface))) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_const_none; + return mp_const_true; + } + } + } +#ifdef USE_BLE + else if (iface == BLE_IFACE) { + if (mode == POLL_READ) { + int len = ble_can_read(); + if (len > 0) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = MP_OBJ_NEW_SMALL_INT(BLE_RX_PACKET_SIZE); + return mp_const_true; + } + } else if (mode == POLL_WRITE) { + if (ble_can_write()) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_const_none; + return mp_const_true; + } } - } else if (mode == POLL_WRITE) { - if ((sectrue == usb_hid_can_write(iface)) || - (sectrue == usb_webusb_can_write(iface))) { + } else if (iface == BLE_EVENT_IFACE) { + ble_event_t event = {0}; + bool read = ble_get_event(&event); + if (read) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(event.type); + tuple->items[1] = mp_obj_new_bytes(event.data, event.data_len); ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = mp_const_none; + ret->items[1] = MP_OBJ_FROM_PTR(tuple); return mp_const_true; } } +#endif } - if (mp_hal_ticks_ms() >= deadline) { break; } else { diff --git a/core/embed/upymod/modtrezorio/modtrezorio.c b/core/embed/upymod/modtrezorio/modtrezorio.c index d03168d8f3e..a8ddcfbf344 100644 --- a/core/embed/upymod/modtrezorio/modtrezorio.c +++ b/core/embed/upymod/modtrezorio/modtrezorio.c @@ -51,6 +51,9 @@ uint32_t last_touch_sample_time = 0; #include "modtrezorio-webusb.h" #include "modtrezorio-usb.h" // clang-format on +#ifdef USE_BLE +#include "modtrezorio-ble.h" +#endif #ifdef USE_SD_CARD #include "modtrezorio-fatfs.h" #include "modtrezorio-sdcard.h" @@ -60,11 +63,14 @@ uint32_t last_touch_sample_time = 0; #endif /// package: trezorio.__init__ -/// from . import fatfs, haptic, sdcard +/// from . import fatfs, haptic, sdcard, ble /// POLL_READ: int # wait until interface is readable and return read data /// POLL_WRITE: int # wait until interface is writable /// +/// BLE: int # interface id of the BLE events +/// BLE_EVENT: int # interface id for BLE events +/// /// TOUCH: int # interface id of the touch events /// TOUCH_START: int # event id of touch start event /// TOUCH_MOVE: int # event id of touch move event @@ -78,7 +84,7 @@ uint32_t last_touch_sample_time = 0; /// USB_EVENT: int # interface id for USB events -/// WireInterface = Union[HID, WebUSB] +/// WireInterface = Union[HID, WebUSB, BleInterface] STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorio)}, @@ -92,6 +98,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_haptic), MP_ROM_PTR(&mod_trezorio_haptic_module)}, #endif +#ifdef USE_BLE + {MP_ROM_QSTR(MP_QSTR_ble), MP_ROM_PTR(&mod_trezorio_BLE_module)}, + {MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_INT(BLE_IFACE)}, + {MP_ROM_QSTR(MP_QSTR_BLE_EVENT), MP_ROM_INT(BLE_EVENT_IFACE)}, +#endif #ifdef USE_TOUCH {MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)}, {MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_ROM_INT((TOUCH_START >> 24) & 0xFFU)}, diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c index 38ac43fb2f9..8ba9c533ba8 100644 --- a/core/embed/upymod/modtrezorutils/modtrezorutils.c +++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c @@ -381,6 +381,8 @@ STATIC mp_obj_tuple_t mod_trezorutils_version_obj = { /// """Git commit hash of the firmware.""" /// VERSION: VersionTuple /// """Firmware version as a tuple (major, minor, patch, build).""" +/// USE_BLE: bool +/// """Whether the hardware supports BLE.""" /// USE_SD_CARD: bool /// """Whether the hardware supports SD card.""" /// USE_BACKLIGHT: bool @@ -447,6 +449,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { #else {MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_false}, #endif +#ifdef USE_BLE + {MP_ROM_QSTR(MP_QSTR_USE_BLE), mp_const_true}, +#else + {MP_ROM_QSTR(MP_QSTR_USE_BLE), mp_const_false}, +#endif #ifdef USE_BACKLIGHT {MP_ROM_QSTR(MP_QSTR_USE_BACKLIGHT), mp_const_true}, #else diff --git a/core/embed/util/tsqueue/inc/util/tsqueue.h b/core/embed/util/tsqueue/inc/util/tsqueue.h new file mode 100644 index 00000000000..653ce7d91d9 --- /dev/null +++ b/core/embed/util/tsqueue/inc/util/tsqueue.h @@ -0,0 +1,67 @@ +/* + * 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 struct { + uint8_t *buffer; // Pointer to the data buffer + uint16_t len; // Length of data in the buffer + int32_t id; // ID of the entry + bool used; // Used flag + bool aborted; // Aborted flag +} tsqueue_entry_t; + +typedef struct { + tsqueue_entry_t *entries; // Array of queue entries + uint16_t rix; // Read index + uint16_t wix; // Write index + uint16_t qlen; // Queue length + uint16_t size; // Size of each buffer + int32_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, uint16_t qlen); + +void tsqueue_reset(tsqueue_t *queue); + +// Insert data into the queue +bool tsqueue_enqueue(tsqueue_t *queue, const uint8_t *data, uint16_t len, + int32_t *id); + +// Read data from the queue +bool tsqueue_dequeue(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len, int32_t *id); + +// Checks if the queue is full +bool tsqueue_full(tsqueue_t *queue); + +// Checks if the queue is empty +bool tsqueue_empty(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, int32_t id, uint8_t *data, + uint16_t max_len, uint16_t *len); + +#endif diff --git a/core/embed/util/tsqueue/tsqueue.c b/core/embed/util/tsqueue/tsqueue.c new file mode 100644 index 00000000000..d0fb408c28a --- /dev/null +++ b/core/embed/util/tsqueue/tsqueue.c @@ -0,0 +1,187 @@ +/* + * 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 +#include + +// Initialize the queue +void tsqueue_init(tsqueue_t *queue, tsqueue_entry_t *entries, + uint8_t *buffer_mem, uint16_t size, uint16_t qlen) { + irq_key_t key = irq_lock(); + queue->entries = entries; + queue->qlen = qlen; + queue->size = size; + + for (int i = 0; i < qlen; i++) { + queue->entries[i].buffer = buffer_mem + i * size; + } + + tsqueue_reset(queue); + + irq_unlock(key); +} + +static void tsqueue_entry_reset(tsqueue_entry_t *entry, uint32_t data_size) { + entry->len = 0; + entry->used = 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->wix = 0; + queue->next_id = 1; + + for (int i = 0; i < queue->qlen; i++) { + tsqueue_entry_reset(&queue->entries[i], queue->size); + } + + irq_unlock(key); +} + +static int32_t get_next_id(tsqueue_t *queue) { + int val = 1; + if (queue->next_id < INT32_MAX) { + val = queue->next_id; + queue->next_id++; + } else { + queue->next_id = 2; + } + return val; +} + +bool tsqueue_enqueue(tsqueue_t *queue, const uint8_t *data, uint16_t len, + int32_t *id) { + irq_key_t key = irq_lock(); + + if (queue->entries[queue->wix].used) { + // Full queue + irq_unlock(key); + return false; + } + + if (len > queue->size) { + irq_unlock(key); + return false; + } + + memcpy(queue->entries[queue->wix].buffer, data, len); + queue->entries[queue->wix].id = get_next_id(queue); + queue->entries[queue->wix].len = len; + queue->entries[queue->wix].used = true; + + if (id != NULL) { + *id = queue->entries[queue->wix].id; + } + queue->wix = (queue->wix + 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; + } +} + +bool tsqueue_dequeue(tsqueue_t *queue, uint8_t *data, uint16_t max_len, + uint16_t *len, int32_t *id) { + irq_key_t key = irq_lock(); + + tsqueue_discard_aborted(queue); + + if (!queue->entries[queue->rix].used) { + irq_unlock(key); + return false; + } + + if (len != NULL) { + *len = queue->entries[queue->rix].len; + } + + if (id != NULL) { + *id = queue->entries[queue->rix].id; + } + + 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; + + 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].used; + irq_unlock(key); + return full; +} + +bool tsqueue_empty(tsqueue_t *queue) { + irq_key_t key = irq_lock(); + + tsqueue_discard_aborted(queue); + + bool empty = !queue->entries[queue->rix].used; + + irq_unlock(key); + + return empty; +} + +bool tsqueue_abort(tsqueue_t *queue, int32_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].used && 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/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi index 538a7d9105f..77f01918b5b 100644 --- a/core/mocks/generated/trezorio/__init__.pyi +++ b/core/mocks/generated/trezorio/__init__.pyi @@ -162,10 +162,13 @@ class WebUSB: """ PACKET_LEN: ClassVar[int] """Length of one USB packet.""" -from . import fatfs, haptic, sdcard +from . import fatfs, haptic, sdcard, ble POLL_READ: int # wait until interface is readable and return read data POLL_WRITE: int # wait until interface is writable +BLE: int # interface id of the BLE events +BLE_EVENT: int # interface id for BLE events + TOUCH: int # interface id of the touch events TOUCH_START: int # event id of touch start event TOUCH_MOVE: int # event id of touch move event @@ -176,4 +179,4 @@ BUTTON_RELEASED: int # button up event BUTTON_LEFT: int # button number of left button BUTTON_RIGHT: int # button number of right button USB_EVENT: int # interface id for USB events -WireInterface = Union[HID, WebUSB] +WireInterface = Union[HID, WebUSB, BleInterface] diff --git a/core/mocks/generated/trezorio/ble.pyi b/core/mocks/generated/trezorio/ble.pyi new file mode 100644 index 00000000000..5a5288b6850 --- /dev/null +++ b/core/mocks/generated/trezorio/ble.pyi @@ -0,0 +1,50 @@ +from typing import * + + +# upymod/modtrezorio/modtrezorio-ble.h +def write(msg: bytes) -> int: + """ + Sends message over BLE + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def read(buf: bytes, offset: int = 0) -> int + """ + Reads message using BLE (device). + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def erase_bonds() -> None: + """ + Erases all BLE bonds + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def start_comm() -> None: + """ + Start communication with BLE chip + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def start_advertising(whitelist: bool) -> None: + """ + Start advertising + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def stop_advertising(whitelist: bool) -> None: + """ + Stop advertising + """ + + +# upymod/modtrezorio/modtrezorio-ble.h +def disconnect() -> None: + """ + Disconnect BLE + """ diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi index e6aaad410e3..9a9a653626f 100644 --- a/core/mocks/generated/trezorutils.pyi +++ b/core/mocks/generated/trezorutils.pyi @@ -122,6 +122,8 @@ SCM_REVISION: bytes """Git commit hash of the firmware.""" VERSION: VersionTuple """Firmware version as a tuple (major, minor, patch, build).""" +USE_BLE: bool +"""Whether the hardware supports BLE.""" USE_SD_CARD: bool """Whether the hardware supports SD card.""" USE_BACKLIGHT: bool diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py index 43348ec0b70..7cdb2ce7e16 100644 --- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py +++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py @@ -77,21 +77,17 @@ def configure( features_available.append("haptic") defines += ["USE_HAPTIC=1"] - # if "ble" in features_wanted: - # sources += ["embed/trezorhal/stm32f4/ble/ble_hal.c"] - # sources += ["embed/trezorhal/stm32f4/ble/dfu.c"] - # sources += ["embed/trezorhal/stm32f4/ble/fwu.c"] - # sources += ["embed/trezorhal/stm32f4/ble/ble.c"] - # sources += ["embed/trezorhal/stm32f4/ble/messages.c"] - # sources += [ - # "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c" - # ] - # features_available.append("ble") - # defines += [("USE_BLE", "1")] - if "ble" in features_wanted: + sources += ["embed/io/ble/stm32/ble.c"] + paths += ["embed/io/ble/inc"] + features_available.append("ble") + defines += [("USE_BLE", "1")] + sources += ["embed/io/nrf/stm32u5/nrf.c"] + sources += ["embed/io/nrf/crc8.c"] + paths += ["embed/io/nrf/inc"] sources += [ - "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c" + "vendor/stm32u5xx_hal_driver/Src/stm32u5xx_hal_uart.c", + "vendor/stm32u5xx_hal_driver/Src/stm32u5xx_hal_uart_ex.c", ] if "optiga" in features_wanted: diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA0.py b/core/site_scons/models/T3W1/trezor_t3w1_revA0.py index 563e0f13285..77c8e1fce9e 100644 --- a/core/site_scons/models/T3W1/trezor_t3w1_revA0.py +++ b/core/site_scons/models/T3W1/trezor_t3w1_revA0.py @@ -76,21 +76,17 @@ def configure( features_available.append("haptic") defines += ["USE_HAPTIC=1"] - # if "ble" in features_wanted: - # sources += ["embed/trezorhal/stm32f4/ble/ble_hal.c"] - # sources += ["embed/trezorhal/stm32f4/ble/dfu.c"] - # sources += ["embed/trezorhal/stm32f4/ble/fwu.c"] - # sources += ["embed/trezorhal/stm32f4/ble/ble.c"] - # sources += ["embed/trezorhal/stm32f4/ble/messages.c"] - # sources += [ - # "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c" - # ] - # features_available.append("ble") - # defines += [("USE_BLE", "1")] - if "ble" in features_wanted: + sources += ["embed/io/ble/stm32/ble.c"] + paths += ["embed/io/ble/inc"] + features_available.append("ble") + defines += [("USE_BLE", "1")] + sources += ["embed/io/nrf/stm32u5/nrf.c"] + sources += ["embed/io/nrf/crc8.c"] + paths += ["embed/io/nrf/inc"] sources += [ - "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c" + "vendor/stm32u5xx_hal_driver/Src/stm32u5xx_hal_uart.c", + "vendor/stm32u5xx_hal_driver/Src/stm32u5xx_hal_uart_ex.c", ] if "optiga" in features_wanted: diff --git a/core/site_scons/models/stm32f4_common.py b/core/site_scons/models/stm32f4_common.py index bba7c41d422..ba37025165e 100644 --- a/core/site_scons/models/stm32f4_common.py +++ b/core/site_scons/models/stm32f4_common.py @@ -29,6 +29,7 @@ def stm32f4_common_files(env, defines, sources, paths): "embed/util/flash/inc", "embed/util/fwutils/inc", "embed/util/option_bytes/inc", + "embed/util/tsqueue/inc", "embed/util/unit_properties/inc", "vendor/micropython/lib/cmsis/inc", "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Inc", @@ -87,6 +88,7 @@ def stm32f4_common_files(env, defines, sources, paths): "embed/util/flash/stm32f4/flash_otp.c", "embed/util/fwutils/fwutils.c", "embed/util/option_bytes/stm32f4/option_bytes.c", + "embed/util/tsqueue/tsqueue.c", "embed/util/unit_properties/stm32/unit_properties.c", ] diff --git a/core/site_scons/models/stm32u5_common.py b/core/site_scons/models/stm32u5_common.py index 0d3ed6437a3..b7faa19e9a1 100644 --- a/core/site_scons/models/stm32u5_common.py +++ b/core/site_scons/models/stm32u5_common.py @@ -32,6 +32,7 @@ def stm32u5_common_files(env, defines, sources, paths): "embed/util/flash/inc", "embed/util/fwutils/inc", "embed/util/option_bytes/inc", + "embed/util/tsqueue/inc", "embed/util/unit_properties/inc", "vendor/stm32u5xx_hal_driver/Inc", "vendor/cmsis_device_u5/Include", @@ -106,6 +107,7 @@ def stm32u5_common_files(env, defines, sources, paths): "embed/util/flash/stm32u5/flash_otp.c", "embed/util/fwutils/fwutils.c", "embed/util/option_bytes/stm32u5/option_bytes.c", + "embed/util/tsqueue/tsqueue.c", "embed/util/unit_properties/stm32/unit_properties.c", ] diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index 694bae2cb9d..3fcb47af353 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -11,6 +11,7 @@ SCM_REVISION, UI_LAYOUT, USE_BACKLIGHT, + USE_BLE, USE_BUTTON, USE_HAPTIC, USE_OPTIGA,