This commit is contained in:
Willy-JL
2023-11-12 02:20:45 +00:00
498 changed files with 4821 additions and 2206 deletions

View File

@@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <core/common_defines.h>
#include <tl.h>
#include "app_conf.h"

View File

@@ -0,0 +1,202 @@
#pragma once
#include <ble/core/ble_defs.h>
#define CFG_TX_POWER (0x19) /* +0dBm */
#define CFG_IDENTITY_ADDRESS GAP_PUBLIC_ADDR
/**
* Define IO Authentication
*/
#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN
#define CFG_ENCRYPTION_KEY_SIZE_MAX (16)
#define CFG_ENCRYPTION_KEY_SIZE_MIN (8)
/**
* Define IO capabilities
*/
#define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO
/**
* Define MITM modes
*/
#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED
/**
* Define Secure Connections Support
*/
#define CFG_SC_SUPPORT SC_PAIRING_OPTIONAL
/**
* Define PHY
*/
#define ALL_PHYS_PREFERENCE 0x00
#define RX_2M_PREFERRED 0x02
#define TX_2M_PREFERRED 0x02
#define TX_1M 0x01
#define TX_2M 0x02
#define RX_1M 0x01
#define RX_2M 0x02
/******************************************************************************
* BLE Stack
******************************************************************************/
/**
* Maximum number of simultaneous connections that the device will support.
* Valid values are from 1 to 8
*/
#define CFG_BLE_NUM_LINK 1
/**
* Maximum number of Services that can be stored in the GATT database.
* Note that the GAP and GATT services are automatically added so this parameter should be 2 plus the number of user services
*/
#define CFG_BLE_NUM_GATT_SERVICES 8
/**
* Maximum number of Attributes
* (i.e. the number of characteristic + the number of characteristic values + the number of descriptors, excluding the services)
* that can be stored in the GATT database.
* Note that certain characteristics and relative descriptors are added automatically during device initialization
* so this parameters should be 9 plus the number of user Attributes
*/
#define CFG_BLE_NUM_GATT_ATTRIBUTES 68
/**
* Maximum supported ATT_MTU size
*/
#define CFG_BLE_MAX_ATT_MTU (256 + 128 + 16 + 8 + 4 + 2)
/**
* Size of the storage area for Attribute values
* This value depends on the number of attributes used by application. In particular the sum of the following quantities (in octets) should be made for each attribute:
* - attribute value length
* - 5, if UUID is 16 bit; 19, if UUID is 128 bit
* - 2, if server configuration descriptor is used
* - 2*DTM_NUM_LINK, if client configuration descriptor is used
* - 2, if extended properties is used
* The total amount of memory needed is the sum of the above quantities for each attribute.
*/
#define CFG_BLE_ATT_VALUE_ARRAY_SIZE (1344)
/**
* Prepare Write List size in terms of number of packet
*/
#define CFG_BLE_PREPARE_WRITE_LIST_SIZE BLE_PREP_WRITE_X_ATT(CFG_BLE_MAX_ATT_MTU)
/**
* Number of allocated memory blocks
*/
#define CFG_BLE_MBLOCK_COUNT \
(BLE_MBLOCKS_CALC(CFG_BLE_PREPARE_WRITE_LIST_SIZE, CFG_BLE_MAX_ATT_MTU, CFG_BLE_NUM_LINK))
/**
* Enable or disable the Extended Packet length feature. Valid values are 0 or 1.
*/
#define CFG_BLE_DATA_LENGTH_EXTENSION 1
/**
* Sleep clock accuracy in Slave mode (ppm value)
*/
#define CFG_BLE_SLAVE_SCA 500
/**
* Sleep clock accuracy in Master mode
* 0 : 251 ppm to 500 ppm
* 1 : 151 ppm to 250 ppm
* 2 : 101 ppm to 150 ppm
* 3 : 76 ppm to 100 ppm
* 4 : 51 ppm to 75 ppm
* 5 : 31 ppm to 50 ppm
* 6 : 21 ppm to 30 ppm
* 7 : 0 ppm to 20 ppm
*/
#define CFG_BLE_MASTER_SCA 0
/**
* Source for the low speed clock for RF wake-up
* 1 : external high speed crystal HSE/32/32
* 0 : external low speed crystal ( no calibration )
*/
#define CFG_BLE_LSE_SOURCE \
SHCI_C2_BLE_INIT_CFG_BLE_LS_CLK_LSE | SHCI_C2_BLE_INIT_CFG_BLE_LS_OTHER_DEV | \
SHCI_C2_BLE_INIT_CFG_BLE_LS_CALIB
/**
* Start up time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 us (~2.44 us)
*/
#define CFG_BLE_HSE_STARTUP_TIME 0x148
/**
* Maximum duration of the connection event when the device is in Slave mode in units of 625/256 us (~2.44 us)
*/
#define CFG_BLE_MAX_CONN_EVENT_LENGTH (0xFFFFFFFF)
/**
* Viterbi Mode
* 1 : enabled
* 0 : disabled
*/
#define CFG_BLE_VITERBI_MODE 1
/**
* BLE stack Options flags to be configured with:
* - SHCI_C2_BLE_INIT_OPTIONS_LL_ONLY
* - SHCI_C2_BLE_INIT_OPTIONS_LL_HOST
* - SHCI_C2_BLE_INIT_OPTIONS_NO_SVC_CHANGE_DESC
* - SHCI_C2_BLE_INIT_OPTIONS_WITH_SVC_CHANGE_DESC
* - SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO
* - SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RW
* - SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV
* - SHCI_C2_BLE_INIT_OPTIONS_NO_EXT_ADV
* - SHCI_C2_BLE_INIT_OPTIONS_CS_ALGO2
* - SHCI_C2_BLE_INIT_OPTIONS_NO_CS_ALGO2
* - SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_1
* - SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3
* which are used to set following configuration bits:
* (bit 0): 1: LL only
* 0: LL + host
* (bit 1): 1: no service change desc.
* 0: with service change desc.
* (bit 2): 1: device name Read-Only
* 0: device name R/W
* (bit 3): 1: extended advertizing supported [NOT SUPPORTED]
* 0: extended advertizing not supported [NOT SUPPORTED]
* (bit 4): 1: CS Algo #2 supported
* 0: CS Algo #2 not supported
* (bit 7): 1: LE Power Class 1
* 0: LE Power Class 2-3
* other bits: reserved (shall be set to 0)
*/
#define CFG_BLE_OPTIONS \
(SHCI_C2_BLE_INIT_OPTIONS_LL_HOST | SHCI_C2_BLE_INIT_OPTIONS_WITH_SVC_CHANGE_DESC | \
SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO | SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV | \
SHCI_C2_BLE_INIT_OPTIONS_CS_ALGO2 | SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3)
/**
* Queue length of BLE Event
* This parameter defines the number of asynchronous events that can be stored in the HCI layer before
* being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer
* is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large
* enough to store all asynchronous events received in between.
* When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events
* between the HCI command and its event.
* This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small,
* the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting
* for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate
* to the application a HCI command did not receive its command event within 30s (Default HCI Timeout).
*/
#define CFG_TLBLE_EVT_QUEUE_LENGTH 5
/**
* This parameter should be set to fit most events received by the HCI layer. It defines the buffer size of each element
* allocated in the queue of received events and can be used to optimize the amount of RAM allocated by the Memory Manager.
* It should not exceed 255 which is the maximum HCI packet payload size (a greater value is a lost of memory as it will
* never be used)
* With the current wireless firmware implementation, this parameter shall be kept to 255
*
*/
#define CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE \
255 /**< Set to 255 with the memory manager and the mailbox */
#define TL_BLE_EVENT_FRAME_SIZE (TL_EVT_HDR_SIZE + CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE)

View File

@@ -0,0 +1,253 @@
#include "app_common.h"
#include "app_debug.h"
#include <interface/patterns/ble_thread/tl/tl.h>
#include <interface/patterns/ble_thread/tl/mbox_def.h>
#include <interface/patterns/ble_thread/shci/shci.h>
#include <utilities/dbg_trace.h>
#include <utilities/utilities_common.h>
#include "stm32wbxx_ll_bus.h"
#include "stm32wbxx_ll_pwr.h"
#include <furi_hal.h>
typedef PACKED_STRUCT {
GPIO_TypeDef* port;
uint16_t pin;
uint8_t enable;
uint8_t reserved;
}
APPD_GpioConfig_t;
#define GPIO_NBR_OF_RF_SIGNALS 9
#define GPIO_CFG_NBR_OF_FEATURES 34
#define NBR_OF_TRACES_CONFIG_PARAMETERS 4
#define NBR_OF_GENERAL_CONFIG_PARAMETERS 4
/**
* THIS SHALL BE SET TO A VALUE DIFFERENT FROM 0 ONLY ON REQUEST FROM ST SUPPORT
*/
#define BLE_DTB_CFG 0
// #define BLE_DTB_CFG 7
#define SYS_DBG_CFG1 (SHCI_C2_DEBUG_OPTIONS_IPCORE_LP | SHCI_C2_DEBUG_OPTIONS_CPU2_STOP_EN)
/* Private variables ---------------------------------------------------------*/
PLACE_IN_SECTION("MB_MEM2")
ALIGN(4) static SHCI_C2_DEBUG_TracesConfig_t APPD_TracesConfig = {0, 0, 0, 0};
PLACE_IN_SECTION("MB_MEM2")
ALIGN(4)
static SHCI_C2_DEBUG_GeneralConfig_t APPD_GeneralConfig =
{BLE_DTB_CFG, SYS_DBG_CFG1, {0, 0}, 0, 0, 0, 0, 0};
/**
* THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT
* It provides timing information on the CPU2 activity.
* All configuration of (port, pin) is supported for each features and can be selected by the user
* depending on the availability
*/
static const APPD_GpioConfig_t aGpioConfigList[GPIO_CFG_NBR_OF_FEATURES] = {
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_ISR - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_7, 1, 0}, /* BLE_STACK_TICK - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_CMD_PROCESS - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_ACL_DATA_PROCESS - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* SYS_CMD_PROCESS - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* RNG_PROCESS - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVM_PROCESS - Set on Entry / Reset on Exit */
{GPIOB, LL_GPIO_PIN_3, 1, 0}, /* IPCC_GENERAL - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_CMD_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_EVT_TX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_ACL_DATA_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_SYS_CMD_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_SYS_EVT_TX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_CLI_CMD_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_OT_CMD_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_OT_ACK_TX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_CLI_ACK_TX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_MEM_MANAGER_RX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_TRACES_TX - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_6, 1, 0}, /* HARD_FAULT - Set on Entry / Reset on Exit */
/* From v1.1.1 */
{GPIOC, LL_GPIO_PIN_1, 1, 0}, /* IP_CORE_LP_STATUS - Set on Entry / Reset on Exit */
/* From v1.2.0 */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* END_OF_CONNECTION_EVENT - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* TIMER_SERVER_CALLBACK - Toggle on Entry */
{GPIOA, LL_GPIO_PIN_4, 1, 0}, /* PES_ACTIVITY - Set on Entry / Reset on Exit */
{GPIOC, LL_GPIO_PIN_0, 1, 0}, /* MB_BLE_SEND_EVT - Set on Entry / Reset on Exit */
/* From v1.3.0 */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_NO_DELAY - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_STACK_STORE_NVM_CB - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_WRITE_ONGOING - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_WRITE_COMPLETE - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_CLEANUP - Set on Entry / Reset on Exit */
/* From v1.4.0 */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_START - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_EOP - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_WRITE - Set on Entry / Reset on Exit */
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_ERASE - Set on Entry / Reset on Exit */
};
/**
* THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT
* This table is relevant only for BLE
* It provides timing information on BLE RF activity.
* New signals may be allocated at any location when requested by ST
* The GPIO allocated to each signal depend on the BLE_DTB_CFG value and cannot be changed
*/
#if(BLE_DTB_CFG == 7)
static const APPD_GpioConfig_t aRfConfigList[GPIO_NBR_OF_RF_SIGNALS] = {
{GPIOB, LL_GPIO_PIN_2, 0, 0}, /* DTB10 - Tx/Rx SPI */
{GPIOB, LL_GPIO_PIN_7, 0, 0}, /* DTB11 - Tx/Tx SPI Clk */
{GPIOA, LL_GPIO_PIN_8, 0, 0}, /* DTB12 - Tx/Rx Ready & SPI Select */
{GPIOA, LL_GPIO_PIN_9, 0, 0}, /* DTB13 - Tx/Rx Start */
{GPIOA, LL_GPIO_PIN_10, 0, 0}, /* DTB14 - FSM0 */
{GPIOA, LL_GPIO_PIN_11, 0, 0}, /* DTB15 - FSM1 */
{GPIOB, LL_GPIO_PIN_8, 0, 0}, /* DTB16 - FSM2 */
{GPIOB, LL_GPIO_PIN_11, 0, 0}, /* DTB17 - FSM3 */
{GPIOB, LL_GPIO_PIN_10, 0, 0}, /* DTB18 - FSM4 */
};
#endif
static void APPD_SetCPU2GpioConfig(void);
static void APPD_BleDtbCfg(void);
void APPD_Init() {
APPD_SetCPU2GpioConfig();
APPD_BleDtbCfg();
}
void APPD_EnableCPU2(void) {
SHCI_C2_DEBUG_Init_Cmd_Packet_t DebugCmdPacket = {
{{0, 0, 0}}, /**< Does not need to be initialized */
{(uint8_t*)aGpioConfigList,
(uint8_t*)&APPD_TracesConfig,
(uint8_t*)&APPD_GeneralConfig,
GPIO_CFG_NBR_OF_FEATURES,
NBR_OF_TRACES_CONFIG_PARAMETERS,
NBR_OF_GENERAL_CONFIG_PARAMETERS}};
/**< Traces channel initialization */
TL_TRACES_Init();
/** GPIO DEBUG Initialization */
SHCI_C2_DEBUG_Init(&DebugCmdPacket);
// We don't need External Power Amplifier
// LL_GPIO_InitTypeDef gpio_config;
// gpio_config.Pull = GPIO_NOPULL;
// gpio_config.Mode = GPIO_MODE_OUTPUT_PP;
// gpio_config.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
// gpio_config.Pin = LL_GPIO_PIN_3;
// HAL_GPIO_Init(GPIOC, &gpio_config);
// SHCI_C2_ExtpaConfig((uint32_t)GPIOC, LL_GPIO_PIN_3, EXT_PA_ENABLED_LOW, EXT_PA_ENABLED);
return;
}
static void APPD_SetCPU2GpioConfig(void) {
LL_GPIO_InitTypeDef gpio_config = {0};
uint8_t local_loop;
uint16_t gpioa_pin_list;
uint16_t gpiob_pin_list;
uint16_t gpioc_pin_list;
gpioa_pin_list = 0;
gpiob_pin_list = 0;
gpioc_pin_list = 0;
for(local_loop = 0; local_loop < GPIO_CFG_NBR_OF_FEATURES; local_loop++) {
if(aGpioConfigList[local_loop].enable != 0) {
switch((uint32_t)aGpioConfigList[local_loop].port) {
case(uint32_t)GPIOA:
gpioa_pin_list |= aGpioConfigList[local_loop].pin;
break;
case(uint32_t)GPIOB:
gpiob_pin_list |= aGpioConfigList[local_loop].pin;
break;
case(uint32_t)GPIOC:
gpioc_pin_list |= aGpioConfigList[local_loop].pin;
break;
default:
break;
}
}
}
gpio_config.Mode = LL_GPIO_MODE_OUTPUT;
gpio_config.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
gpio_config.Pull = LL_GPIO_PULL_NO;
// Never disable SWD, why would you?
// gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13;
// LL_GPIO_Init(GPIOA, &gpio_config);
if(gpioa_pin_list != 0) {
gpio_config.Pin = gpioa_pin_list;
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOA);
LL_GPIO_Init(GPIOA, &gpio_config);
LL_GPIO_ResetOutputPin(GPIOA, gpioa_pin_list);
}
if(gpiob_pin_list != 0) {
gpio_config.Pin = gpiob_pin_list;
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOB);
LL_GPIO_Init(GPIOB, &gpio_config);
LL_GPIO_ResetOutputPin(GPIOB, gpiob_pin_list);
}
if(gpioc_pin_list != 0) {
gpio_config.Pin = gpioc_pin_list;
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOC);
LL_GPIO_Init(GPIOC, &gpio_config);
LL_GPIO_ResetOutputPin(GPIOC, gpioc_pin_list);
}
}
static void APPD_BleDtbCfg(void) {
#if(BLE_DTB_CFG != 0)
LL_GPIO_InitTypeDef gpio_config = {0};
uint8_t local_loop;
uint16_t gpioa_pin_list;
uint16_t gpiob_pin_list;
gpioa_pin_list = 0;
gpiob_pin_list = 0;
for(local_loop = 0; local_loop < GPIO_NBR_OF_RF_SIGNALS; local_loop++) {
if(aRfConfigList[local_loop].enable != 0) {
switch((uint32_t)aRfConfigList[local_loop].port) {
case(uint32_t)GPIOA:
gpioa_pin_list |= aRfConfigList[local_loop].pin;
break;
case(uint32_t)GPIOB:
gpiob_pin_list |= aRfConfigList[local_loop].pin;
break;
default:
break;
}
}
}
gpio_config.Mode = LL_GPIO_MODE_ALTERNATE;
gpio_config.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
gpio_config.Pull = LL_GPIO_PULL_NO;
gpio_config.Alternate = LL_GPIO_AF_6;
gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13;
if(gpioa_pin_list != 0) {
gpio_config.Pin = gpioa_pin_list;
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOA);
LL_GPIO_Init(GPIOA, &gpio_config);
}
if(gpiob_pin_list != 0) {
gpio_config.Pin = gpiob_pin_list;
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOB);
LL_GPIO_Init(GPIOB, &gpio_config);
}
#endif
}

View File

@@ -0,0 +1,12 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void APPD_Init(void);
void APPD_EnableCPU2(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,194 @@
#include "ble_app.h"
#include <ble/ble.h>
#include <interface/patterns/ble_thread/tl/hci_tl.h>
#include <interface/patterns/ble_thread/shci/shci.h>
#include "gap.h"
#include <furi_hal.h>
#include <furi.h>
#define TAG "Bt"
#define BLE_APP_FLAG_HCI_EVENT (1UL << 0)
#define BLE_APP_FLAG_KILL_THREAD (1UL << 1)
#define BLE_APP_FLAG_ALL (BLE_APP_FLAG_HCI_EVENT | BLE_APP_FLAG_KILL_THREAD)
PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer;
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE];
_Static_assert(
sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58,
"Ble stack config structure size mismatch (check new config options - last updated for v.1.17.3)");
typedef struct {
FuriMutex* hci_mtx;
FuriSemaphore* hci_sem;
FuriThread* thread;
} BleApp;
static BleApp* ble_app = NULL;
static int32_t ble_app_hci_thread(void* context);
static void ble_app_hci_event_handler(void* pPayload);
static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status);
static const HCI_TL_HciInitConf_t hci_tl_config = {
.p_cmdbuffer = (uint8_t*)&ble_app_cmd_buffer,
.StatusNotCallBack = ble_app_hci_status_not_handler,
};
static const SHCI_C2_CONFIG_Cmd_Param_t config_param = {
.PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE,
.Config1 = SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM,
.BleNvmRamAddress = (uint32_t)ble_app_nvm,
.EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE,
};
static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = {
.Header = {{0, 0, 0}}, // Header unused
.Param = {
.pBleBufferAddress = 0, // pBleBufferAddress not used
.BleBufferSize = 0, // BleBufferSize not used
.NumAttrRecord = CFG_BLE_NUM_GATT_ATTRIBUTES,
.NumAttrServ = CFG_BLE_NUM_GATT_SERVICES,
.AttrValueArrSize = CFG_BLE_ATT_VALUE_ARRAY_SIZE,
.NumOfLinks = CFG_BLE_NUM_LINK,
.ExtendedPacketLengthEnable = CFG_BLE_DATA_LENGTH_EXTENSION,
.PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE,
.MblockCount = CFG_BLE_MBLOCK_COUNT,
.AttMtu = CFG_BLE_MAX_ATT_MTU,
.SlaveSca = CFG_BLE_SLAVE_SCA,
.MasterSca = CFG_BLE_MASTER_SCA,
.LsSource = CFG_BLE_LSE_SOURCE,
.MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH,
.HsStartupTime = CFG_BLE_HSE_STARTUP_TIME,
.ViterbiEnable = CFG_BLE_VITERBI_MODE,
.Options = CFG_BLE_OPTIONS,
.HwVersion = 0,
.max_coc_initiator_nbr = 32,
.min_tx_power = 0,
.max_tx_power = 0,
.rx_model_config = 1,
/* New stack (13.3->15.0) */
.max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set
.max_adv_data_len = 1650, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set
.tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB
.rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB
.ble_core_version = SHCI_C2_BLE_INIT_BLE_CORE_5_4,
/*15.0->17.0*/
.Options_extension = SHCI_C2_BLE_INIT_OPTIONS_ENHANCED_ATT_NOTSUPPORTED |
SHCI_C2_BLE_INIT_OPTIONS_APPEARANCE_READONLY,
}};
bool ble_app_init() {
SHCI_CmdStatus_t status;
ble_app = malloc(sizeof(BleApp));
// Allocate semafore and mutex for ble command buffer access
ble_app->hci_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
ble_app->hci_sem = furi_semaphore_alloc(1, 0);
// HCI transport layer thread to handle user asynch events
ble_app->thread = furi_thread_alloc_ex("BleHciDriver", 1024, ble_app_hci_thread, ble_app);
furi_thread_start(ble_app->thread);
// Initialize Ble Transport Layer
hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config);
// Configure NVM store for pairing data
status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param);
if(status) {
FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status);
}
// Start ble stack on 2nd core
status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet);
if(status) {
FURI_LOG_E(TAG, "Failed to start ble stack: %d", status);
}
return status == SHCI_Success;
}
void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) {
*addr = (uint8_t*)ble_app_nvm;
*size = sizeof(ble_app_nvm);
}
void ble_app_thread_stop() {
if(ble_app) {
FuriThreadId thread_id = furi_thread_get_id(ble_app->thread);
furi_assert(thread_id);
furi_thread_flags_set(thread_id, BLE_APP_FLAG_KILL_THREAD);
furi_thread_join(ble_app->thread);
furi_thread_free(ble_app->thread);
// Free resources
furi_mutex_free(ble_app->hci_mtx);
furi_semaphore_free(ble_app->hci_sem);
free(ble_app);
ble_app = NULL;
memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer));
}
}
static int32_t ble_app_hci_thread(void* arg) {
UNUSED(arg);
uint32_t flags = 0;
while(1) {
flags = furi_thread_flags_wait(BLE_APP_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
if(flags & BLE_APP_FLAG_KILL_THREAD) {
break;
}
if(flags & BLE_APP_FLAG_HCI_EVENT) {
hci_user_evt_proc();
}
}
return 0;
}
// Called by WPAN lib
void hci_notify_asynch_evt(void* pdata) {
UNUSED(pdata);
furi_check(ble_app);
FuriThreadId thread_id = furi_thread_get_id(ble_app->thread);
furi_assert(thread_id);
furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT);
}
void hci_cmd_resp_release(uint32_t flag) {
UNUSED(flag);
furi_check(ble_app);
furi_check(furi_semaphore_release(ble_app->hci_sem) == FuriStatusOk);
}
void hci_cmd_resp_wait(uint32_t timeout) {
furi_check(ble_app);
furi_check(furi_semaphore_acquire(ble_app->hci_sem, timeout) == FuriStatusOk);
}
static void ble_app_hci_event_handler(void* pPayload) {
SVCCTL_UserEvtFlowStatus_t svctl_return_status;
tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload;
furi_check(ble_app);
svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial));
if(svctl_return_status != SVCCTL_UserEvtFlowDisable) {
pParam->status = HCI_TL_UserEventFlow_Enable;
} else {
pParam->status = HCI_TL_UserEventFlow_Disable;
}
}
static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status) {
if(status == HCI_TL_CmdBusy) {
furi_hal_power_insomnia_enter();
furi_mutex_acquire(ble_app->hci_mtx, FuriWaitForever);
} else if(status == HCI_TL_CmdAvailable) {
furi_mutex_release(ble_app->hci_mtx);
furi_hal_power_insomnia_exit();
}
}
void SVCCTL_ResumeUserEventFlow(void) {
hci_resume_flow();
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
bool ble_app_init();
void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size);
void ble_app_thread_stop();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,14 @@
#pragma once
#include "app_conf.h"
/**
* There is one handler per service enabled
* Note: There is no handler for the Device Information Service
*
* This shall take into account all registered handlers
* (from either the provided services or the custom services)
*/
#define BLE_CFG_SVC_MAX_NBR_CB 7
#define BLE_CFG_CLT_MAX_NBR_CB 0

View File

@@ -0,0 +1,99 @@
#pragma once
#include <stdint.h>
#include <string.h>
#include <ble/core/ble_std.h>
#include <ble/core/ble_defs.h>
#include "osal.h"
#include "compiler.h"
/* Default BLE variant */
#ifndef BASIC_FEATURES
#define BASIC_FEATURES 0
#endif
#ifndef SLAVE_ONLY
#define SLAVE_ONLY 0
#endif
#ifndef LL_ONLY
#define LL_ONLY 0
#endif
#ifndef LL_ONLY_BASIC
#define LL_ONLY_BASIC 0
#endif
#ifndef BEACON_ONLY
#define BEACON_ONLY 0
#endif
/* Size of command/events buffers:
*
* To change the size of commands and events parameters used in the
* auto-generated files, you need to update 2 defines:
*
* - BLE_CMD_MAX_PARAM_LEN
* - BLE_EVT_MAX_PARAM_LEN
*
* These 2 defines are set below with default values and can be changed.
*
* To compute the value to support a characteristic of 512 bytes for a specific
* command or an event, you need to look in "ble_types.h".
*
* Here are 2 examples, one with a command and one with an event:
*
* - aci_gatt_update_char_value_ext_cp0
* ----------------------------------
*
* we have in the structure:
*
* uint8_t Value[(BLE_CMD_MAX_PARAM_LEN- 12)/sizeof(uint8_t)];
*
* so to support a 512 byte value, we need to have
*
* BLE_CMD_MAX_PARAM_LEN at least equal to: 512 + 12 = 524
*
* - aci_gatt_read_handle_value_rp0
* ------------------------------
*
* we have in the structure:
*
* uint8_t Value[((BLE_EVT_MAX_PARAM_LEN - 3) - 5)/sizeof(uint8_t)];
*
* so to support a 512 byte value, we need to have
*
* BLE_EVT_MAX_PARAM_LEN at least equal to: 512 + 3 + 5 = 520
*
* If you need several events or commands with 512-size values, you need to
* take the maximum values for BLE_EVT_MAX_PARAM_LEN and BLE_CMD_MAX_PARAM_LEN.
*
*/
/* Maximum parameter size of BLE commands.
* Change this value if needed. */
#define BLE_CMD_MAX_PARAM_LEN HCI_COMMAND_MAX_PARAM_LEN
/* Maximum parameter size of BLE responses/events.
* Change this value if needed. */
#define BLE_EVT_MAX_PARAM_LEN HCI_EVENT_MAX_PARAM_LEN
/* Callback function to send command and receive response */
struct hci_request {
uint16_t ogf;
uint16_t ocf;
int event;
void* cparam;
int clen;
void* rparam;
int rlen;
};
extern int hci_send_req(struct hci_request* req, uint8_t async);
#ifndef FALSE
#define FALSE 0
#endif
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -0,0 +1,481 @@
#include "ble_glue.h"
#include "app_common.h"
#include "ble_app.h"
#include <ble/ble.h>
#include <hci_tl.h>
#include <interface/patterns/ble_thread/tl/tl.h>
#include <interface/patterns/ble_thread/shci/shci.h>
#include <interface/patterns/ble_thread/tl/shci_tl.h>
#include "app_debug.h"
#include <furi_hal.h>
#define TAG "Core2"
#define BLE_GLUE_FLAG_SHCI_EVENT (1UL << 0)
#define BLE_GLUE_FLAG_KILL_THREAD (1UL << 1)
#define BLE_GLUE_FLAG_ALL (BLE_GLUE_FLAG_SHCI_EVENT | BLE_GLUE_FLAG_KILL_THREAD)
#define POOL_SIZE \
(CFG_TLBLE_EVT_QUEUE_LENGTH * 4U * \
DIVC((sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE), 4U))
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE];
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff;
PLACE_IN_SECTION("MB_MEM2")
ALIGN(4)
static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U];
PLACE_IN_SECTION("MB_MEM2")
ALIGN(4)
static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255];
typedef struct {
FuriMutex* shci_mtx;
FuriThread* thread;
BleGlueStatus status;
BleGlueKeyStorageChangedCallback callback;
BleGlueC2Info c2_info;
void* context;
} BleGlue;
static BleGlue* ble_glue = NULL;
static int32_t ble_glue_shci_thread(void* argument);
static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status);
static void ble_glue_sys_user_event_callback(void* pPayload);
void ble_glue_set_key_storage_changed_callback(
BleGlueKeyStorageChangedCallback callback,
void* context) {
furi_assert(ble_glue);
furi_assert(callback);
ble_glue->callback = callback;
ble_glue->context = context;
}
///////////////////////////////////////////////////////////////////////////////
/* TL hook to catch hardfaults */
int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) {
if(furi_hal_bt_get_hardfault_info()) {
furi_crash("ST(R) Copro(R) HardFault");
}
return TL_SYS_SendCmd(buffer, size);
}
void shci_register_io_bus(tSHciIO* fops) {
/* Register IO bus services */
fops->Init = TL_SYS_Init;
fops->Send = ble_glue_TL_SYS_SendCmd;
}
static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) {
if(furi_hal_bt_get_hardfault_info()) {
furi_crash("ST(R) Copro(R) HardFault");
}
return TL_BLE_SendCmd(buffer, size);
}
void hci_register_io_bus(tHciIO* fops) {
/* Register IO bus services */
fops->Init = TL_BLE_Init;
fops->Send = ble_glue_TL_BLE_SendCmd;
}
///////////////////////////////////////////////////////////////////////////////
void ble_glue_init() {
ble_glue = malloc(sizeof(BleGlue));
ble_glue->status = BleGlueStatusStartup;
#ifdef BLE_GLUE_DEBUG
APPD_Init();
#endif
// Initialize all transport layers
TL_MM_Config_t tl_mm_config;
SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf;
// Reference table initialization
TL_Init();
ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
// FreeRTOS system task creation
ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue);
furi_thread_start(ble_glue->thread);
// System channel initialization
SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff;
SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback;
shci_init(ble_glue_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf);
/**< Memory Manager channel initialization */
tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff;
tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff;
tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool;
tl_mm_config.AsynchEvtPoolSize = POOL_SIZE;
TL_MM_Init(&tl_mm_config);
TL_Enable();
/*
* From now, the application is waiting for the ready event ( VS_HCI_C2_Ready )
* received on the system channel before starting the Stack
* This system event is received with ble_glue_sys_user_event_callback()
*/
}
const BleGlueC2Info* ble_glue_get_c2_info() {
return &ble_glue->c2_info;
}
BleGlueStatus ble_glue_get_c2_status() {
return ble_glue->status;
}
static const char* ble_glue_get_reltype_str(const uint8_t reltype) {
static char relcode[3] = {0};
switch(reltype) {
case INFO_STACK_TYPE_BLE_FULL:
return "F";
case INFO_STACK_TYPE_BLE_HCI:
return "H";
case INFO_STACK_TYPE_BLE_LIGHT:
return "L";
case INFO_STACK_TYPE_BLE_BEACON:
return "Be";
case INFO_STACK_TYPE_BLE_BASIC:
return "Ba";
case INFO_STACK_TYPE_BLE_FULL_EXT_ADV:
return "F+";
case INFO_STACK_TYPE_BLE_HCI_EXT_ADV:
return "H+";
default:
snprintf(relcode, sizeof(relcode), "%X", reltype);
return relcode;
}
}
static void ble_glue_update_c2_fw_info() {
WirelessFwInfo_t wireless_info;
SHCI_GetWirelessFwInfo(&wireless_info);
BleGlueC2Info* local_info = &ble_glue->c2_info;
local_info->VersionMajor = wireless_info.VersionMajor;
local_info->VersionMinor = wireless_info.VersionMinor;
local_info->VersionSub = wireless_info.VersionSub;
local_info->VersionBranch = wireless_info.VersionBranch;
local_info->VersionReleaseType = wireless_info.VersionReleaseType;
local_info->MemorySizeSram2B = wireless_info.MemorySizeSram2B;
local_info->MemorySizeSram2A = wireless_info.MemorySizeSram2A;
local_info->MemorySizeSram1 = wireless_info.MemorySizeSram1;
local_info->MemorySizeFlash = wireless_info.MemorySizeFlash;
local_info->StackType = wireless_info.StackType;
snprintf(
local_info->StackTypeString,
BLE_GLUE_MAX_VERSION_STRING_LEN,
"%d.%d.%d:%s",
local_info->VersionMajor,
local_info->VersionMinor,
local_info->VersionSub,
ble_glue_get_reltype_str(local_info->StackType));
local_info->FusVersionMajor = wireless_info.FusVersionMajor;
local_info->FusVersionMinor = wireless_info.FusVersionMinor;
local_info->FusVersionSub = wireless_info.FusVersionSub;
local_info->FusMemorySizeSram2B = wireless_info.FusMemorySizeSram2B;
local_info->FusMemorySizeSram2A = wireless_info.FusMemorySizeSram2A;
local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash;
}
static void ble_glue_dump_stack_info() {
const BleGlueC2Info* c2_info = &ble_glue->c2_info;
FURI_LOG_I(
TAG,
"Core2: FUS: %d.%d.%d, mem %d/%d, flash %d pages",
c2_info->FusVersionMajor,
c2_info->FusVersionMinor,
c2_info->FusVersionSub,
c2_info->FusMemorySizeSram2B,
c2_info->FusMemorySizeSram2A,
c2_info->FusMemorySizeFlash);
FURI_LOG_I(
TAG,
"Core2: Stack: %d.%d.%d, branch %d, reltype %d, stacktype %d, flash %d pages",
c2_info->VersionMajor,
c2_info->VersionMinor,
c2_info->VersionSub,
c2_info->VersionBranch,
c2_info->VersionReleaseType,
c2_info->StackType,
c2_info->MemorySizeFlash);
}
bool ble_glue_wait_for_c2_start(int32_t timeout) {
bool started = false;
do {
started = ble_glue->status == BleGlueStatusC2Started;
if(!started) {
timeout--;
furi_delay_tick(1);
}
} while(!started && (timeout > 0));
if(started) {
FURI_LOG_I(
TAG,
"C2 boot completed, mode: %s",
ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack");
ble_glue_update_c2_fw_info();
ble_glue_dump_stack_info();
} else {
FURI_LOG_E(TAG, "C2 startup failed");
ble_glue->status = BleGlueStatusBroken;
}
return started;
}
bool ble_glue_start() {
furi_assert(ble_glue);
if(ble_glue->status != BleGlueStatusC2Started) {
return false;
}
bool ret = false;
if(ble_app_init()) {
FURI_LOG_I(TAG, "Radio stack started");
ble_glue->status = BleGlueStatusRadioStackRunning;
ret = true;
if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) {
FURI_LOG_I(TAG, "Flash activity control switched to SEM7");
} else {
FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7");
}
} else {
FURI_LOG_E(TAG, "Radio stack startup failed");
ble_glue->status = BleGlueStatusRadioStackMissing;
ble_app_thread_stop();
}
return ret;
}
bool ble_glue_is_alive() {
if(!ble_glue) {
return false;
}
return ble_glue->status >= BleGlueStatusC2Started;
}
bool ble_glue_is_radio_stack_ready() {
if(!ble_glue) {
return false;
}
return ble_glue->status == BleGlueStatusRadioStackRunning;
}
BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) {
furi_check(desired_mode > BleGlueC2ModeUnknown);
if(desired_mode == ble_glue->c2_info.mode) {
return BleGlueCommandResultOK;
}
if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) {
if((ble_glue->c2_info.VersionMajor == 0) && (ble_glue->c2_info.VersionMinor == 0)) {
FURI_LOG_W(TAG, "Stack isn't installed!");
return BleGlueCommandResultError;
}
SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs();
if(status) {
FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status);
return BleGlueCommandResultError;
}
return BleGlueCommandResultRestartPending;
}
if((ble_glue->c2_info.mode == BleGlueC2ModeStack) && (desired_mode == BleGlueC2ModeFUS)) {
SHCI_FUS_GetState_ErrorCode_t error_code = 0;
uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code);
FURI_LOG_D(TAG, "FUS state: %X, error = %x", fus_state, error_code);
if(fus_state == SHCI_FUS_CMD_NOT_SUPPORTED) {
// Second call to SHCI_C2_FUS_GetState() restarts whole MCU & boots FUS
fus_state = SHCI_C2_FUS_GetState(&error_code);
FURI_LOG_D(TAG, "FUS state#2: %X, error = %x", fus_state, error_code);
return BleGlueCommandResultRestartPending;
}
return BleGlueCommandResultOK;
}
return BleGlueCommandResultError;
}
static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) {
switch(status) {
case SHCI_TL_CmdBusy:
furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever);
break;
case SHCI_TL_CmdAvailable:
furi_mutex_release(ble_glue->shci_mtx);
break;
default:
break;
}
}
/*
* The type of the payload for a system user event is tSHCI_UserEvtRxParam
* When the system event is both :
* - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY)
* - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING)
* The buffer shall not be released
* ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable )
* When the status is not filled, the buffer is released by default
*/
static void ble_glue_sys_user_event_callback(void* pPayload) {
UNUSED(pPayload);
#ifdef BLE_GLUE_DEBUG
APPD_EnableCPU2();
#endif
TL_AsynchEvt_t* p_sys_event =
(TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload);
if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) {
FURI_LOG_I(TAG, "Core2 started");
SHCI_C2_Ready_Evt_t* p_c2_ready_evt = (SHCI_C2_Ready_Evt_t*)p_sys_event->payload;
if(p_c2_ready_evt->sysevt_ready_rsp == WIRELESS_FW_RUNNING) {
ble_glue->c2_info.mode = BleGlueC2ModeStack;
} else if(p_c2_ready_evt->sysevt_ready_rsp == FUS_FW_RUNNING) {
ble_glue->c2_info.mode = BleGlueC2ModeFUS;
}
ble_glue->status = BleGlueStatusC2Started;
} else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) {
FURI_LOG_E(TAG, "Error during initialization");
} else if(p_sys_event->subevtcode == SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE) {
SHCI_C2_BleNvmRamUpdate_Evt_t* p_sys_ble_nvm_ram_update_event =
(SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload;
if(ble_glue->callback) {
ble_glue->callback(
(uint8_t*)p_sys_ble_nvm_ram_update_event->StartAddress,
p_sys_ble_nvm_ram_update_event->Size,
ble_glue->context);
}
}
}
static void ble_glue_clear_shared_memory() {
memset(ble_glue_event_pool, 0, sizeof(ble_glue_event_pool));
memset(&ble_glue_system_cmd_buff, 0, sizeof(ble_glue_system_cmd_buff));
memset(ble_glue_system_spare_event_buff, 0, sizeof(ble_glue_system_spare_event_buff));
memset(ble_glue_ble_spare_event_buff, 0, sizeof(ble_glue_ble_spare_event_buff));
}
void ble_glue_thread_stop() {
if(ble_glue) {
FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread);
furi_assert(thread_id);
furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_KILL_THREAD);
furi_thread_join(ble_glue->thread);
furi_thread_free(ble_glue->thread);
// Free resources
furi_mutex_free(ble_glue->shci_mtx);
ble_glue_clear_shared_memory();
free(ble_glue);
ble_glue = NULL;
}
}
// Wrap functions
static int32_t ble_glue_shci_thread(void* context) {
UNUSED(context);
uint32_t flags = 0;
while(true) {
flags = furi_thread_flags_wait(BLE_GLUE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
if(flags & BLE_GLUE_FLAG_SHCI_EVENT) {
shci_user_evt_proc();
}
if(flags & BLE_GLUE_FLAG_KILL_THREAD) {
break;
}
}
return 0;
}
void shci_notify_asynch_evt(void* pdata) {
UNUSED(pdata);
if(ble_glue) {
FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread);
furi_assert(thread_id);
furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_SHCI_EVENT);
}
}
bool ble_glue_reinit_c2() {
return SHCI_C2_Reinit() == SHCI_Success;
}
BleGlueCommandResult ble_glue_fus_stack_delete() {
FURI_LOG_I(TAG, "Erasing stack");
SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete();
FURI_LOG_I(TAG, "Cmd res = %x", erase_stat);
if(erase_stat == SHCI_Success) {
return BleGlueCommandResultOperationOngoing;
}
ble_glue_fus_get_status();
return BleGlueCommandResultError;
}
BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr) {
FURI_LOG_I(TAG, "Installing stack");
SHCI_CmdStatus_t write_stat = SHCI_C2_FUS_FwUpgrade(src_addr, dst_addr);
FURI_LOG_I(TAG, "Cmd res = %x", write_stat);
if(write_stat == SHCI_Success) {
return BleGlueCommandResultOperationOngoing;
}
ble_glue_fus_get_status();
return BleGlueCommandResultError;
}
BleGlueCommandResult ble_glue_fus_get_status() {
furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS);
SHCI_FUS_GetState_ErrorCode_t error_code = 0;
uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code);
FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code);
if((error_code != 0) || (fus_state == FUS_STATE_VALUE_ERROR)) {
return BleGlueCommandResultError;
} else if(
(fus_state >= FUS_STATE_VALUE_FW_UPGRD_ONGOING) &&
(fus_state <= FUS_STATE_VALUE_SERVICE_ONGOING_END)) {
return BleGlueCommandResultOperationOngoing;
}
return BleGlueCommandResultOK;
}
BleGlueCommandResult ble_glue_fus_wait_operation() {
furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS);
while(true) {
BleGlueCommandResult fus_status = ble_glue_fus_get_status();
if(fus_status == BleGlueCommandResultOperationOngoing) {
furi_delay_ms(20);
} else if(fus_status == BleGlueCommandResultError) {
return BleGlueCommandResultError;
} else {
return BleGlueCommandResultOK;
}
}
}

View File

@@ -0,0 +1,126 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
BleGlueC2ModeUnknown = 0,
BleGlueC2ModeFUS,
BleGlueC2ModeStack,
} BleGlueC2Mode;
#define BLE_GLUE_MAX_VERSION_STRING_LEN 20
typedef struct {
BleGlueC2Mode mode;
/**
* Wireless Info
*/
uint8_t VersionMajor;
uint8_t VersionMinor;
uint8_t VersionSub;
uint8_t VersionBranch;
uint8_t VersionReleaseType;
uint8_t MemorySizeSram2B; /*< Multiple of 1K */
uint8_t MemorySizeSram2A; /*< Multiple of 1K */
uint8_t MemorySizeSram1; /*< Multiple of 1K */
uint8_t MemorySizeFlash; /*< Multiple of 4K */
uint8_t StackType;
char StackTypeString[BLE_GLUE_MAX_VERSION_STRING_LEN];
/**
* Fus Info
*/
uint8_t FusVersionMajor;
uint8_t FusVersionMinor;
uint8_t FusVersionSub;
uint8_t FusMemorySizeSram2B; /*< Multiple of 1K */
uint8_t FusMemorySizeSram2A; /*< Multiple of 1K */
uint8_t FusMemorySizeFlash; /*< Multiple of 4K */
} BleGlueC2Info;
typedef enum {
// Stage 1: core2 startup and FUS
BleGlueStatusStartup,
BleGlueStatusBroken,
BleGlueStatusC2Started,
// Stage 2: radio stack
BleGlueStatusRadioStackRunning,
BleGlueStatusRadioStackMissing
} BleGlueStatus;
typedef void (
*BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context);
/** Initialize start core2 and initialize transport */
void ble_glue_init();
/** Start Core2 Radio stack
*
* @return true on success
*/
bool ble_glue_start();
/** Is core2 alive and at least FUS is running
*
* @return true if core2 is alive
*/
bool ble_glue_is_alive();
/** Waits for C2 to reports its mode to callback
*
* @return true if it reported before reaching timeout
*/
bool ble_glue_wait_for_c2_start(int32_t timeout);
BleGlueStatus ble_glue_get_c2_status();
const BleGlueC2Info* ble_glue_get_c2_info();
/** Is core2 radio stack present and ready
*
* @return true if present and ready
*/
bool ble_glue_is_radio_stack_ready();
/** Set callback for NVM in RAM changes
*
* @param[in] callback The callback to call on NVM change
* @param context The context for callback
*/
void ble_glue_set_key_storage_changed_callback(
BleGlueKeyStorageChangedCallback callback,
void* context);
/** Stop SHCI thread */
void ble_glue_thread_stop();
bool ble_glue_reinit_c2();
typedef enum {
BleGlueCommandResultUnknown,
BleGlueCommandResultOK,
BleGlueCommandResultError,
BleGlueCommandResultRestartPending,
BleGlueCommandResultOperationOngoing,
} BleGlueCommandResult;
/** Restart MCU to launch radio stack firmware if necessary
*
* @return true on radio stack start command
*/
BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode);
BleGlueCommandResult ble_glue_fus_stack_delete();
BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr);
BleGlueCommandResult ble_glue_fus_get_status();
BleGlueCommandResult ble_glue_fus_wait_operation();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,138 @@
#pragma once
#ifndef __PACKED_STRUCT
#define __PACKED_STRUCT PACKED(struct)
#endif
#ifndef __PACKED_UNION
#define __PACKED_UNION PACKED(union)
#endif
/**
* @brief This is the section dedicated to IAR toolchain
*/
#if defined(__ICCARM__) || defined(__IAR_SYSTEMS_ASM__)
#ifndef __WEAK
#define __WEAK __weak
#endif
#define QUOTE_(a) #a
/**
* @brief PACKED
* Use the PACKED macro for variables that needs to be packed.
* Usage: PACKED(struct) myStruct_s
* PACKED(union) myStruct_s
*/
#define PACKED(decl) __packed decl
/**
* @brief SECTION
* Use the SECTION macro to assign data or code in a specific section.
* Usage: SECTION(".my_section")
*/
#define SECTION(name) _Pragma(QUOTE_(location = name))
/**
* @brief ALIGN_DEF
* Use the ALIGN_DEF macro to specify the alignment of a variable.
* Usage: ALIGN_DEF(4)
*/
#define ALIGN_DEF(v) _Pragma(QUOTE_(data_alignment = v))
/**
* @brief NO_INIT
* Use the NO_INIT macro to declare a not initialized variable.
* Usage: NO_INIT(int my_no_init_var)
* Usage: NO_INIT(uint16_t my_no_init_array[10])
*/
#define NO_INIT(var) __no_init var
/**
* @brief This is the section dedicated to GNU toolchain
*/
#else
#ifdef __GNUC__
#ifndef __WEAK
#define __WEAK __attribute__((weak))
#endif
/**
* @brief PACKED
* Use the PACKED macro for variables that needs to be packed.
* Usage: PACKED(struct) myStruct_s
* PACKED(union) myStruct_s
*/
#define PACKED(decl) decl __attribute__((packed))
/**
* @brief SECTION
* Use the SECTION macro to assign data or code in a specific section.
* Usage: SECTION(".my_section")
*/
#define SECTION(name) __attribute__((section(name)))
/**
* @brief ALIGN_DEF
* Use the ALIGN_DEF macro to specify the alignment of a variable.
* Usage: ALIGN_DEF(4)
*/
#define ALIGN_DEF(N) __attribute__((aligned(N)))
/**
* @brief NO_INIT
* Use the NO_INIT macro to declare a not initialized variable.
* Usage: NO_INIT(int my_no_init_var)
* Usage: NO_INIT(uint16_t my_no_init_array[10])
*/
#define NO_INIT(var) var __attribute__((section(".noinit")))
/**
* @brief This is the section dedicated to Keil toolchain
*/
#else
#ifdef __CC_ARM
#ifndef __WEAK
#define __WEAK __weak
#endif
/**
* @brief PACKED
* Use the PACKED macro for variables that needs to be packed.
* Usage: PACKED(struct) myStruct_s
* PACKED(union) myStruct_s
*/
#define PACKED(decl) decl __attribute__((packed))
/**
* @brief SECTION
* Use the SECTION macro to assign data or code in a specific section.
* Usage: SECTION(".my_section")
*/
#define SECTION(name) __attribute__((section(name)))
/**
* @brief ALIGN_DEF
* Use the ALIGN_DEF macro to specify the alignment of a variable.
* Usage: ALIGN_DEF(4)
*/
#define ALIGN_DEF(N) __attribute__((aligned(N)))
/**
* @brief NO_INIT
* Use the NO_INIT macro to declare a not initialized variable.
* Usage: NO_INIT(int my_no_init_var)
* Usage: NO_INIT(uint16_t my_no_init_array[10])
*/
#define NO_INIT(var) var __attribute__((section("NoInit")))
#else
#error Neither ICCARM, CC ARM nor GNUC C detected. Define your macros.
#endif
#endif
#endif

619
targets/f7/ble_glue/gap.c Normal file
View File

@@ -0,0 +1,619 @@
#include "gap.h"
#include "app_common.h"
#include <ble/ble.h>
#include <furi_hal.h>
#include <furi.h>
#define TAG "BtGap"
#define FAST_ADV_TIMEOUT 30000
#define INITIAL_ADV_TIMEOUT 60000
#define GAP_INTERVAL_TO_MS(x) (uint16_t)((x)*1.25)
typedef struct {
uint16_t gap_svc_handle;
uint16_t dev_name_char_handle;
uint16_t appearance_char_handle;
uint16_t connection_handle;
uint8_t adv_svc_uuid_len;
uint8_t adv_svc_uuid[20];
char* adv_name;
} GapSvc;
typedef struct {
GapSvc service;
GapConfig* config;
GapConnectionParams connection_params;
GapState state;
int8_t conn_rssi;
uint32_t time_rssi_sample;
FuriMutex* state_mutex;
GapEventCallback on_event_cb;
void* context;
FuriTimer* advertise_timer;
FuriThread* thread;
FuriMessageQueue* command_queue;
bool enable_adv;
} Gap;
typedef enum {
GapCommandAdvFast,
GapCommandAdvLowPower,
GapCommandAdvStop,
GapCommandKillThread,
} GapCommand;
// Identity root key
static const uint8_t gap_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
// Encryption root key
static const uint8_t gap_erk[16] =
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
static Gap* gap = NULL;
static void gap_advertise_start(GapState new_state);
static int32_t gap_app(void* context);
/** function for updating rssi informations in global Gap object
*
*/
static inline void fetch_rssi() {
uint8_t ret_rssi = 127;
if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) {
gap->conn_rssi = (int8_t)ret_rssi;
gap->time_rssi_sample = furi_get_tick();
return;
}
FURI_LOG_D(TAG, "Failed to read RSSI");
}
static void gap_verify_connection_parameters(Gap* gap) {
furi_assert(gap);
FURI_LOG_I(
TAG,
"Connection parameters: Connection Interval: %d (%d ms), Slave Latency: %d, Supervision Timeout: %d",
gap->connection_params.conn_interval,
GAP_INTERVAL_TO_MS(gap->connection_params.conn_interval),
gap->connection_params.slave_latency,
gap->connection_params.supervisor_timeout);
// Send connection parameters request update if necessary
GapConnectionParamsRequest* params = &gap->config->conn_param;
if(params->conn_int_min > gap->connection_params.conn_interval ||
params->conn_int_max < gap->connection_params.conn_interval) {
FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update");
if(aci_l2cap_connection_parameter_update_req(
gap->service.connection_handle,
params->conn_int_min,
params->conn_int_max,
gap->connection_params.slave_latency,
gap->connection_params.supervisor_timeout)) {
FURI_LOG_E(TAG, "Failed to request connection parameters update");
}
}
}
SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) {
hci_event_pckt* event_pckt;
evt_le_meta_event* meta_evt;
evt_blecore_aci* blue_evt;
hci_le_phy_update_complete_event_rp0* evt_le_phy_update_complete;
uint8_t tx_phy;
uint8_t rx_phy;
tBleStatus ret = BLE_STATUS_INVALID_PARAMS;
event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data;
if(gap) {
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
}
switch(event_pckt->evt) {
case HCI_DISCONNECTION_COMPLETE_EVT_CODE: {
hci_disconnection_complete_event_rp0* disconnection_complete_event =
(hci_disconnection_complete_event_rp0*)event_pckt->data;
if(disconnection_complete_event->Connection_Handle == gap->service.connection_handle) {
gap->service.connection_handle = 0;
gap->state = GapStateIdle;
FURI_LOG_I(
TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason);
}
// Enterprise sleep
furi_delay_us(666 + 666);
if(gap->enable_adv) {
// Restart advertising
gap_advertise_start(GapStateAdvFast);
}
GapEvent event = {.type = GapEventTypeDisconnected};
gap->on_event_cb(event, gap->context);
} break;
case HCI_LE_META_EVT_CODE:
meta_evt = (evt_le_meta_event*)event_pckt->data;
switch(meta_evt->subevent) {
case HCI_LE_CONNECTION_UPDATE_COMPLETE_SUBEVT_CODE: {
hci_le_connection_update_complete_event_rp0* event =
(hci_le_connection_update_complete_event_rp0*)meta_evt->data;
gap->connection_params.conn_interval = event->Conn_Interval;
gap->connection_params.slave_latency = event->Conn_Latency;
gap->connection_params.supervisor_timeout = event->Supervision_Timeout;
FURI_LOG_I(TAG, "Connection parameters event complete");
gap_verify_connection_parameters(gap);
// Save rssi for current connection
fetch_rssi();
break;
}
case HCI_LE_PHY_UPDATE_COMPLETE_SUBEVT_CODE:
evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data;
if(evt_le_phy_update_complete->Status) {
FURI_LOG_E(
TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status);
} else {
FURI_LOG_I(TAG, "Update PHY succeed");
}
ret = hci_le_read_phy(gap->service.connection_handle, &tx_phy, &rx_phy);
if(ret) {
FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret);
} else {
FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy);
}
break;
case HCI_LE_CONNECTION_COMPLETE_SUBEVT_CODE: {
hci_le_connection_complete_event_rp0* event =
(hci_le_connection_complete_event_rp0*)meta_evt->data;
gap->connection_params.conn_interval = event->Conn_Interval;
gap->connection_params.slave_latency = event->Conn_Latency;
gap->connection_params.supervisor_timeout = event->Supervision_Timeout;
// Stop advertising as connection completed
furi_timer_stop(gap->advertise_timer);
// Update connection status and handle
gap->state = GapStateConnected;
gap->service.connection_handle = event->Connection_Handle;
gap_verify_connection_parameters(gap);
// Save rssi for current connection
fetch_rssi();
// Start pairing by sending security request
aci_gap_slave_security_req(event->Connection_Handle);
} break;
default:
break;
}
break;
case HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE:
blue_evt = (evt_blecore_aci*)event_pckt->data;
switch(blue_evt->ecode) {
aci_gap_pairing_complete_event_rp0* pairing_complete;
case ACI_GAP_LIMITED_DISCOVERABLE_VSEVT_CODE:
FURI_LOG_I(TAG, "Limited discoverable event");
break;
case ACI_GAP_PASS_KEY_REQ_VSEVT_CODE: {
// Generate random PIN code
uint32_t pin = rand() % 999999; //-V1064
aci_gap_pass_key_resp(gap->service.connection_handle, pin);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
FURI_LOG_I(TAG, "Pass key request event. Pin: ******");
} else {
FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin);
}
GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin};
gap->on_event_cb(event, gap->context);
} break;
case ACI_ATT_EXCHANGE_MTU_RESP_VSEVT_CODE: {
aci_att_exchange_mtu_resp_event_rp0* pr = (void*)blue_evt->data;
FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU);
// Set maximum packet size given header size is 3 bytes
GapEvent event = {
.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
gap->on_event_cb(event, gap->context);
} break;
case ACI_GAP_AUTHORIZATION_REQ_VSEVT_CODE:
FURI_LOG_D(TAG, "Authorization request event");
break;
case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE:
FURI_LOG_D(TAG, "Slave security initiated");
break;
case ACI_GAP_BOND_LOST_VSEVT_CODE:
FURI_LOG_D(TAG, "Bond lost event. Start rebonding");
aci_gap_allow_rebond(gap->service.connection_handle);
break;
case ACI_GAP_ADDR_NOT_RESOLVED_VSEVT_CODE:
FURI_LOG_D(TAG, "Address not resolved event");
break;
case ACI_GAP_KEYPRESS_NOTIFICATION_VSEVT_CODE:
FURI_LOG_D(TAG, "Key press notification event");
break;
case ACI_GAP_NUMERIC_COMPARISON_VALUE_VSEVT_CODE: {
uint32_t pin =
((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value;
FURI_LOG_I(TAG, "Verify numeric comparison: %06lu", pin);
GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin};
bool result = gap->on_event_cb(event, gap->context);
aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result);
break;
}
case ACI_GAP_PAIRING_COMPLETE_VSEVT_CODE:
pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data;
if(pairing_complete->Status) {
FURI_LOG_E(
TAG,
"Pairing failed with status: %d. Terminating connection",
pairing_complete->Status);
aci_gap_terminate(gap->service.connection_handle, 5);
} else {
// Save RSSI
fetch_rssi();
FURI_LOG_I(TAG, "Pairing complete");
GapEvent event = {.type = GapEventTypeConnected};
gap->on_event_cb(event, gap->context); //-V595
}
break;
case ACI_L2CAP_CONNECTION_UPDATE_RESP_VSEVT_CODE:
FURI_LOG_D(TAG, "Procedure complete event");
break;
case ACI_L2CAP_CONNECTION_UPDATE_REQ_VSEVT_CODE: {
uint16_t result =
((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result;
if(result == 0) {
FURI_LOG_D(TAG, "Connection parameters accepted");
} else if(result == 1) {
FURI_LOG_D(TAG, "Connection parameters denied");
}
break;
}
}
default:
break;
}
if(gap) {
furi_mutex_release(gap->state_mutex);
}
return SVCCTL_UserEvtFlowEnable;
}
static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) {
if(uid_len == 2) {
gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID;
} else if(uid_len == 4) {
gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID;
} else if(uid_len == 16) {
gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST;
}
memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len);
gap->service.adv_svc_uuid_len += uid_len;
}
static void gap_init_svc(Gap* gap) {
tBleStatus status;
uint32_t srd_bd_addr[2];
// Configure mac address
aci_hal_write_config_data(
CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address);
/* Static random Address
* The two upper bits shall be set to 1
* The lowest 32bits is read from the UDN to differentiate between devices
* The RNG may be used to provide a random number on each power on
*/
srd_bd_addr[1] = 0x0000ED6E;
srd_bd_addr[0] = LL_FLASH_GetUDN();
aci_hal_write_config_data(
CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr);
// Set Identity root key used to derive LTK and CSRK
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk);
// Set Encryption root key used to derive LTK and CSRK
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk);
// Set TX Power to 0 dBm
aci_hal_set_tx_power_level(1, 0x19);
// Initialize GATT interface
aci_gatt_init();
// Initialize GAP interface
// Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME
char* name = gap->service.adv_name + 1;
aci_gap_init(
GAP_PERIPHERAL_ROLE,
0,
strlen(name),
&gap->service.gap_svc_handle,
&gap->service.dev_name_char_handle,
&gap->service.appearance_char_handle);
// Set GAP characteristics
status = aci_gatt_update_char_value(
gap->service.gap_svc_handle,
gap->service.dev_name_char_handle,
0,
strlen(name),
(uint8_t*)name);
if(status) {
FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status);
}
uint8_t gap_appearence_char_uuid[2] = {
gap->config->appearance_char & 0xff, gap->config->appearance_char >> 8};
status = aci_gatt_update_char_value(
gap->service.gap_svc_handle,
gap->service.appearance_char_handle,
0,
2,
gap_appearence_char_uuid);
if(status) {
FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status);
}
// Set default PHY
hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED);
// Set I/O capability
bool keypress_supported = false;
// New things below
uint8_t conf_mitm = CFG_MITM_PROTECTION;
uint8_t conf_used_fixed_pin = CFG_USED_FIXED_PIN;
bool conf_bonding = gap->config->bonding_mode;
if(gap->config->pairing_method == GapPairingPinCodeShow) {
aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY);
} else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) {
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);
keypress_supported = true;
} else if(gap->config->pairing_method == GapPairingNone) {
// Just works pairing method (IOS accept it, it seems android and linux doesn't)
conf_mitm = 0;
conf_used_fixed_pin = 0;
conf_bonding = false;
// if just works isn't supported, we want the numeric comparaison method
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);
keypress_supported = true;
}
// Setup authentication
aci_gap_set_authentication_requirement(
conf_bonding,
conf_mitm,
CFG_SC_SUPPORT,
keypress_supported,
CFG_ENCRYPTION_KEY_SIZE_MIN,
CFG_ENCRYPTION_KEY_SIZE_MAX,
conf_used_fixed_pin, // 0x0 for no pin
0,
CFG_IDENTITY_ADDRESS);
// Configure whitelist
aci_gap_configure_whitelist();
}
static void gap_advertise_start(GapState new_state) {
tBleStatus status;
uint16_t min_interval;
uint16_t max_interval;
if(new_state == GapStateAdvFast) {
min_interval = 0x80; // 80 ms
max_interval = 0xa0; // 100 ms
} else {
min_interval = 0x0640; // 1 s
max_interval = 0x0fa0; // 2.5 s
}
// Stop advertising timer
furi_timer_stop(gap->advertise_timer);
if((new_state == GapStateAdvLowPower) &&
((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) {
// Stop advertising
status = aci_gap_set_non_discoverable();
if(status) {
FURI_LOG_E(TAG, "set_non_discoverable failed %d", status);
} else {
FURI_LOG_D(TAG, "set_non_discoverable success");
}
}
// Configure advertising
status = aci_gap_set_discoverable(
ADV_IND,
min_interval,
max_interval,
CFG_IDENTITY_ADDRESS,
0,
strlen(gap->service.adv_name),
(uint8_t*)gap->service.adv_name,
gap->service.adv_svc_uuid_len,
gap->service.adv_svc_uuid,
0,
0);
if(status) {
FURI_LOG_E(TAG, "set_discoverable failed %d", status);
} else {
FURI_LOG_D(TAG, "set_discoverable success");
}
gap->state = new_state;
GapEvent event = {.type = GapEventTypeStartAdvertising};
gap->on_event_cb(event, gap->context);
furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT);
}
static void gap_advertise_stop() {
tBleStatus ret;
if(gap->state > GapStateIdle) {
if(gap->state == GapStateConnected) {
// Terminate connection
ret = aci_gap_terminate(gap->service.connection_handle, 0x13);
if(ret != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "terminate failed %d", ret);
} else {
FURI_LOG_D(TAG, "terminate success");
}
}
// Stop advertising
furi_timer_stop(gap->advertise_timer);
ret = aci_gap_set_non_discoverable();
if(ret != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret);
} else {
FURI_LOG_D(TAG, "set_non_discoverable success");
}
gap->state = GapStateIdle;
}
GapEvent event = {.type = GapEventTypeStopAdvertising};
gap->on_event_cb(event, gap->context);
}
void gap_start_advertising() {
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
if(gap->state == GapStateIdle) {
gap->state = GapStateStartingAdv;
FURI_LOG_I(TAG, "Start advertising");
gap->enable_adv = true;
GapCommand command = GapCommandAdvFast;
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
}
furi_mutex_release(gap->state_mutex);
}
void gap_stop_advertising() {
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
if(gap->state > GapStateIdle) {
FURI_LOG_I(TAG, "Stop advertising");
gap->enable_adv = false;
GapCommand command = GapCommandAdvStop;
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
}
furi_mutex_release(gap->state_mutex);
}
static void gap_advertise_timer_callback(void* context) {
UNUSED(context);
GapCommand command = GapCommandAdvLowPower;
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
}
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
if(!ble_glue_is_radio_stack_ready()) {
return false;
}
gap = malloc(sizeof(Gap));
gap->config = config;
// Create advertising timer
gap->advertise_timer = furi_timer_alloc(gap_advertise_timer_callback, FuriTimerTypeOnce, NULL);
// Initialization of GATT & GAP layer
gap->service.adv_name = config->adv_name;
gap_init_svc(gap);
// Initialization of the BLE Services
SVCCTL_Init();
// Initialization of the GAP state
gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
gap->state = GapStateIdle;
gap->service.connection_handle = 0xFFFF;
gap->enable_adv = true;
gap->conn_rssi = 127;
gap->time_rssi_sample = 0;
// Thread configuration
gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap);
furi_thread_start(gap->thread);
// Command queue allocation
gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand));
uint8_t adv_service_uid[2];
gap->service.adv_svc_uuid_len = 1;
adv_service_uid[0] = gap->config->adv_service_uuid & 0xff;
adv_service_uid[1] = gap->config->adv_service_uuid >> 8;
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
// Set callback
gap->on_event_cb = on_event_cb;
gap->context = context;
return true;
}
// Get RSSI
uint32_t gap_get_remote_conn_rssi(int8_t* rssi) {
if(gap && gap->state == GapStateConnected) {
fetch_rssi();
*rssi = gap->conn_rssi;
if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample;
}
return 0;
}
GapState gap_get_state() {
GapState state;
if(gap) {
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
state = gap->state;
furi_mutex_release(gap->state_mutex);
} else {
state = GapStateUninitialized;
}
return state;
}
void gap_thread_stop() {
if(gap) {
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
gap->enable_adv = false;
GapCommand command = GapCommandKillThread;
furi_message_queue_put(gap->command_queue, &command, FuriWaitForever);
furi_mutex_release(gap->state_mutex);
furi_thread_join(gap->thread);
furi_thread_free(gap->thread);
// Free resources
furi_mutex_free(gap->state_mutex);
furi_message_queue_free(gap->command_queue);
furi_timer_free(gap->advertise_timer);
free(gap);
gap = NULL;
}
}
static int32_t gap_app(void* context) {
UNUSED(context);
GapCommand command;
while(1) {
FuriStatus status = furi_message_queue_get(gap->command_queue, &command, FuriWaitForever);
if(status != FuriStatusOk) {
FURI_LOG_E(TAG, "Message queue get error: %d", status);
continue;
}
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
if(command == GapCommandKillThread) {
break;
}
if(command == GapCommandAdvFast) {
gap_advertise_start(GapStateAdvFast);
} else if(command == GapCommandAdvLowPower) {
gap_advertise_start(GapStateAdvLowPower);
} else if(command == GapCommandAdvStop) {
gap_advertise_stop();
}
furi_mutex_release(gap->state_mutex);
}
return 0;
}

88
targets/f7/ble_glue/gap.h Normal file
View File

@@ -0,0 +1,88 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <furi_hal_version.h>
#define GAP_MAC_ADDR_SIZE (6)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
GapEventTypeConnected,
GapEventTypeDisconnected,
GapEventTypeStartAdvertising,
GapEventTypeStopAdvertising,
GapEventTypePinCodeShow,
GapEventTypePinCodeVerify,
GapEventTypeUpdateMTU,
} GapEventType;
typedef union {
uint32_t pin_code;
uint16_t max_packet_size;
} GapEventData;
typedef struct {
GapEventType type;
GapEventData data;
} GapEvent;
typedef bool (*GapEventCallback)(GapEvent event, void* context);
typedef enum {
GapStateUninitialized,
GapStateIdle,
GapStateStartingAdv,
GapStateAdvFast,
GapStateAdvLowPower,
GapStateConnected,
} GapState;
typedef enum {
GapPairingNone,
GapPairingPinCodeShow,
GapPairingPinCodeVerifyYesNo,
} GapPairing;
typedef struct {
uint16_t conn_interval;
uint16_t slave_latency;
uint16_t supervisor_timeout;
} GapConnectionParams;
typedef struct {
uint16_t conn_int_min;
uint16_t conn_int_max;
uint16_t slave_latency;
uint16_t supervisor_timeout;
} GapConnectionParamsRequest;
typedef struct {
uint16_t adv_service_uuid;
uint16_t appearance_char;
bool bonding_mode;
GapPairing pairing_method;
uint8_t mac_address[GAP_MAC_ADDR_SIZE];
char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH];
GapConnectionParamsRequest conn_param;
} GapConfig;
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
void gap_start_advertising();
void gap_stop_advertising();
GapState gap_get_state();
void gap_thread_stop();
uint32_t gap_get_remote_conn_rssi(int8_t* rssi);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,81 @@
#pragma once
/******************************************************************************
* Semaphores
* THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+
*****************************************************************************/
/**
* Index of the semaphore used the prevent conflicts after standby sleep.
* Each CPUs takes this semaphore at standby wakeup until conflicting elements are restored.
*/
#define CFG_HW_PWR_STANDBY_SEMID 10
/**
* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in
* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config()
* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed.
* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be:
* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore
* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1)
* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore
* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them.
* There is no timing constraint on how long this semaphore can be kept.
*/
#define CFG_HW_THREAD_NVM_SRAM_SEMID 9
/**
* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in
* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config()
* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed.
* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be:
* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore
* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1)
* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore
* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them.
* There is no timing constraint on how long this semaphore can be kept.
*/
#define CFG_HW_BLE_NVM_SRAM_SEMID 8
/**
* Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash
* The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2
* When the CPU1 needs to either write or erase in flash, it shall first get the semaphore and release it just
* after writing a raw (64bits data) or erasing one sector.
* Once the Semaphore has been released, there shall be at least 1us before it can be taken again. This is required
* to give the opportunity to CPU2 to take it.
* On v1.4.0 and older CPU2 wireless firmware, this semaphore is unused and CPU2 is using PES bit.
* By default, CPU2 is using the PES bit to protect its timing. The CPU1 may request the CPU2 to use the semaphore
* instead of the PES bit by sending the system command SHCI_C2_SetFlashActivityControl()
*/
#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID 7
/**
* Index of the semaphore used by CPU1 to prevent the CPU2 to either write or erase data in flash
* In order to protect its timing, the CPU1 may get this semaphore to prevent the CPU2 to either
* write or erase in flash (as this will stall both CPUs)
* The PES bit shall not be used as this may stall the CPU2 in some cases.
*/
#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID 6
/**
* Index of the semaphore used to manage the CLK48 clock configuration
* When the USB is required, this semaphore shall be taken before configuring te CLK48 for USB
* and should be released after the application switch OFF the clock when the USB is not used anymore
* When using the RNG, it is good enough to use CFG_HW_RNG_SEMID to control CLK48.
* More details in AN5289
*/
#define CFG_HW_CLK48_CONFIG_SEMID 5
/* Index of the semaphore used to manage the entry Stop Mode procedure */
#define CFG_HW_ENTRY_STOP_MODE_SEMID 4
/* Index of the semaphore used to access the RCC */
#define CFG_HW_RCC_SEMID 3
/* Index of the semaphore used to access the FLASH */
#define CFG_HW_FLASH_SEMID 2
/* Index of the semaphore used to access the PKA */
#define CFG_HW_PKA_SEMID 1
/* Index of the semaphore used to access the RNG */
#define CFG_HW_RNG_SEMID 0

View File

@@ -0,0 +1,164 @@
#include "app_common.h"
#include <interface/patterns/ble_thread/tl/mbox_def.h>
#include <interface/patterns/ble_thread/hw.h>
#include <furi_hal.h>
#include <stm32wbxx_ll_ipcc.h>
#include <stm32wbxx_ll_pwr.h>
#include <hsem_map.h>
#define HW_IPCC_TX_PENDING(channel) \
((!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && \
LL_C1_IPCC_IsEnabledTransmitChannel(IPCC, channel))
#define HW_IPCC_RX_PENDING(channel) \
(LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel) && \
LL_C1_IPCC_IsEnabledReceiveChannel(IPCC, channel))
static void (*FreeBufCb)();
static void HW_IPCC_BLE_EvtHandler();
static void HW_IPCC_BLE_AclDataEvtHandler();
static void HW_IPCC_MM_FreeBufHandler();
static void HW_IPCC_SYS_CmdEvtHandler();
static void HW_IPCC_SYS_EvtHandler();
static void HW_IPCC_TRACES_EvtHandler();
void HW_IPCC_Rx_Handler() {
if(HW_IPCC_RX_PENDING(HW_IPCC_SYSTEM_EVENT_CHANNEL)) {
HW_IPCC_SYS_EvtHandler();
} else if(HW_IPCC_RX_PENDING(HW_IPCC_BLE_EVENT_CHANNEL)) {
HW_IPCC_BLE_EvtHandler();
} else if(HW_IPCC_RX_PENDING(HW_IPCC_TRACES_CHANNEL)) {
HW_IPCC_TRACES_EvtHandler();
}
}
void HW_IPCC_Tx_Handler() {
if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
HW_IPCC_SYS_CmdEvtHandler();
} else if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
HW_IPCC_SYS_CmdEvtHandler();
} else if(HW_IPCC_TX_PENDING(HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) {
HW_IPCC_MM_FreeBufHandler();
} else if(HW_IPCC_TX_PENDING(HW_IPCC_HCI_ACL_DATA_CHANNEL)) {
HW_IPCC_BLE_AclDataEvtHandler();
}
}
void HW_IPCC_Enable() {
/**
* Such as IPCC IP available to the CPU2, it is required to keep the IPCC clock running
when FUS is running on CPU2 and CPU1 enters deep sleep mode
*/
/**
* When the device is out of standby, it is required to use the EXTI mechanism to wakeup CPU2
*/
LL_C2_EXTI_EnableEvent_32_63(LL_EXTI_LINE_41);
LL_EXTI_EnableRisingTrig_32_63(LL_EXTI_LINE_41);
/**
* In case the SBSFU is implemented, it may have already set the C2BOOT bit to startup the CPU2.
* In that case, to keep the mechanism transparent to the user application, it shall call the system command
* SHCI_C2_Reinit( ) before jumping to the application.
* When the CPU2 receives that command, it waits for its event input to be set to restart the CPU2 firmware.
* This is required because once C2BOOT has been set once, a clear/set on C2BOOT has no effect.
* When SHCI_C2_Reinit( ) is not called, generating an event to the CPU2 does not have any effect
* So, by default, the application shall both set the event flag and set the C2BOOT bit.
*/
__SEV(); /* Set the internal event flag and send an event to the CPU2 */
__WFE(); /* Clear the internal event flag */
LL_PWR_EnableBootC2();
}
void HW_IPCC_Init() {
LL_C1_IPCC_EnableIT_RXO(IPCC);
LL_C1_IPCC_EnableIT_TXF(IPCC);
NVIC_SetPriority(IPCC_C1_RX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 6, 0));
NVIC_EnableIRQ(IPCC_C1_RX_IRQn);
NVIC_SetPriority(IPCC_C1_TX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 6, 0));
NVIC_EnableIRQ(IPCC_C1_TX_IRQn);
}
void HW_IPCC_BLE_Init() {
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_BLE_EVENT_CHANNEL);
}
void HW_IPCC_BLE_SendCmd() {
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_BLE_CMD_CHANNEL);
}
static void HW_IPCC_BLE_EvtHandler() {
HW_IPCC_BLE_RxEvtNot();
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_BLE_EVENT_CHANNEL);
}
void HW_IPCC_BLE_SendAclData() {
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
}
static void HW_IPCC_BLE_AclDataEvtHandler() {
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
HW_IPCC_BLE_AclDataAckNot();
}
void HW_IPCC_SYS_Init() {
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL);
}
void HW_IPCC_SYS_SendCmd() {
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL);
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(33000000);
while(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
furi_check(!furi_hal_cortex_timer_is_expired(timer), "HW_IPCC_SYS_SendCmd timeout");
}
HW_IPCC_SYS_CmdEvtHandler();
}
static void HW_IPCC_SYS_CmdEvtHandler() {
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL);
HW_IPCC_SYS_CmdEvtNot();
}
static void HW_IPCC_SYS_EvtHandler() {
HW_IPCC_SYS_EvtNot();
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL);
}
void HW_IPCC_MM_SendFreeBuf(void (*cb)()) {
if(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) {
FreeBufCb = cb;
LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
} else {
cb();
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
}
}
static void HW_IPCC_MM_FreeBufHandler() {
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
FreeBufCb();
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
}
void HW_IPCC_TRACES_Init() {
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_TRACES_CHANNEL);
}
static void HW_IPCC_TRACES_EvtHandler() {
HW_IPCC_TRACES_EvtNot();
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_TRACES_CHANNEL);
}

View File

@@ -0,0 +1,40 @@
#pragma once
/**
* This function copies size number of bytes from a
* memory location pointed by src to a destination
* memory location pointed by dest
*
* @param[in] dest Destination address
* @param[in] src Source address
* @param[in] size size in the bytes
*
* @return Address of the destination
*/
extern void* Osal_MemCpy(void* dest, const void* src, unsigned int size);
/**
* This function sets first number of bytes, specified
* by size, to the destination memory pointed by ptr
* to the specified value
*
* @param[in] ptr Destination address
* @param[in] value Value to be set
* @param[in] size Size in the bytes
*
* @return Address of the destination
*/
extern void* Osal_MemSet(void* ptr, int value, unsigned int size);
/**
* This function compares n bytes of two regions of memory
*
* @param[in] s1 First buffer to compare.
* @param[in] s2 Second buffer to compare.
* @param[in] size Number of bytes to compare.
*
* @return 0 if the two buffers are equal, 1 otherwise
*/
extern int Osal_MemCmp(const void* s1, const void* s2, unsigned int size);

View File

@@ -0,0 +1,150 @@
#include "battery_service.h"
#include "app_common.h"
#include "gatt_char.h"
#include <ble/ble.h>
#include <furi.h>
#include <furi_hal_power.h>
#define TAG "BtBatterySvc"
enum {
// Common states
BatterySvcPowerStateUnknown = 0b00,
BatterySvcPowerStateUnsupported = 0b01,
// Level states
BatterySvcPowerStateGoodLevel = 0b10,
BatterySvcPowerStateCriticallyLowLevel = 0b11,
// Charging states
BatterySvcPowerStateNotCharging = 0b10,
BatterySvcPowerStateCharging = 0b11,
// Discharging states
BatterySvcPowerStateNotDischarging = 0b10,
BatterySvcPowerStateDischarging = 0b11,
// Battery states
BatterySvcPowerStateBatteryNotPresent = 0b10,
BatterySvcPowerStateBatteryPresent = 0b11,
};
typedef struct {
uint8_t present : 2;
uint8_t discharging : 2;
uint8_t charging : 2;
uint8_t level : 2;
} BattrySvcPowerState;
_Static_assert(sizeof(BattrySvcPowerState) == 1, "Incorrect structure size");
#define BATTERY_POWER_STATE (0x2A1A)
static const uint16_t service_uuid = BATTERY_SERVICE_UUID;
typedef enum {
BatterySvcGattCharacteristicBatteryLevel = 0,
BatterySvcGattCharacteristicPowerState,
BatterySvcGattCharacteristicCount,
} BatterySvcGattCharacteristicId;
static const FlipperGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] =
{[BatterySvcGattCharacteristicBatteryLevel] =
{.name = "Battery Level",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = 1,
.uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[BatterySvcGattCharacteristicPowerState] = {
.name = "Power State",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = 1,
.uuid.Char_UUID_16 = BATTERY_POWER_STATE,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
typedef struct {
uint16_t svc_handle;
FlipperGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount];
} BatterySvc;
static BatterySvc* battery_svc = NULL;
void battery_svc_start() {
battery_svc = malloc(sizeof(BatterySvc));
tBleStatus status;
// Add Battery service
status = aci_gatt_add_service(
UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add Battery service: %d", status);
}
for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_init(
battery_svc->svc_handle, &battery_svc_chars[i], &battery_svc->chars[i]);
}
battery_svc_update_power_state();
}
void battery_svc_stop() {
tBleStatus status;
if(battery_svc) {
for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]);
}
// Delete Battery service
status = aci_gatt_del_service(battery_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status);
}
free(battery_svc);
battery_svc = NULL;
}
}
bool battery_svc_is_started() {
return battery_svc != NULL;
}
bool battery_svc_update_level(uint8_t battery_charge) {
// Check if service was started
if(battery_svc == NULL) {
return false;
}
// Update battery level characteristic
return flipper_gatt_characteristic_update(
battery_svc->svc_handle,
&battery_svc->chars[BatterySvcGattCharacteristicBatteryLevel],
&battery_charge);
}
bool battery_svc_update_power_state() {
// Check if service was started
if(battery_svc == NULL) {
return false;
}
// Update power state characteristic
BattrySvcPowerState power_state = {
.level = BatterySvcPowerStateUnsupported,
.present = BatterySvcPowerStateBatteryPresent,
};
if(furi_hal_power_is_charging()) {
power_state.charging = BatterySvcPowerStateCharging;
power_state.discharging = BatterySvcPowerStateNotDischarging;
} else {
power_state.charging = BatterySvcPowerStateNotCharging;
power_state.discharging = BatterySvcPowerStateDischarging;
}
return flipper_gatt_characteristic_update(
battery_svc->svc_handle,
&battery_svc->chars[BatterySvcGattCharacteristicPowerState],
&power_state);
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
void battery_svc_start();
void battery_svc_stop();
bool battery_svc_is_started();
bool battery_svc_update_level(uint8_t battery_level);
bool battery_svc_update_power_state();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,178 @@
#include "dev_info_service.h"
#include "app_common.h"
#include "gatt_char.h"
#include <ble/ble.h>
#include <furi.h>
#include <protobuf_version.h>
#include <lib/toolbox/version.h>
#include "dev_info_service_uuid.inc"
#define TAG "BtDevInfoSvc"
typedef enum {
DevInfoSvcGattCharacteristicMfgName = 0,
DevInfoSvcGattCharacteristicSerial,
DevInfoSvcGattCharacteristicFirmwareRev,
DevInfoSvcGattCharacteristicSoftwareRev,
DevInfoSvcGattCharacteristicRpcVersion,
DevInfoSvcGattCharacteristicCount,
} DevInfoSvcGattCharacteristicId;
#define DEVICE_INFO_HARDWARE_REV_SIZE 4
typedef struct {
uint16_t service_handle;
FlipperGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount];
FuriString* version_string;
char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE];
} DevInfoSvc;
static DevInfoSvc* dev_info_svc = NULL;
static const char dev_info_man_name[] = "Flipper Devices Inc.";
static const char dev_info_serial_num[] = "1.0";
static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION);
static bool dev_info_char_firmware_rev_callback(
const void* context,
const uint8_t** data,
uint16_t* data_len) {
const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context;
*data_len = strlen(dev_info_svc->hardware_revision);
if(data) {
*data = (const uint8_t*)&dev_info_svc->hardware_revision;
}
return false;
}
static bool dev_info_char_software_rev_callback(
const void* context,
const uint8_t** data,
uint16_t* data_len) {
const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context;
*data_len = furi_string_size(dev_info_svc->version_string);
if(data) {
*data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string);
}
return false;
}
static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCharacteristicCount] =
{[DevInfoSvcGattCharacteristicMfgName] =
{.name = "Manufacturer Name",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = sizeof(dev_info_man_name) - 1,
.data.fixed.ptr = (const uint8_t*)&dev_info_man_name,
.uuid.Char_UUID_16 = MANUFACTURER_NAME_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[DevInfoSvcGattCharacteristicSerial] =
{.name = "Serial Number",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = sizeof(dev_info_serial_num) - 1,
.data.fixed.ptr = (const uint8_t*)&dev_info_serial_num,
.uuid.Char_UUID_16 = SERIAL_NUMBER_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[DevInfoSvcGattCharacteristicFirmwareRev] =
{.name = "Firmware Revision",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.context = &dev_info_svc,
.data.callback.fn = dev_info_char_firmware_rev_callback,
.uuid.Char_UUID_16 = FIRMWARE_REVISION_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[DevInfoSvcGattCharacteristicSoftwareRev] =
{.name = "Software Revision",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.context = &dev_info_svc,
.data.callback.fn = dev_info_char_software_rev_callback,
.uuid.Char_UUID_16 = SOFTWARE_REVISION_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[DevInfoSvcGattCharacteristicRpcVersion] = {
.name = "RPC Version",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = sizeof(dev_info_rpc_version) - 1,
.data.fixed.ptr = (const uint8_t*)&dev_info_rpc_version,
.uuid.Char_UUID_128 = DEV_INVO_RPC_VERSION_UID,
.uuid_type = UUID_TYPE_128,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
void dev_info_svc_start() {
dev_info_svc = malloc(sizeof(DevInfoSvc));
dev_info_svc->version_string = furi_string_alloc_printf(
"%s %s %s %s",
version_get_githash(NULL),
version_get_version(NULL),
version_get_gitbranchnum(NULL),
version_get_builddate(NULL));
snprintf(
dev_info_svc->hardware_revision,
sizeof(dev_info_svc->hardware_revision),
"%d",
version_get_target(NULL));
tBleStatus status;
// Add Device Information Service
uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID;
status = aci_gatt_add_service(
UUID_TYPE_16,
(Service_UUID_t*)&uuid,
PRIMARY_SERVICE,
1 + 2 * DevInfoSvcGattCharacteristicCount,
&dev_info_svc->service_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status);
}
for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_init(
dev_info_svc->service_handle,
&dev_info_svc_chars[i],
&dev_info_svc->characteristics[i]);
flipper_gatt_characteristic_update(
dev_info_svc->service_handle, &dev_info_svc->characteristics[i], NULL);
}
}
void dev_info_svc_stop() {
tBleStatus status;
if(dev_info_svc) {
// Delete service characteristics
for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_delete(
dev_info_svc->service_handle, &dev_info_svc->characteristics[i]);
}
// Delete service
status = aci_gatt_del_service(dev_info_svc->service_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to delete device info service: %d", status);
}
furi_string_free(dev_info_svc->version_string);
free(dev_info_svc);
dev_info_svc = NULL;
}
}
bool dev_info_svc_is_started() {
return dev_info_svc != NULL;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
void dev_info_svc_start();
void dev_info_svc_stop();
bool dev_info_svc_is_started();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,3 @@
#define DEV_INVO_RPC_VERSION_UID \
{ 0x33, 0xa9, 0xb5, 0x3e, 0x87, 0x5d, 0x1a, 0x8e, 0xc8, 0x47, 0x5e, 0xae, 0x6d, 0x66, 0xf6, 0x03 }

View File

@@ -0,0 +1,122 @@
#include "gatt_char.h"
#include <furi.h>
#define TAG "GattChar"
#define GATT_MIN_READ_KEY_SIZE (10)
void flipper_gatt_characteristic_init(
uint16_t svc_handle,
const FlipperGattCharacteristicParams* char_descriptor,
FlipperGattCharacteristicInstance* char_instance) {
furi_assert(char_descriptor);
furi_assert(char_instance);
// Copy the descriptor to the instance, since it may point to stack memory
char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams));
memcpy(
(void*)char_instance->characteristic,
char_descriptor,
sizeof(FlipperGattCharacteristicParams));
uint16_t char_data_size = 0;
if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) {
char_data_size = char_descriptor->data.fixed.length;
} else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) {
char_descriptor->data.callback.fn(
char_descriptor->data.callback.context, NULL, &char_data_size);
}
tBleStatus status = aci_gatt_add_char(
svc_handle,
char_descriptor->uuid_type,
&char_descriptor->uuid,
char_data_size,
char_descriptor->char_properties,
char_descriptor->security_permissions,
char_descriptor->gatt_evt_mask,
GATT_MIN_READ_KEY_SIZE,
char_descriptor->is_variable,
&char_instance->handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status);
}
char_instance->descriptor_handle = 0;
if((status == 0) && char_descriptor->descriptor_params) {
uint8_t const* char_data = NULL;
const FlipperGattCharacteristicDescriptorParams* char_data_descriptor =
char_descriptor->descriptor_params;
bool release_data = char_data_descriptor->data_callback.fn(
char_data_descriptor->data_callback.context, &char_data, &char_data_size);
status = aci_gatt_add_char_desc(
svc_handle,
char_instance->handle,
char_data_descriptor->uuid_type,
&char_data_descriptor->uuid,
char_data_descriptor->max_length,
char_data_size,
char_data,
char_data_descriptor->security_permissions,
char_data_descriptor->access_permissions,
char_data_descriptor->gatt_evt_mask,
MIN_ENCRY_KEY_SIZE,
char_data_descriptor->is_variable,
&char_instance->descriptor_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status);
}
if(release_data) {
free((void*)char_data);
}
}
}
void flipper_gatt_characteristic_delete(
uint16_t svc_handle,
FlipperGattCharacteristicInstance* char_instance) {
tBleStatus status = aci_gatt_del_char(svc_handle, char_instance->handle);
if(status) {
FURI_LOG_E(
TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status);
}
free((void*)char_instance->characteristic);
}
bool flipper_gatt_characteristic_update(
uint16_t svc_handle,
FlipperGattCharacteristicInstance* char_instance,
const void* source) {
furi_assert(char_instance);
const FlipperGattCharacteristicParams* char_descriptor = char_instance->characteristic;
FURI_LOG_D(TAG, "Updating %s char", char_descriptor->name);
const uint8_t* char_data = NULL;
uint16_t char_data_size = 0;
bool release_data = false;
if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) {
char_data = char_descriptor->data.fixed.ptr;
if(source) {
char_data = (uint8_t*)source;
}
char_data_size = char_descriptor->data.fixed.length;
} else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) {
const void* context = char_descriptor->data.callback.context;
if(source) {
context = source;
}
release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size);
}
tBleStatus result = aci_gatt_update_char_value(
svc_handle, char_instance->handle, 0, char_data_size, char_data);
if(result) {
FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result);
}
if(release_data) {
free((void*)char_data);
}
return result != BLE_STATUS_SUCCESS;
}

View File

@@ -0,0 +1,96 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <ble/ble.h>
#ifdef __cplusplus
extern "C" {
#endif
// Callback signature for getting characteristic data
// Is called when characteristic is created to get max data length. Data ptr is NULL in this case
// The result is passed to aci_gatt_add_char as "Char_Value_Length"
// For updates, called with a context - see flipper_gatt_characteristic_update
// Returns true if *data ownership is transferred to the caller and will be freed
typedef bool (*cbFlipperGattCharacteristicData)(
const void* context,
const uint8_t** data,
uint16_t* data_len);
typedef enum {
FlipperGattCharacteristicDataFixed,
FlipperGattCharacteristicDataCallback,
} FlipperGattCharacteristicDataType;
typedef struct {
Char_Desc_Uuid_t uuid;
struct {
cbFlipperGattCharacteristicData fn;
const void* context;
} data_callback;
uint8_t uuid_type;
uint8_t max_length;
uint8_t security_permissions;
uint8_t access_permissions;
uint8_t gatt_evt_mask;
uint8_t is_variable;
} FlipperGattCharacteristicDescriptorParams;
typedef struct {
const char* name;
FlipperGattCharacteristicDescriptorParams* descriptor_params;
union {
struct {
const uint8_t* ptr;
uint16_t length;
} fixed;
struct {
cbFlipperGattCharacteristicData fn;
const void* context;
} callback;
} data;
Char_UUID_t uuid;
// Some packed bitfields to save space
FlipperGattCharacteristicDataType data_prop_type : 2;
uint8_t is_variable : 2;
uint8_t uuid_type : 2;
uint8_t char_properties;
uint8_t security_permissions;
uint8_t gatt_evt_mask;
} FlipperGattCharacteristicParams;
_Static_assert(
sizeof(FlipperGattCharacteristicParams) == 36,
"FlipperGattCharacteristicParams size must be 36 bytes");
typedef struct {
const FlipperGattCharacteristicParams* characteristic;
uint16_t handle;
uint16_t descriptor_handle;
} FlipperGattCharacteristicInstance;
// Initialize a characteristic instance; copies the characteristic descriptor into the instance
void flipper_gatt_characteristic_init(
uint16_t svc_handle,
const FlipperGattCharacteristicParams* char_descriptor,
FlipperGattCharacteristicInstance* char_instance);
// Delete a characteristic instance; frees the copied characteristic descriptor from the instance
void flipper_gatt_characteristic_delete(
uint16_t svc_handle,
FlipperGattCharacteristicInstance* char_instance);
// Update a characteristic instance; if source==NULL, uses the data from the characteristic
// - For fixed data, fixed.ptr is used as the source if source==NULL
// - For callback-based data, collback.context is passed as the context if source==NULL
bool flipper_gatt_characteristic_update(
uint16_t svc_handle,
FlipperGattCharacteristicInstance* char_instance,
const void* source);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,366 @@
#include "hid_service.h"
#include "app_common.h"
#include <ble/ble.h>
#include "gatt_char.h"
#include <furi.h>
#define TAG "BtHid"
typedef enum {
HidSvcGattCharacteristicProtocolMode = 0,
HidSvcGattCharacteristicReportMap,
HidSvcGattCharacteristicInfo,
HidSvcGattCharacteristicCtrlPoint,
HidSvcGattCharacteristicLed,
HidSvcGattCharacteristicCount,
} HidSvcGattCharacteristicId;
typedef struct {
uint8_t report_idx;
uint8_t report_type;
} HidSvcReportId;
static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes");
static const Service_UUID_t hid_svc_uuid = {
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
};
static bool
hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) {
const HidSvcReportId* report_id = context;
*data_len = sizeof(HidSvcReportId);
if(data) {
*data = (const uint8_t*)report_id;
}
return false;
}
typedef struct {
const void* data_ptr;
uint16_t data_len;
} HidSvcDataWrapper;
static bool
hid_svc_report_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) {
const HidSvcDataWrapper* report_data = context;
if(data) {
*data = report_data->data_ptr;
*data_len = report_data->data_len;
} else {
*data_len = HID_SVC_REPORT_MAP_MAX_LEN;
}
return false;
}
// LED Descriptor params for BadBT
static uint8_t led_desc_context_buf[2] = {HID_SVC_REPORT_COUNT + 1, 2};
static FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_led = {
.uuid_type = UUID_TYPE_16,
.uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID,
.max_length = HID_SVC_REPORT_REF_LEN,
.data_callback.fn = hid_svc_char_desc_data_callback,
.data_callback.context = led_desc_context_buf,
.security_permissions = ATTR_PERMISSION_NONE,
.access_permissions = ATTR_ACCESS_READ_WRITE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT,
};
static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteristicCount] = {
[HidSvcGattCharacteristicProtocolMode] =
{.name = "Protocol Mode",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = 1,
.uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[HidSvcGattCharacteristicReportMap] =
{.name = "Report Map",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.fn = hid_svc_report_data_callback,
.data.callback.context = NULL,
.uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_VARIABLE},
[HidSvcGattCharacteristicInfo] =
{.name = "HID Information",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = HID_SVC_INFO_LEN,
.data.fixed.ptr = NULL,
.uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[HidSvcGattCharacteristicCtrlPoint] =
{.name = "HID Control Point",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = HID_SVC_CONTROL_POINT_LEN,
.uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[HidSvcGattCharacteristicLed] =
{
.name =
"HID LED State", // LED Characteristic and descriptor for BadBT to get numlock state for altchars
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = 1,
.uuid.Char_UUID_16 = REPORT_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE |
GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,
.is_variable = CHAR_VALUE_LEN_CONSTANT,
.descriptor_params = &hid_svc_char_descr_led,
},
};
static const FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_template = {
.uuid_type = UUID_TYPE_16,
.uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID,
.max_length = HID_SVC_REPORT_REF_LEN,
.data_callback.fn = hid_svc_char_desc_data_callback,
.security_permissions = ATTR_PERMISSION_NONE,
.access_permissions = ATTR_ACCESS_READ_WRITE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT,
};
static const FlipperGattCharacteristicParams hid_svc_report_template = {
.name = "Report",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.fn = hid_svc_report_data_callback,
.data.callback.context = NULL,
.uuid.Char_UUID_16 = REPORT_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_VARIABLE,
};
typedef struct {
uint16_t svc_handle;
FlipperGattCharacteristicInstance chars[HidSvcGattCharacteristicCount];
FlipperGattCharacteristicInstance input_report_chars[HID_SVC_INPUT_REPORT_COUNT];
FlipperGattCharacteristicInstance output_report_chars[HID_SVC_OUTPUT_REPORT_COUNT];
FlipperGattCharacteristicInstance feature_report_chars[HID_SVC_FEATURE_REPORT_COUNT];
// led state
HidLedStateEventCallback led_state_event_callback;
void* led_state_ctx;
} HIDSvc;
static HIDSvc* hid_svc = NULL;
static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) {
SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
// Process modification events
ret = SVCCTL_EvtAckFlowEnable;
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
// Process notification confirmation
ret = SVCCTL_EvtAckFlowEnable;
} else if(blecore_evt->ecode == ACI_GATT_WRITE_PERMIT_REQ_VSEVT_CODE) {
// LED Characteristic and descriptor for BadBT to get numlock state for altchars
//
// Process write request
aci_gatt_write_permit_req_event_rp0* req =
(aci_gatt_write_permit_req_event_rp0*)blecore_evt->data;
furi_check(hid_svc->led_state_event_callback && hid_svc->led_state_ctx);
// this check is likely to be incorrect, it will actually work in our case
// but we need to investigate gatt api to see what is the rules
// that specify attibute handle value from char handle (or the reverse)
if(req->Attribute_Handle == (hid_svc->chars[HidSvcGattCharacteristicLed].handle + 1)) {
hid_svc->led_state_event_callback(req->Data[0], hid_svc->led_state_ctx);
aci_gatt_write_resp(
req->Connection_Handle,
req->Attribute_Handle,
0x00, /* write_status = 0 (no error))*/
0x00, /* err_code */
req->Data_Length,
req->Data);
aci_gatt_write_char_value(
req->Connection_Handle,
hid_svc->chars[HidSvcGattCharacteristicLed].handle,
req->Data_Length,
req->Data);
ret = SVCCTL_EvtAckFlowEnable;
}
}
}
return ret;
}
void hid_svc_start() {
tBleStatus status;
hid_svc = malloc(sizeof(HIDSvc));
// Register event handler
SVCCTL_RegisterSvcHandler(hid_svc_event_handler);
/**
* Add Human Interface Device Service
*/
status = aci_gatt_add_service(
UUID_TYPE_16,
&hid_svc_uuid,
PRIMARY_SERVICE,
2 + /* protocol mode */
(4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) +
(3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + 2 +
4, /* Service + Report Map + HID Information + HID Control Point + LED state */
&hid_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add HID service: %d", status);
}
for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_init(
hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]);
}
uint8_t protocol_mode = 1;
flipper_gatt_characteristic_update(
hid_svc->svc_handle,
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode],
&protocol_mode);
// reports
FlipperGattCharacteristicDescriptorParams hid_svc_char_descr;
FlipperGattCharacteristicParams report_char;
HidSvcReportId report_id;
memcpy(&hid_svc_char_descr, &hid_svc_char_descr_template, sizeof(hid_svc_char_descr));
memcpy(&report_char, &hid_svc_report_template, sizeof(report_char));
hid_svc_char_descr.data_callback.context = &report_id;
report_char.descriptor_params = &hid_svc_char_descr;
typedef struct {
uint8_t report_type;
uint8_t report_count;
FlipperGattCharacteristicInstance* chars;
} HidSvcReportCharProps;
HidSvcReportCharProps hid_report_chars[] = {
{0x01, HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
{0x02, HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
{0x03, HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
};
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
report_type_idx++) {
report_id.report_type = hid_report_chars[report_type_idx].report_type;
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
report_idx++) {
report_id.report_idx = report_idx + 1;
flipper_gatt_characteristic_init(
hid_svc->svc_handle,
&report_char,
&hid_report_chars[report_type_idx].chars[report_idx]);
}
}
}
bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) {
furi_assert(data);
furi_assert(hid_svc);
HidSvcDataWrapper report_data = {
.data_ptr = data,
.data_len = len,
};
return flipper_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data);
}
bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) {
furi_assert(data);
furi_assert(hid_svc);
furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT);
HidSvcDataWrapper report_data = {
.data_ptr = data,
.data_len = len,
};
return flipper_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
}
bool hid_svc_update_info(uint8_t* data) {
furi_assert(data);
furi_assert(hid_svc);
return flipper_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data);
}
void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context) {
furi_assert(hid_svc);
furi_assert(callback);
furi_assert(context);
hid_svc->led_state_event_callback = callback;
hid_svc->led_state_ctx = context;
}
bool hid_svc_is_started() {
return hid_svc != NULL;
}
void hid_svc_stop() {
tBleStatus status;
if(hid_svc) {
// Delete characteristics
for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]);
}
typedef struct {
uint8_t report_count;
FlipperGattCharacteristicInstance* chars;
} HidSvcReportCharProps;
HidSvcReportCharProps hid_report_chars[] = {
{HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
{HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
{HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
};
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
report_type_idx++) {
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
report_idx++) {
flipper_gatt_characteristic_delete(
hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]);
}
}
// Delete service
status = aci_gatt_del_service(hid_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to delete HID service: %d", status);
}
free(hid_svc);
hid_svc = NULL;
}
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define HID_SVC_REPORT_MAP_MAX_LEN (255)
#define HID_SVC_REPORT_MAX_LEN (255)
#define HID_SVC_REPORT_REF_LEN (2)
#define HID_SVC_INFO_LEN (4)
#define HID_SVC_CONTROL_POINT_LEN (1)
#define HID_SVC_INPUT_REPORT_COUNT (3)
#define HID_SVC_OUTPUT_REPORT_COUNT (0)
#define HID_SVC_FEATURE_REPORT_COUNT (0)
#define HID_SVC_REPORT_COUNT \
(HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT)
typedef uint16_t (*HidLedStateEventCallback)(uint8_t state, void* ctx);
void hid_svc_start();
void hid_svc_stop();
bool hid_svc_is_started();
bool hid_svc_update_report_map(const uint8_t* data, uint16_t len);
bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len);
// Expects data to be of length HID_SVC_INFO_LEN (4 bytes)
bool hid_svc_update_info(uint8_t* data);
void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context);

View File

@@ -0,0 +1,262 @@
#include "serial_service.h"
#include "app_common.h"
#include <ble/ble.h>
#include "gatt_char.h"
#include <furi.h>
#include "serial_service_uuid.inc"
#define TAG "BtSerialSvc"
typedef enum {
SerialSvcGattCharacteristicRx = 0,
SerialSvcGattCharacteristicTx,
SerialSvcGattCharacteristicFlowCtrl,
SerialSvcGattCharacteristicStatus,
SerialSvcGattCharacteristicCount,
} SerialSvcGattCharacteristicId;
static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = {
[SerialSvcGattCharacteristicRx] =
{.name = "RX",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = SERIAL_SVC_DATA_LEN_MAX,
.uuid.Char_UUID_128 = SERIAL_SVC_RX_CHAR_UUID,
.uuid_type = UUID_TYPE_128,
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_VARIABLE},
[SerialSvcGattCharacteristicTx] =
{.name = "TX",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = SERIAL_SVC_DATA_LEN_MAX,
.uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID,
.uuid_type = UUID_TYPE_128,
.char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_VARIABLE},
[SerialSvcGattCharacteristicFlowCtrl] =
{.name = "Flow control",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = sizeof(uint32_t),
.uuid.Char_UUID_128 = SERIAL_SVC_FLOW_CONTROL_UUID,
.uuid_type = UUID_TYPE_128,
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[SerialSvcGattCharacteristicStatus] = {
.name = "RPC status",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = sizeof(SerialServiceRpcStatus),
.uuid.Char_UUID_128 = SERIAL_SVC_RPC_STATUS_UUID,
.uuid_type = UUID_TYPE_128,
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
typedef struct {
uint16_t svc_handle;
FlipperGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount];
FuriMutex* buff_size_mtx;
uint32_t buff_size;
uint16_t bytes_ready_to_receive;
SerialServiceEventCallback callback;
void* context;
} SerialSvc;
static SerialSvc* serial_svc = NULL;
static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) {
SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
aci_gatt_attribute_modified_event_rp0* attribute_modified;
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data;
if(attribute_modified->Attr_Handle ==
serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 2) {
// Descriptor handle
ret = SVCCTL_EvtAckFlowEnable;
FURI_LOG_D(TAG, "RX descriptor event");
} else if(
attribute_modified->Attr_Handle ==
serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 1) {
FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length);
if(serial_svc->callback) {
furi_check(
furi_mutex_acquire(serial_svc->buff_size_mtx, FuriWaitForever) ==
FuriStatusOk);
if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) {
FURI_LOG_W(
TAG,
"Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!",
attribute_modified->Attr_Data_Length,
serial_svc->bytes_ready_to_receive);
}
serial_svc->bytes_ready_to_receive -= MIN(
serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length);
SerialServiceEvent event = {
.event = SerialServiceEventTypeDataReceived,
.data = {
.buffer = attribute_modified->Attr_Data,
.size = attribute_modified->Attr_Data_Length,
}};
uint32_t buff_free_size = serial_svc->callback(event, serial_svc->context);
FURI_LOG_D(TAG, "Available buff size: %ld", buff_free_size);
furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk);
}
ret = SVCCTL_EvtAckFlowEnable;
} else if(
attribute_modified->Attr_Handle ==
serial_svc->chars[SerialSvcGattCharacteristicStatus].handle + 1) {
SerialServiceRpcStatus* rpc_status =
(SerialServiceRpcStatus*)attribute_modified->Attr_Data;
if(*rpc_status == SerialServiceRpcStatusNotActive) {
if(serial_svc->callback) {
SerialServiceEvent event = {
.event = SerialServiceEventTypesBleResetRequest,
};
serial_svc->callback(event, serial_svc->context);
}
}
}
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
FURI_LOG_T(TAG, "Ack received");
if(serial_svc->callback) {
SerialServiceEvent event = {
.event = SerialServiceEventTypeDataSent,
};
serial_svc->callback(event, serial_svc->context);
}
ret = SVCCTL_EvtAckFlowEnable;
}
}
return ret;
}
static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) {
flipper_gatt_characteristic_update(
serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicStatus], &status);
}
void serial_svc_start() {
UNUSED(serial_svc_chars);
tBleStatus status;
serial_svc = malloc(sizeof(SerialSvc));
// Register event handler
SVCCTL_RegisterSvcHandler(serial_svc_event_handler);
// Add service
status = aci_gatt_add_service(
UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add Serial service: %d", status);
}
// Add characteristics
for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_init(
serial_svc->svc_handle, &serial_svc_chars[i], &serial_svc->chars[i]);
}
serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive);
// Allocate buffer size mutex
serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
}
void serial_svc_set_callbacks(
uint16_t buff_size,
SerialServiceEventCallback callback,
void* context) {
furi_assert(serial_svc);
serial_svc->callback = callback;
serial_svc->context = context;
serial_svc->buff_size = buff_size;
serial_svc->bytes_ready_to_receive = buff_size;
uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
flipper_gatt_characteristic_update(
serial_svc->svc_handle,
&serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl],
&buff_size_reversed);
}
void serial_svc_notify_buffer_is_empty() {
furi_assert(serial_svc);
furi_assert(serial_svc->buff_size_mtx);
furi_check(furi_mutex_acquire(serial_svc->buff_size_mtx, FuriWaitForever) == FuriStatusOk);
if(serial_svc->bytes_ready_to_receive == 0) {
FURI_LOG_D(TAG, "Buffer is empty. Notifying client");
serial_svc->bytes_ready_to_receive = serial_svc->buff_size;
uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
flipper_gatt_characteristic_update(
serial_svc->svc_handle,
&serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl],
&buff_size_reversed);
}
furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk);
}
void serial_svc_stop() {
tBleStatus status;
if(serial_svc) {
for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) {
flipper_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]);
}
// Delete service
status = aci_gatt_del_service(serial_svc->svc_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status);
}
// Delete buffer size mutex
furi_mutex_free(serial_svc->buff_size_mtx);
free(serial_svc);
serial_svc = NULL;
}
}
bool serial_svc_is_started() {
return serial_svc != NULL;
}
bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) {
if(data_len > SERIAL_SVC_DATA_LEN_MAX) {
return false;
}
for(uint16_t remained = data_len; remained > 0;) {
uint8_t value_len = MIN(SERIAL_SVC_CHAR_VALUE_LEN_MAX, remained);
uint16_t value_offset = data_len - remained;
remained -= value_len;
tBleStatus result = aci_gatt_update_char_value_ext(
0,
serial_svc->svc_handle,
serial_svc->chars[SerialSvcGattCharacteristicTx].handle,
remained ? 0x00 : 0x02,
data_len,
value_offset,
value_len,
data + value_offset);
if(result) {
FURI_LOG_E(TAG, "Failed updating TX characteristic: %d", result);
return false;
}
}
return true;
}
void serial_svc_set_rpc_status(SerialServiceRpcStatus status) {
furi_assert(serial_svc);
serial_svc_update_rpc_char(status);
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define SERIAL_SVC_DATA_LEN_MAX (486)
#define SERIAL_SVC_CHAR_VALUE_LEN_MAX (243)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
SerialServiceRpcStatusNotActive = 0UL,
SerialServiceRpcStatusActive = 1UL,
} SerialServiceRpcStatus;
typedef enum {
SerialServiceEventTypeDataReceived,
SerialServiceEventTypeDataSent,
SerialServiceEventTypesBleResetRequest,
} SerialServiceEventType;
typedef struct {
uint8_t* buffer;
uint16_t size;
} SerialServiceData;
typedef struct {
SerialServiceEventType event;
SerialServiceData data;
} SerialServiceEvent;
typedef uint16_t (*SerialServiceEventCallback)(SerialServiceEvent event, void* context);
void serial_svc_start();
void serial_svc_set_callbacks(
uint16_t buff_size,
SerialServiceEventCallback callback,
void* context);
void serial_svc_set_rpc_status(SerialServiceRpcStatus status);
void serial_svc_notify_buffer_is_empty();
void serial_svc_stop();
bool serial_svc_is_started();
bool serial_svc_update_tx(uint8_t* data, uint16_t data_len);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,12 @@
static const Service_UUID_t service_uuid = { .Service_UUID_128 = \
{ 0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f }};
#define SERIAL_SVC_TX_CHAR_UUID \
{ 0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
#define SERIAL_SVC_RX_CHAR_UUID \
{ 0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
#define SERIAL_SVC_FLOW_CONTROL_UUID \
{ 0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
#define SERIAL_SVC_RPC_STATUS_UUID \
{ 0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }

View File

@@ -0,0 +1,102 @@
#pragma once
#include "app_conf.h" /* required as some configuration used in dbg_trace.h are set there */
#include "dbg_trace.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Enable or Disable traces
* The raw data output is the hci binary packet format as specified by the BT specification *
*/
#define TL_SHCI_CMD_DBG_EN 1 /* Reports System commands sent to CPU2 and the command response */
#define TL_SHCI_CMD_DBG_RAW_EN \
0 /* Reports raw data System commands sent to CPU2 and the command response */
#define TL_SHCI_EVT_DBG_EN 1 /* Reports System Asynchronous Events received from CPU2 */
#define TL_SHCI_EVT_DBG_RAW_EN \
0 /* Reports raw data System Asynchronous Events received from CPU2 */
#define TL_HCI_CMD_DBG_EN 1 /* Reports BLE command sent to CPU2 and the command response */
#define TL_HCI_CMD_DBG_RAW_EN \
0 /* Reports raw data BLE command sent to CPU2 and the command response */
#define TL_HCI_EVT_DBG_EN 1 /* Reports BLE Asynchronous Events received from CPU2 */
#define TL_HCI_EVT_DBG_RAW_EN 0 /* Reports raw data BLE Asynchronous Events received from CPU2 */
#define TL_MM_DBG_EN 1 /* Reports the informations of the buffer released to CPU2 */
/**
* System Transport Layer
*/
#if(TL_SHCI_CMD_DBG_EN != 0)
#define TL_SHCI_CMD_DBG_MSG PRINT_MESG_DBG
#define TL_SHCI_CMD_DBG_BUF PRINT_LOG_BUFF_DBG
#else
#define TL_SHCI_CMD_DBG_MSG(...)
#define TL_SHCI_CMD_DBG_BUF(...)
#endif
#if(TL_SHCI_CMD_DBG_RAW_EN != 0)
#define TL_SHCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
#else
#define TL_SHCI_CMD_DBG_RAW(...)
#endif
#if(TL_SHCI_EVT_DBG_EN != 0)
#define TL_SHCI_EVT_DBG_MSG PRINT_MESG_DBG
#define TL_SHCI_EVT_DBG_BUF PRINT_LOG_BUFF_DBG
#else
#define TL_SHCI_EVT_DBG_MSG(...)
#define TL_SHCI_EVT_DBG_BUF(...)
#endif
#if(TL_SHCI_EVT_DBG_RAW_EN != 0)
#define TL_SHCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
#else
#define TL_SHCI_EVT_DBG_RAW(...)
#endif
/**
* BLE Transport Layer
*/
#if(TL_HCI_CMD_DBG_EN != 0)
#define TL_HCI_CMD_DBG_MSG PRINT_MESG_DBG
#define TL_HCI_CMD_DBG_BUF PRINT_LOG_BUFF_DBG
#else
#define TL_HCI_CMD_DBG_MSG(...)
#define TL_HCI_CMD_DBG_BUF(...)
#endif
#if(TL_HCI_CMD_DBG_RAW_EN != 0)
#define TL_HCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
#else
#define TL_HCI_CMD_DBG_RAW(...)
#endif
#if(TL_HCI_EVT_DBG_EN != 0)
#define TL_HCI_EVT_DBG_MSG PRINT_MESG_DBG
#define TL_HCI_EVT_DBG_BUF PRINT_LOG_BUFF_DBG
#else
#define TL_HCI_EVT_DBG_MSG(...)
#define TL_HCI_EVT_DBG_BUF(...)
#endif
#if(TL_HCI_EVT_DBG_RAW_EN != 0)
#define TL_HCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
#else
#define TL_HCI_EVT_DBG_RAW(...)
#endif
/**
* Memory Manager - Released buffer tracing
*/
#if(TL_MM_DBG_EN != 0)
#define TL_MM_DBG_MSG PRINT_MESG_DBG
#else
#define TL_MM_DBG_MSG(...)
#endif
#ifdef __cplusplus
}
#endif