mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-13 07:28:36 -07:00
Merge branch 'dev' of https://github.com/flipperdevices/flipperzero-firmware into xfw-dev
This commit is contained in:
12
targets/f7/ble_glue/app_common.h
Normal file
12
targets/f7/ble_glue/app_common.h
Normal 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"
|
||||
202
targets/f7/ble_glue/app_conf.h
Normal file
202
targets/f7/ble_glue/app_conf.h
Normal 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)
|
||||
253
targets/f7/ble_glue/app_debug.c
Normal file
253
targets/f7/ble_glue/app_debug.c
Normal 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
|
||||
}
|
||||
12
targets/f7/ble_glue/app_debug.h
Normal file
12
targets/f7/ble_glue/app_debug.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void APPD_Init(void);
|
||||
void APPD_EnableCPU2(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
194
targets/f7/ble_glue/ble_app.c
Normal file
194
targets/f7/ble_glue/ble_app.c
Normal 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();
|
||||
}
|
||||
16
targets/f7/ble_glue/ble_app.h
Normal file
16
targets/f7/ble_glue/ble_app.h
Normal 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
|
||||
14
targets/f7/ble_glue/ble_conf.h
Normal file
14
targets/f7/ble_glue/ble_conf.h
Normal 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
|
||||
99
targets/f7/ble_glue/ble_const.h
Normal file
99
targets/f7/ble_glue/ble_const.h
Normal 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
|
||||
1
targets/f7/ble_glue/ble_dbg_conf.h
Normal file
1
targets/f7/ble_glue/ble_dbg_conf.h
Normal file
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
481
targets/f7/ble_glue/ble_glue.c
Normal file
481
targets/f7/ble_glue/ble_glue.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
targets/f7/ble_glue/ble_glue.h
Normal file
126
targets/f7/ble_glue/ble_glue.h
Normal 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
|
||||
138
targets/f7/ble_glue/compiler.h
Normal file
138
targets/f7/ble_glue/compiler.h
Normal 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
619
targets/f7/ble_glue/gap.c
Normal 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
88
targets/f7/ble_glue/gap.h
Normal 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
|
||||
81
targets/f7/ble_glue/hsem_map.h
Normal file
81
targets/f7/ble_glue/hsem_map.h
Normal 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
|
||||
164
targets/f7/ble_glue/hw_ipcc.c
Normal file
164
targets/f7/ble_glue/hw_ipcc.c
Normal 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);
|
||||
}
|
||||
40
targets/f7/ble_glue/osal.h
Normal file
40
targets/f7/ble_glue/osal.h
Normal 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);
|
||||
150
targets/f7/ble_glue/services/battery_service.c
Normal file
150
targets/f7/ble_glue/services/battery_service.c
Normal 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);
|
||||
}
|
||||
22
targets/f7/ble_glue/services/battery_service.h
Normal file
22
targets/f7/ble_glue/services/battery_service.h
Normal 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
|
||||
178
targets/f7/ble_glue/services/dev_info_service.c
Normal file
178
targets/f7/ble_glue/services/dev_info_service.c
Normal 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;
|
||||
}
|
||||
18
targets/f7/ble_glue/services/dev_info_service.h
Normal file
18
targets/f7/ble_glue/services/dev_info_service.h
Normal 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
|
||||
3
targets/f7/ble_glue/services/dev_info_service_uuid.inc
Normal file
3
targets/f7/ble_glue/services/dev_info_service_uuid.inc
Normal 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 }
|
||||
|
||||
122
targets/f7/ble_glue/services/gatt_char.c
Normal file
122
targets/f7/ble_glue/services/gatt_char.c
Normal 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;
|
||||
}
|
||||
96
targets/f7/ble_glue/services/gatt_char.h
Normal file
96
targets/f7/ble_glue/services/gatt_char.h
Normal 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
|
||||
366
targets/f7/ble_glue/services/hid_service.c
Normal file
366
targets/f7/ble_glue/services/hid_service.c
Normal 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;
|
||||
}
|
||||
}
|
||||
33
targets/f7/ble_glue/services/hid_service.h
Normal file
33
targets/f7/ble_glue/services/hid_service.h
Normal 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);
|
||||
262
targets/f7/ble_glue/services/serial_service.c
Normal file
262
targets/f7/ble_glue/services/serial_service.c
Normal 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);
|
||||
}
|
||||
55
targets/f7/ble_glue/services/serial_service.h
Normal file
55
targets/f7/ble_glue/services/serial_service.h
Normal 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
|
||||
12
targets/f7/ble_glue/services/serial_service_uuid.inc
Normal file
12
targets/f7/ble_glue/services/serial_service_uuid.inc
Normal 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 }
|
||||
102
targets/f7/ble_glue/tl_dbg_conf.h
Normal file
102
targets/f7/ble_glue/tl_dbg_conf.h
Normal 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
|
||||
Reference in New Issue
Block a user