1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-01 03:18:12 +00:00

feat(core): ble tunnel via SPI

This commit is contained in:
tychovrahe 2023-03-15 15:42:24 +01:00
parent 9cb2eb40b7
commit e8083af961
16 changed files with 231 additions and 62 deletions

View File

@ -235,12 +235,15 @@ SOURCE_NRFHAL = [
'embed/sdk/nrf52/components/boards/boards.c',
'embed/sdk/nrf52/integration/nrfx/legacy/nrf_drv_clock.c',
'embed/sdk/nrf52/integration/nrfx/legacy/nrf_drv_uart.c',
'embed/sdk/nrf52/integration/nrfx/legacy/nrf_drv_spi.c',
'embed/sdk/nrf52/modules/nrfx/soc/nrfx_atomic.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_clock.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_gpiote.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/prs/nrfx_prs.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_uart.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_uarte.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_spi.c',
'embed/sdk/nrf52/modules/nrfx/drivers/src/nrfx_spim.c',
'embed/sdk/nrf52/components/libraries/bsp/bsp.c',
'embed/sdk/nrf52/components/libraries/bsp/bsp_btn_ble.c',
'embed/sdk/nrf52/components/ble/common/ble_advdata.c',

View File

@ -3,10 +3,17 @@
#include "app_error.h"
#include "app_uart.h"
#include "ble_nus.h"
#include "nrf_drv_spi.h"
#include "nrf_log.h"
#include "stdint.h"
#include "trezorhal/ble/int_comm_defs.h"
#define SPI_INSTANCE 0 /**< SPI instance index. */
#define SPI_SCK_PIN 26
#define SPI_MISO_PIN 30
#define SPI_MOSI_PIN 29
#define SPI_SS_PIN 31
static uint8_t m_uart_rx_data[BLE_NUS_MAX_DATA_LEN];
static bool m_uart_rx_data_ready_internal = false;
static uint16_t *m_p_conn_handle = NULL;
@ -14,6 +21,30 @@ static uint16_t *m_p_conn_handle = NULL;
BLE_NUS_DEF(m_nus,
NRF_SDH_BLE_TOTAL_LINK_COUNT); /**< BLE NUS service instance. */
static const nrf_drv_spi_t spi =
NRF_DRV_SPI_INSTANCE(SPI_INSTANCE); /**< SPI instance. */
static volatile bool spi_xfer_done; /**< Flag used to indicate that SPI instance
completed the transfer. */
/**
* @brief SPI user event handler.
* @param event
*/
void spi_event_handler(nrf_drv_spi_evt_t const *p_event, void *p_context) {
spi_xfer_done = true;
NRF_LOG_INFO("Transfer completed.");
}
void spi_init(void) {
nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
spi_config.ss_pin = SPI_SS_PIN;
spi_config.miso_pin = SPI_MISO_PIN;
spi_config.mosi_pin = SPI_MOSI_PIN;
spi_config.sck_pin = SPI_SCK_PIN;
spi_config.frequency = NRF_DRV_SPI_FREQ_8M;
APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL));
}
void nus_init(uint16_t *p_conn_handle) {
m_p_conn_handle = p_conn_handle;
uint32_t err_code;
@ -158,14 +189,14 @@ void send_packet(uint8_t message_type, const uint8_t *tx_data, uint16_t len) {
/**@brief Function for handling the data from the Nordic UART Service.
*
* @details This function will process the data received from the Nordic UART
* BLE Service and send it to the UART module.
* BLE Service and forward it to Trezor
*
* @param[in] p_evt Nordic UART Service event.
*/
/**@snippet [Handling the data received over BLE] */
void nus_data_handler(ble_nus_evt_t *p_evt) {
if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
NRF_LOG_DEBUG("Received data from BLE NUS. Writing data on UART.");
NRF_LOG_DEBUG("Received data from BLE NUS. Forwarding.");
NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data,
p_evt->params.rx_data.length);
@ -173,8 +204,8 @@ void nus_data_handler(ble_nus_evt_t *p_evt) {
return;
}
send_packet(EXTERNAL_MESSAGE, p_evt->params.rx_data.p_data,
p_evt->params.rx_data.length);
nrf_drv_spi_transfer(&spi, p_evt->params.rx_data.p_data,
p_evt->params.rx_data.length, NULL, 0);
}
}
/**@snippet [Handling the data received over BLE] */

View File

@ -6,6 +6,8 @@
#include "ble_nus.h"
#include "stdint.h"
void spi_init(void);
void nus_init(uint16_t *p_conn_handle);
void nus_data_handler(ble_nus_evt_t *p_evt);

View File

@ -66,6 +66,7 @@
#include "nrf.h"
#include "nrf_ble_gatt.h"
#include "nrf_ble_qwr.h"
#include "nrf_drv_spi.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
@ -826,6 +827,7 @@ int main(void) {
bool erase_bonds;
// Initialize.
spi_init();
uart_init();
log_init();
timers_init();

View File

@ -3544,7 +3544,7 @@
// <e> NRFX_SPIM_ENABLED - nrfx_spim - SPIM peripheral driver
//==========================================================
#ifndef NRFX_SPIM_ENABLED
#define NRFX_SPIM_ENABLED 0
#define NRFX_SPIM_ENABLED 1
#endif
// <q> NRFX_SPIM0_ENABLED - Enable SPIM0 instance
@ -3770,7 +3770,7 @@
// <e> NRFX_SPI_ENABLED - nrfx_spi - SPI peripheral driver
//==========================================================
#ifndef NRFX_SPI_ENABLED
#define NRFX_SPI_ENABLED 0
#define NRFX_SPI_ENABLED 1
#endif
// <q> NRFX_SPI0_ENABLED - Enable SPI0 instance
@ -5624,7 +5624,7 @@
// <e> SPI_ENABLED - nrf_drv_spi - SPI/SPIM peripheral driver - legacy layer
//==========================================================
#ifndef SPI_ENABLED
#define SPI_ENABLED 0
#define SPI_ENABLED 1
#endif
// <o> SPI_DEFAULT_CONFIG_IRQ_PRIORITY - Interrupt priority
@ -5656,7 +5656,7 @@
// <e> SPI0_ENABLED - Enable SPI0 instance
//==========================================================
#ifndef SPI0_ENABLED
#define SPI0_ENABLED 0
#define SPI0_ENABLED 1
#endif
// <q> SPI0_USE_EASY_DMA - Use EasyDMA

View File

@ -65,29 +65,31 @@ STATIC mp_obj_t mod_trezorio_BLE_update_chunk(mp_obj_t data) {
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_update_chunk_obj,
mod_trezorio_BLE_update_chunk);
/// def write(self, msg: bytes) -> int:
/// def write_int(self, msg: bytes) -> int:
/// """
/// Sends message using BLE.
/// Sends internal message to NRF.
/// """
STATIC mp_obj_t mod_trezorio_BLE_write(mp_obj_t self, mp_obj_t msg) {
STATIC mp_obj_t mod_trezorio_BLE_write_int(mp_obj_t self, mp_obj_t msg) {
mp_buffer_info_t buf = {0};
mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ);
ble_int_comm_send(buf.buf, buf.len,
ble_last_internal ? INTERNAL_MESSAGE : EXTERNAL_MESSAGE);
ble_int_comm_send(buf.buf, buf.len, INTERNAL_MESSAGE);
return MP_OBJ_NEW_SMALL_INT(buf.len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_write_obj,
mod_trezorio_BLE_write);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_write_int_obj,
mod_trezorio_BLE_write_int);
/// def iface_type(self) -> int:
/// def write_ext(self, msg: bytes) -> int:
/// """
/// Internal / External distinction
/// Sends message over BLE
/// """
STATIC mp_obj_t mod_trezorio_BLE_iface_type(mp_obj_t self) {
return MP_OBJ_NEW_SMALL_INT(ble_last_internal ? 1 : 0);
STATIC mp_obj_t mod_trezorio_BLE_write_ext(mp_obj_t self, mp_obj_t msg) {
mp_buffer_info_t buf = {0};
mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ);
ble_int_comm_send(buf.buf, buf.len, EXTERNAL_MESSAGE);
return MP_OBJ_NEW_SMALL_INT(buf.len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_iface_type_obj,
mod_trezorio_BLE_iface_type);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_write_ext_obj,
mod_trezorio_BLE_write_ext);
STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ble)},
@ -95,9 +97,10 @@ STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = {
MP_ROM_PTR(&mod_trezorio_BLE_update_init_obj)},
{MP_ROM_QSTR(MP_QSTR_update_chunk),
MP_ROM_PTR(&mod_trezorio_BLE_update_chunk_obj)},
{MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_BLE_write_obj)},
{MP_ROM_QSTR(MP_QSTR_iface_type),
MP_ROM_PTR(&mod_trezorio_BLE_iface_type_obj)},
{MP_ROM_QSTR(MP_QSTR_write_int),
MP_ROM_PTR(&mod_trezorio_BLE_write_int_obj)},
{MP_ROM_QSTR(MP_QSTR_write_ext),
MP_ROM_PTR(&mod_trezorio_BLE_write_ext_obj)},
};
STATIC MP_DEFINE_CONST_DICT(mod_trezorio_BLE_globals,
mod_trezorio_BLE_globals_table);

View File

@ -31,7 +31,8 @@
#define BUTTON_IFACE (254)
#define TOUCH_IFACE (255)
#define USB_RW_IFACE_MAX (15) // 0-15 reserved for USB
#define BLE_IFACE (16)
#define BLE_IFACE_INT (16)
#define BLE_IFACE_EXT (17)
#define POLL_READ (0x0000)
#define POLL_WRITE (0x0100)
@ -189,10 +190,24 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref,
return mp_const_true;
}
}
} else if (iface == BLE_IFACE) {
} else if (iface == BLE_IFACE_INT) {
if (mode == POLL_READ) {
uint8_t buf[64] = {0};
int len = ble_int_comm_receive(buf, sizeof(buf), &ble_last_internal);
int len = ble_int_comm_receive(buf, sizeof(buf));
if (len > 0) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
ret->items[1] = mp_obj_new_bytes(buf, len);
return mp_const_true;
}
} else if (mode == POLL_WRITE) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
ret->items[1] = mp_const_none;
return mp_const_true;
}
} else if (iface == BLE_IFACE_EXT) {
if (mode == POLL_READ) {
uint8_t buf[64] = {0};
int len = ble_ext_comm_receive(buf, sizeof(buf));
if (len > 0) {
ret->items[0] = MP_OBJ_NEW_SMALL_INT(i);
ret->items[1] = mp_obj_new_bytes(buf, len);

View File

@ -77,6 +77,7 @@ bool ble_last_internal = false;
/// BUTTON_RIGHT: int # button number of right button
/// USB_CHECK: int # interface id for check of USB data connection
/// BLE_CHECK: int # interface id for check of BLE data connection
/// WireInterface = Union[HID, WebUSB, BleInterface]

View File

@ -21,12 +21,30 @@
#include TREZOR_BOARD
#include "comm.h"
#include <string.h>
#include "dma.h"
#include "int_comm_defs.h"
#include "state.h"
#define SPI_PACKET_SIZE 64
#define SPI_QUEUE_SIZE 4
static UART_HandleTypeDef urt;
static uint8_t last_init_byte = 0;
static SPI_HandleTypeDef spi = {0};
static DMA_HandleTypeDef spi_dma = {0};
typedef struct {
uint8_t buffer[SPI_PACKET_SIZE];
bool used;
bool ready;
} spi_buffer_t;
spi_buffer_t spi_queue[SPI_QUEUE_SIZE];
static int head = 0, tail = 0;
static bool overrun = 1;
void ble_comm_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
@ -50,7 +68,40 @@ void ble_comm_init(void) {
HAL_UART_Init(&urt);
__HAL_RCC_DMA2_CLK_ENABLE();
__HAL_RCC_SPI4_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitStructure.Pin = GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Alternate = GPIO_AF5_SPI4;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
dma_init(&spi_dma, &dma_SPI_4_RX, DMA_PERIPH_TO_MEMORY, &spi);
spi.Instance = SPI4;
spi.Init.Mode = SPI_MODE_SLAVE;
spi.Init.Direction = SPI_DIRECTION_2LINES; // rx only?
spi.Init.DataSize = SPI_DATASIZE_8BIT;
spi.Init.CLKPolarity = SPI_POLARITY_LOW;
spi.Init.CLKPhase = SPI_PHASE_1EDGE;
spi.Init.NSS = SPI_NSS_HARD_INPUT;
spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
spi.Init.TIMode = SPI_TIMODE_DISABLE;
spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
spi.Init.CRCPolynomial = 0;
spi.hdmarx = &spi_dma;
spi_dma.Parent = &spi;
HAL_SPI_Init(&spi);
set_initialized(false);
HAL_SPI_Receive_DMA(&spi, spi_queue[0].buffer, 64);
}
void ble_comm_send(uint8_t *data, uint32_t len) {
@ -181,13 +232,12 @@ uint32_t ble_int_comm_poll(void) {
return 0;
}
uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len, bool *internal) {
uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len) {
if (urt.Instance->SR & USART_SR_RXNE) {
uint8_t init_byte = 0;
if (last_init_byte != 0) {
if (last_init_byte == INTERNAL_MESSAGE ||
last_init_byte == EXTERNAL_MESSAGE) {
if (last_init_byte == INTERNAL_MESSAGE) {
init_byte = last_init_byte;
} else {
return 0;
@ -197,7 +247,7 @@ uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len, bool *internal) {
HAL_UART_Receive(&urt, &init_byte, 1, 1);
}
if (init_byte == INTERNAL_MESSAGE || init_byte == EXTERNAL_MESSAGE) {
if (init_byte == INTERNAL_MESSAGE) {
uint8_t len_hi = 0;
uint8_t len_lo = 0;
HAL_UART_Receive(&urt, &len_hi, 1, 1);
@ -224,11 +274,6 @@ uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len, bool *internal) {
HAL_UART_Receive(&urt, &eom, 1, 1);
if (eom == EOM) {
if (init_byte == INTERNAL_MESSAGE) {
*internal = true;
} else {
*internal = false;
}
last_init_byte = 0;
return act_len - OVERHEAD_SIZE;
}
@ -244,3 +289,39 @@ uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len, bool *internal) {
return 0;
}
bool start_spi_dma(void) {
if (spi_queue[tail].used || spi_queue[tail].ready) {
overrun = true;
return false;
}
spi_queue[tail].used = true;
HAL_SPI_Receive_DMA(&spi, spi_queue[tail].buffer, SPI_PACKET_SIZE);
return true;
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
spi_queue[tail].ready = true;
tail = (tail + 1) % SPI_QUEUE_SIZE;
start_spi_dma();
}
uint32_t ble_ext_comm_receive(uint8_t *data, uint32_t len) {
if (spi_queue[head].ready) {
uint8_t *buffer = (uint8_t *)spi_queue[head].buffer;
memcpy(data, buffer, len > SPI_PACKET_SIZE ? SPI_PACKET_SIZE : len);
spi_queue[head].used = false;
spi_queue[head].ready = false;
head = (head + 1) % SPI_QUEUE_SIZE;
if (overrun && start_spi_dma()) {
// overrun was before, need to restart the DMA
overrun = false;
}
return len > SPI_PACKET_SIZE ? SPI_PACKET_SIZE : len;
}
return 0;
}

View File

@ -11,7 +11,8 @@ void ble_comm_send(uint8_t *data, uint32_t len);
uint32_t ble_comm_receive(uint8_t *data, uint32_t len);
void ble_int_comm_send(uint8_t *data, uint32_t len, uint8_t message_type);
uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len, bool *internal);
uint32_t ble_int_comm_receive(uint8_t *data, uint32_t len);
uint32_t ble_ext_comm_receive(uint8_t *data, uint32_t len);
uint32_t ble_int_comm_poll(void);

View File

@ -74,18 +74,34 @@ struct _dma_descr_t {
// Parameters to dma_init() for SDIO tx and rx.
static const DMA_InitTypeDef dma_init_struct_sdio = {
.Channel = 0,
.Direction = 0,
.PeriphInc = DMA_PINC_DISABLE,
.MemInc = DMA_MINC_ENABLE,
.PeriphDataAlignment = DMA_PDATAALIGN_WORD,
.MemDataAlignment = DMA_MDATAALIGN_WORD,
.Mode = DMA_PFCTRL,
.Priority = DMA_PRIORITY_VERY_HIGH,
.FIFOMode = DMA_FIFOMODE_ENABLE,
.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL,
.MemBurst = DMA_MBURST_INC4,
.PeriphBurst = DMA_PBURST_INC4,
.Channel = 0,
.Direction = 0,
.PeriphInc = DMA_PINC_DISABLE,
.MemInc = DMA_MINC_ENABLE,
.PeriphDataAlignment = DMA_PDATAALIGN_WORD,
.MemDataAlignment = DMA_MDATAALIGN_WORD,
.Mode = DMA_PFCTRL,
.Priority = DMA_PRIORITY_VERY_HIGH,
.FIFOMode = DMA_FIFOMODE_ENABLE,
.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL,
.MemBurst = DMA_MBURST_INC4,
.PeriphBurst = DMA_PBURST_INC4,
};
// Parameters to dma_init() for SPI 4 RX
static const DMA_InitTypeDef dma_init_struct_spi4 = {
.Channel = 0,
.Direction = 0,
.PeriphInc = DMA_PINC_DISABLE,
.MemInc = DMA_MINC_ENABLE,
.PeriphDataAlignment = DMA_PDATAALIGN_BYTE,
.MemDataAlignment = DMA_MDATAALIGN_BYTE,
.Mode = DMA_NORMAL,
.Priority = DMA_PRIORITY_VERY_HIGH,
.FIFOMode = DMA_FIFOMODE_DISABLE,
.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL,
.MemBurst = DMA_MBURST_SINGLE,
.PeriphBurst = DMA_PBURST_SINGLE,
};
#define NCONTROLLERS (2)
@ -98,6 +114,7 @@ static const DMA_InitTypeDef dma_init_struct_sdio = {
#define DMA2_ENABLE_MASK (0xff00) // Bits in dma_enable_mask corresponding to DMA2
const dma_descr_t dma_SDIO_0 = { DMA2_Stream3, DMA_CHANNEL_4, dma_id_11, &dma_init_struct_sdio };
const dma_descr_t dma_SPI_4_RX = { DMA2_Stream0, DMA_CHANNEL_4, dma_id_8, &dma_init_struct_spi4 };
static const uint8_t dma_irqn[NSTREAM] = {
DMA1_Stream0_IRQn,

View File

@ -33,6 +33,7 @@
typedef struct _dma_descr_t dma_descr_t;
extern const dma_descr_t dma_SDIO_0;
extern const dma_descr_t dma_SPI_4_RX;
void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data);
void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data);

View File

@ -16,7 +16,14 @@ def update_chunk(chunk: bytes) -> int:
# extmod/modtrezorio/modtrezorio-ble.h
def write(self, msg: bytes) -> int:
def write_int(self, msg: bytes) -> int:
"""
Sends message using BLE.
Sends internal message to NRF.
"""
# extmod/modtrezorio/modtrezorio-ble.h
def write_ext(self, msg: bytes) -> int:
"""
Sends message over BLE
"""

View File

@ -56,12 +56,12 @@ def _find_message_handler_module(msg_type: int, iface: WireInterface) -> str:
return "apps.management.sd_protect"
# BLE
if iface.iface_num() != 16:
if iface.iface_num() != 16 and iface.iface_num() != 17:
# cannot update over BLE
if msg_type == MessageType.UploadBLEFirmwareInit:
return "apps.management.ble.upload_ble_firmware_init"
if iface.iface_num() == 16 and iface.iface_type() == 1:
if iface.iface_num() == 16:
if msg_type == MessageType.PairingRequest:
return "apps.management.ble.pairing_request"
if msg_type == MessageType.RepairRequest:

View File

@ -2,19 +2,22 @@ from trezorio import ble
class BleInterface:
def __init__(self):
def __init__(self, interface: int):
self.interface = interface
pass
def iface_num(self) -> int:
return 16
def iface_type(self):
return ble.iface_type(self)
return self.interface
def write(self, msg: bytes) -> int:
return ble.write(self, msg)
if self.interface == 16:
return ble.write_int(self, msg)
if self.interface == 17:
return ble.write_ext(self, msg)
return 0
# interface used for trezor wire protocol
iface_ble = BleInterface()
iface_ble_int = BleInterface(16)
iface_ble_ext = BleInterface(17)

View File

@ -27,13 +27,15 @@ mutex = Mutex()
mutex.add(usb.iface_wire.iface_num())
mutex.add(usb.iface_debug.iface_num())
mutex.add(bluetooth.iface_ble.iface_num())
mutex.add(bluetooth.iface_ble_int.iface_num())
mutex.add(bluetooth.iface_ble_ext.iface_num())
# initialize the wire codec
wire.setup(usb.iface_wire, mutex=mutex)
if __debug__:
wire.setup(usb.iface_debug, is_debug_session=True, mutex=mutex)
wire.setup(bluetooth.iface_ble, mutex=mutex)
wire.setup(bluetooth.iface_ble_int, mutex=mutex)
wire.setup(bluetooth.iface_ble_ext, mutex=mutex)
loop.run()