mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-24 15:28:10 +00:00
feat(core): introduce BLE driver
[no changelog]
This commit is contained in:
parent
79cf4959d3
commit
ae60228ed9
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
121
core/embed/trezorhal/ble.h
Normal file
121
core/embed/trezorhal/ble.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <trezor_types.h>
|
||||
|
||||
#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
|
486
core/embed/trezorhal/stm32f4/ble/ble.c
Normal file
486
core/embed/trezorhal/stm32f4/ble/ble.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef KERNEL_MODE
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#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
|
58
core/embed/trezorhal/stm32f4/ble/ble_comm_defs.h
Normal file
58
core/embed/trezorhal/stm32f4/ble/ble_comm_defs.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_BLE_COMM_DEFS_H_
|
||||
#define TREZORHAL_BLE_COMM_DEFS_H_
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
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
|
53
core/embed/trezorhal/stm32f4/nrf/crc8.c
Normal file
53
core/embed/trezorhal/stm32f4/nrf/crc8.c
Normal file
@ -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;
|
||||
}
|
28
core/embed/trezorhal/stm32f4/nrf/crc8.h
Normal file
28
core/embed/trezorhal/stm32f4/nrf/crc8.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_NRF_CRC8_H
|
||||
#define TREZORHAL_NRF_CRC8_H
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
uint8_t crc8(const uint8_t *src, size_t len, uint8_t polynomial,
|
||||
uint8_t initial_value, bool reversed);
|
||||
|
||||
#endif
|
138
core/embed/trezorhal/stm32f4/nrf/dfu.c
Normal file
138
core/embed/trezorhal/stm32f4/nrf/dfu.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
19
core/embed/trezorhal/stm32f4/nrf/dfu.h
Normal file
19
core/embed/trezorhal/stm32f4/nrf/dfu.h
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
#ifndef __DFU_H__
|
||||
#define __DFU_H__
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
typedef enum {
|
||||
DFU_NEXT_CHUNK,
|
||||
DFU_SUCCESS,
|
||||
DFU_FAIL,
|
||||
} dfu_result_t;
|
||||
|
||||
void dfu_init(void);
|
||||
dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len);
|
||||
dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len);
|
||||
dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
|
||||
uint8_t *binfile, uint32_t binfile_len);
|
||||
|
||||
#endif
|
669
core/embed/trezorhal/stm32f4/nrf/fwu.c
Normal file
669
core/embed/trezorhal/stm32f4/nrf/fwu.c
Normal file
@ -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 <trezor_rtl.h>
|
||||
|
||||
#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 <cmd> <ok> C0
|
||||
if (fwu->privateResponseLen < 4) {
|
||||
return FWU_RSP_TOO_SHORT;
|
||||
}
|
||||
if (fwu->privateResponseBuf[0] != FWU_RESPONSE_START) {
|
||||
return FWU_RSP_START_MARKER_MISSING;
|
||||
}
|
||||
if (fwu->privateResponseBuf[1] != fwu->privateRequestBuf[0]) {
|
||||
return FWU_RSP_REQUEST_REFERENCE_INVALID;
|
||||
}
|
||||
if (fwu->privateResponseBuf[2] != FWU_RESPONSE_SUCCESS) {
|
||||
return FWU_RSP_ERROR_RESPONSE;
|
||||
}
|
||||
if (fwu->privateResponseBuf[fwu->privateResponseLen - 1] != FWU_EOM) {
|
||||
return FWU_RSP_END_MARKER_MISSING;
|
||||
}
|
||||
return FWU_RSP_OK;
|
||||
}
|
||||
|
||||
static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode) {
|
||||
uint16_t bytesTodo = fwu->privateObjectLen - fwu->privateObjectIx;
|
||||
uint16_t bufSpace = FWU_REQUEST_BUF_SIZE - 2;
|
||||
|
||||
uint16_t i;
|
||||
uint8_t *p = &fwu->privateRequestBuf[0];
|
||||
|
||||
*p++ = requestCode;
|
||||
fwu->privateRequestLen = 2; // including requestCode and FWU_EOM
|
||||
fwu->privateRequestIx = 0;
|
||||
|
||||
if (bytesTodo > 32) {
|
||||
bytesTodo = 32;
|
||||
}
|
||||
|
||||
for (i = 0; i < bytesTodo && bufSpace >= 2; i++) {
|
||||
uint8_t b = fwu->privateObjectBuf[fwu->privateObjectIx];
|
||||
// SLIP escape characters: C0->DBDC, DB->DBDD
|
||||
if (b == 0xC0 || b == 0xDB) {
|
||||
*p++ = 0xDB;
|
||||
*p++ = (b == 0xC0) ? 0xDC : 0xDD;
|
||||
fwu->privateRequestLen += 2;
|
||||
bufSpace -= 2;
|
||||
} else {
|
||||
*p++ = b;
|
||||
fwu->privateRequestLen++;
|
||||
bufSpace--;
|
||||
}
|
||||
updateCrc(fwu, b);
|
||||
fwu->privateObjectIx++;
|
||||
}
|
||||
|
||||
*p = FWU_EOM;
|
||||
fwu->privateCommandRequest = FWU_CR_SENDONLY;
|
||||
}
|
||||
|
||||
static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len) {
|
||||
// TODO assert privateCommandState == FWU_CS_IDLE | _DONE | _FAIL
|
||||
// TODO assert len <= FWU_REQUEST_BUF_SIZE
|
||||
|
||||
uint8_t i;
|
||||
uint8_t *p = &fwu->privateRequestBuf[0];
|
||||
|
||||
fwu->privateRequestIx = 0;
|
||||
fwu->privateRequestLen = len + 1;
|
||||
fwu->privateResponseLen = 0;
|
||||
|
||||
// Copy the data into our internal buffer.
|
||||
for (i = 0; i < len; i++) {
|
||||
*p++ = *data++;
|
||||
}
|
||||
|
||||
// Add the end-of-message marker.
|
||||
*p = FWU_EOM;
|
||||
|
||||
// Ready to send!
|
||||
fwu->privateCommandRequest = FWU_CR_SEND;
|
||||
}
|
||||
|
||||
static void updateCrc(TFwu *fwu, uint8_t b) {
|
||||
uint8_t i;
|
||||
uint32_t crc = fwu->privateObjectCrc;
|
||||
crc ^= b;
|
||||
for (i = 0; i < 8; i++) {
|
||||
uint32_t m = (crc & 1) ? 0xffffffff : 0;
|
||||
crc = (crc >> 1) ^ (0xedb88320u & m);
|
||||
}
|
||||
fwu->privateObjectCrc = crc;
|
||||
}
|
||||
|
||||
static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason) {
|
||||
fwu->responseStatus = reason;
|
||||
fwu->privateCommandState = FWU_CS_FAIL;
|
||||
// Signal failure to process state machine
|
||||
fwu->privateProcessRequest = FWU_PR_REQUEST_FAILED;
|
||||
}
|
||||
|
||||
static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes) {
|
||||
return bytes[0] | ((uint16_t)bytes[1] << 8);
|
||||
}
|
||||
|
||||
static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes) {
|
||||
return bytes[0] | ((uint16_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) |
|
||||
((uint32_t)bytes[3] << 24);
|
||||
}
|
||||
|
||||
static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes) {
|
||||
uint8_t i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
*bytes++ = v & 0xff;
|
||||
v = v >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
127
core/embed/trezorhal/stm32f4/nrf/fwu.h
Normal file
127
core/embed/trezorhal/stm32f4/nrf/fwu.h
Normal file
@ -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 <trezor_types.h>
|
||||
|
||||
struct SFwu;
|
||||
|
||||
#define FWU_REQUEST_BUF_SIZE 67
|
||||
#define FWU_RESPONSE_BUF_SIZE 16
|
||||
|
||||
typedef enum {
|
||||
FWU_STATUS_UNDEFINED = 0,
|
||||
FWU_STATUS_FAILURE = 1,
|
||||
FWU_STATUS_COMPLETION = 2,
|
||||
} EFwuProcessStatus;
|
||||
|
||||
typedef enum {
|
||||
FWU_RSP_OK = 0,
|
||||
FWU_RSP_TOO_SHORT = 1,
|
||||
FWU_RSP_START_MARKER_MISSING = 2,
|
||||
FWU_RSP_END_MARKER_MISSING = 3,
|
||||
FWU_RSP_REQUEST_REFERENCE_INVALID = 4,
|
||||
FWU_RSP_ERROR_RESPONSE = 5,
|
||||
FWU_RSP_TIMEOUT = 6,
|
||||
FWU_RSP_PING_ID_MISMATCH = 7,
|
||||
FWU_RSP_RX_OVERFLOW = 8,
|
||||
FWU_RSP_INIT_COMMAND_TOO_LARGE = 9,
|
||||
FWU_RSP_CHECKSUM_ERROR = 10,
|
||||
FWU_RSP_DATA_OBJECT_TOO_LARGE = 11,
|
||||
FWU_RSP_RX_INVALID_ESCAPE_SEQ = 12,
|
||||
} EFwuResponseStatus;
|
||||
|
||||
typedef void (*FTxFunction)(struct SFwu *fwu, uint8_t *buf, uint8_t len);
|
||||
|
||||
typedef struct SFwu {
|
||||
// --- public - define these before calling fwuInit ---
|
||||
// .dat
|
||||
uint8_t *commandObject;
|
||||
uint32_t commandObjectLen;
|
||||
// .bin
|
||||
uint8_t *dataObject;
|
||||
uint32_t dataObjectLen;
|
||||
// Sending bytes to the target
|
||||
FTxFunction txFunction;
|
||||
// Timeout when waiting for a response from the target
|
||||
uint32_t responseTimeoutMillisec;
|
||||
// --- public - result codes
|
||||
// Overall process status code
|
||||
EFwuProcessStatus processStatus;
|
||||
// Response status code
|
||||
EFwuResponseStatus responseStatus;
|
||||
// --- private, don't modify ---
|
||||
uint32_t privateDataObjectOffset;
|
||||
uint32_t privateDataObjectSize;
|
||||
uint32_t privateDataObjectMaxSize;
|
||||
uint8_t privateProcessState;
|
||||
uint8_t privateCommandState;
|
||||
uint8_t privateCommandSendOnly;
|
||||
uint32_t privateCommandTimeoutRemainingMillisec;
|
||||
uint8_t privateRequestBuf[FWU_REQUEST_BUF_SIZE + 1];
|
||||
uint8_t privateRequestLen;
|
||||
uint8_t privateRequestIx;
|
||||
uint8_t privateResponseBuf[FWU_RESPONSE_BUF_SIZE];
|
||||
uint8_t privateResponseEscapeCharacter;
|
||||
uint8_t privateResponseLen;
|
||||
uint32_t privateResponseTimeElapsedMillisec;
|
||||
uint8_t privateSendBufSpace;
|
||||
uint8_t privateProcessRequest;
|
||||
uint8_t privateCommandRequest;
|
||||
uint16_t privateMtuSize;
|
||||
// sending a large object buffer
|
||||
uint8_t *privateObjectBuf;
|
||||
uint32_t privateObjectLen;
|
||||
uint32_t privateObjectIx;
|
||||
uint32_t privateObjectCrc;
|
||||
} TFwu;
|
||||
|
||||
// First function to call to set up the internal state in the FWU structure.
|
||||
void fwuInit(TFwu *fwu);
|
||||
|
||||
// Execute the firmware update.
|
||||
void fwuExec(TFwu *fwu);
|
||||
|
||||
// Call regularly to allow asynchronous processing to continue.
|
||||
EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec);
|
||||
|
||||
// Call after data from the target has been received.
|
||||
void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len);
|
||||
|
||||
// Inform the FWU module that it may send maxLen bytes of data to the target.
|
||||
void fwuCanSendData(TFwu *fwu, uint8_t maxLen);
|
||||
|
||||
// Call to send a chunk of the data object to the target.
|
||||
void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len);
|
||||
|
||||
// Call to check if a chunk of the data object can be sent to the target.
|
||||
bool fwuIsReadyForChunk(TFwu *fwu);
|
||||
|
||||
#endif // __FWU_H__
|
719
core/embed/trezorhal/stm32f4/nrf/nrf.c
Normal file
719
core/embed/trezorhal/stm32f4/nrf/nrf.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef KERNEL_MODE
|
||||
|
||||
#include <trezor_bsp.h>
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#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
|
81
core/embed/trezorhal/stm32f4/nrf/nrf.h
Normal file
81
core/embed/trezorhal/stm32f4/nrf/nrf.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_NRF_H
|
||||
#define TREZORHAL_NRF_H
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
// 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
|
39
core/embed/trezorhal/stm32f4/nrf/nrf_internal.h
Normal file
39
core/embed/trezorhal/stm32f4/nrf/nrf_internal.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_NRF_INTERNAL_H
|
||||
#define TREZORHAL_NRF_INTERNAL_H
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
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
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
301
core/embed/trezorhal/stm32f4/tsqueue/tsqueue.c
Normal file
301
core/embed/trezorhal/stm32f4/tsqueue/tsqueue.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <trezor_rtl.h>
|
||||
|
||||
#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;
|
||||
}
|
94
core/embed/trezorhal/stm32f4/tsqueue/tsqueue.h
Normal file
94
core/embed/trezorhal/stm32f4/tsqueue/tsqueue.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_TSQUEUE_H
|
||||
#define TREZORHAL_TSQUEUE_H
|
||||
|
||||
#include <trezor_types.h>
|
||||
|
||||
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
|
@ -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",
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user