Merge branch 'bugfix/nimble_hid_fixes_v5.2' into 'release/v5.2'

fix(nimble): Always read initial BAS level and forward HID report/control/protocol writes as HIDD events (v5.2)

See merge request espressif/esp-idf!48696
This commit is contained in:
Rahul Tank
2026-06-05 07:35:03 +05:30
6 changed files with 494 additions and 51 deletions
@@ -1029,6 +1029,8 @@
#ifndef MYNEWT_VAL_BLE_SM_SC_LVL
#ifdef CONFIG_BT_NIMBLE_SM_SC_LVL
#define MYNEWT_VAL_BLE_SM_SC_LVL CONFIG_BT_NIMBLE_SM_SC_LVL
#elif defined(CONFIG_BT_NIMBLE_SM_LVL)
#define MYNEWT_VAL_BLE_SM_SC_LVL CONFIG_BT_NIMBLE_SM_LVL
#else
#define MYNEWT_VAL_BLE_SM_SC_LVL (0)
#endif
+15
View File
@@ -302,6 +302,21 @@ static void attach_report_listeners(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev)
//subscribe to battery notifications
if (dev->ble.battery_handle) {
uint8_t *rdata = NULL;
uint16_t rlen = 0;
if (event_loop_handle &&
read_char(gattc_if, dev->ble.conn_id, dev->ble.battery_handle,
ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK &&
rlen >= 1 && rdata != NULL) {
esp_hidh_event_data_t p = {0};
p.battery.dev = dev;
p.battery.level = rdata[0];
esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT,
&p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
}
free(rdata);
register_for_notify(gattc_if, dev->bda, dev->ble.battery_handle);
if (dev->ble.battery_ccc_handle) {
//Write CCC descr to enable notifications
+268 -11
View File
@@ -26,6 +26,9 @@
#include "services/hid/ble_svc_hid.h"
#include "services/dis/ble_svc_dis.h"
#include "services/sps/ble_svc_sps.h"
#include <stdatomic.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if CONFIG_BT_NIMBLE_HID_SERVICE
@@ -41,6 +44,22 @@ static esp_ble_hidd_dev_t *s_dev = NULL;
Assuming the first instance of the hid service is registered first.
Increment service index as the hid services get registered */
static int service_index = -1;
static SemaphoreHandle_t s_hidd_cb_mutex = NULL;
static struct ble_gap_event_listener nimble_gap_event_listener;
static inline void hidd_cb_lock(void)
{
if (s_hidd_cb_mutex) {
xSemaphoreTake(s_hidd_cb_mutex, portMAX_DELAY);
}
}
static inline void hidd_cb_unlock(void)
{
if (s_hidd_cb_mutex) {
xSemaphoreGive(s_hidd_cb_mutex);
}
}
typedef hidd_report_item_t hidd_le_report_item_t;
@@ -75,8 +94,13 @@ struct esp_ble_hidd_dev_s {
uint8_t pnp[7]; /* something related to device info service */
hidd_dev_map_t *devices;
uint8_t devices_len;
atomic_uint_fast32_t event_loop_ref; /* refcount for in-flight posts */
};
static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id,
const uint8_t *data, uint16_t len);
static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value);
// HID Information characteristic value
static const uint8_t hidInfo[4] = {
0x11, 0x01, // bcdHID (USB HID version)
@@ -287,8 +311,22 @@ static int ble_hid_free_config(esp_ble_hidd_dev_t *dev)
return ESP_OK;
}
static void nimble_hidd_stop_event_ack_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
(void)event_base;
(void)event_id;
(void)event_data;
SemaphoreHandle_t sem = (SemaphoreHandle_t)arg;
if (sem != NULL) {
xSemaphoreGive(sem);
}
}
static int nimble_hidd_dev_deinit(void *devp) {
esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
SemaphoreHandle_t stop_event_sem = NULL;
esp_err_t reg_rc = ESP_FAIL;
if (!s_dev) {
ESP_LOGE(TAG, "HID device profile already uninitialized");
return ESP_OK;
@@ -298,11 +336,57 @@ static int nimble_hidd_dev_deinit(void *devp) {
ESP_LOGE(TAG, "Wrong HID device provided");
return ESP_FAIL;
}
hidd_cb_lock();
nimble_hid_stop_gatts(dev);
ble_svc_hid_register_report_write_cb(NULL);
ble_svc_hid_register_char_write_cb(NULL);
/* unregister GAP event listener to prevent callbacks accessing s_dev
after we clear it; clear NimBLE host callbacks too for extra safety */
ble_gap_event_listener_unregister(&nimble_gap_event_listener);
ble_hs_cfg.sync_cb = NULL;
ble_hs_cfg.gatts_register_cb = NULL;
s_dev = NULL;
service_index = -1; // resetting the value
hidd_cb_unlock();
stop_event_sem = xSemaphoreCreateBinary();
if (stop_event_sem != NULL) {
reg_rc = esp_event_handler_register_with(dev->event_loop_handle, ESP_HIDD_EVENTS,
ESP_HIDD_STOP_EVENT,
nimble_hidd_stop_event_ack_handler,
stop_event_sem);
}
nimble_hid_stop_gatts(dev);
esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY);
if (reg_rc == ESP_OK) {
xSemaphoreTake(stop_event_sem, pdMS_TO_TICKS(100));
esp_event_handler_unregister_with(dev->event_loop_handle, ESP_HIDD_EVENTS,
ESP_HIDD_STOP_EVENT, nimble_hidd_stop_event_ack_handler);
}
if (stop_event_sem != NULL) {
vSemaphoreDelete(stop_event_sem);
}
/* Wait for any in-flight event posts to complete before deleting the event loop.
Callbacks increment event_loop_ref while holding hidd_cb_lock, so once s_dev
is set to NULL no new increments will occur. Wait up to 1000ms for refcount
to drain to zero to avoid use-after-free; proceed after timeout with a warn. */
{
int waited_ms = 0;
const int max_wait_ms = 1000;
while (atomic_load(&dev->event_loop_ref) != 0 && waited_ms < max_wait_ms) {
vTaskDelay(pdMS_TO_TICKS(10));
waited_ms += 10;
}
if (atomic_load(&dev->event_loop_ref) != 0) {
ESP_LOGW(TAG, "Event loop posts still in flight (%u); leaving resources allocated to avoid UAF",
(unsigned)atomic_load(&dev->event_loop_ref));
return ESP_FAIL;
}
}
ble_hid_free_config(dev);
free(dev);
return ESP_OK;
@@ -362,12 +446,163 @@ static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t usage, uint8
return NULL;
}
static hidd_le_report_item_t *find_report_by_handle(uint16_t attr_handle, uint8_t *map_index)
{
for (uint8_t d = 0; d < s_dev->devices_len; d++) {
for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) {
hidd_le_report_item_t *rpt = &s_dev->devices[d].reports[i];
if (rpt->handle == attr_handle) {
if (map_index) {
*map_index = d;
}
return rpt;
}
}
}
return NULL;
}
static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id,
const uint8_t *data, uint16_t len)
{
esp_hidd_event_data_t *p_cb_param = NULL;
size_t event_data_size;
uint8_t map_index = 0;
hidd_le_report_item_t *match = NULL;
esp_event_loop_handle_t event_loop = NULL;
esp_ble_hidd_dev_t *dev_handle = NULL;
esp_hidd_dev_t *dev = NULL;
uint8_t usage = 0;
hidd_cb_lock();
if (s_dev == NULL || s_dev->event_loop_handle == NULL || data == NULL || len == 0) {
hidd_cb_unlock();
return;
}
if (report_type != ESP_HID_REPORT_TYPE_OUTPUT && report_type != ESP_HID_REPORT_TYPE_FEATURE) {
hidd_cb_unlock();
return;
}
match = find_report_by_handle(attr_handle, &map_index);
if (match == NULL) {
hidd_cb_unlock();
return;
}
event_data_size = sizeof(esp_hidd_event_data_t) + len;
p_cb_param = (esp_hidd_event_data_t *)calloc(1, event_data_size);
if (p_cb_param == NULL) {
ESP_LOGE(TAG, "%s malloc event data failed!", __func__);
hidd_cb_unlock();
return;
}
memcpy(((uint8_t *)p_cb_param) + sizeof(esp_hidd_event_data_t), data, len);
/* copy out data we need while holding lock and bump refcount */
dev_handle = s_dev;
atomic_fetch_add(&dev_handle->event_loop_ref, 1);
event_loop = dev_handle->event_loop_handle;
dev = dev_handle->dev;
usage = match->usage;
hidd_cb_unlock();
if (report_type == ESP_HID_REPORT_TYPE_OUTPUT) {
p_cb_param->output.dev = dev;
p_cb_param->output.usage = usage;
p_cb_param->output.report_id = report_id;
p_cb_param->output.length = len;
p_cb_param->output.data = (uint8_t *)data; /* fixed by esp_hidd_process_event_data_handler */
p_cb_param->output.map_index = map_index;
esp_event_post_to(event_loop, ESP_HIDD_EVENTS, ESP_HIDD_OUTPUT_EVENT,
p_cb_param, event_data_size, portMAX_DELAY);
atomic_fetch_sub(&dev_handle->event_loop_ref, 1);
} else {
p_cb_param->feature.dev = dev;
p_cb_param->feature.usage = usage;
p_cb_param->feature.report_id = report_id;
p_cb_param->feature.length = len;
p_cb_param->feature.data = (uint8_t *)data; /* fixed by esp_hidd_process_event_data_handler */
p_cb_param->feature.map_index = map_index;
esp_event_post_to(event_loop, ESP_HIDD_EVENTS, ESP_HIDD_FEATURE_EVENT,
p_cb_param, event_data_size, portMAX_DELAY);
atomic_fetch_sub(&dev_handle->event_loop_ref, 1);
}
free(p_cb_param);
}
static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value)
{
uint8_t map_index = 0;
bool found = false;
esp_hidd_event_data_t cb_param = {0};
esp_event_loop_handle_t event_loop = NULL;
esp_ble_hidd_dev_t *dev_handle = NULL;
hidd_cb_lock();
if (s_dev == NULL || s_dev->event_loop_handle == NULL) {
hidd_cb_unlock();
return;
}
for (uint8_t d = 0; d < s_dev->devices_len; d++) {
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE &&
s_dev->devices[d].hid_protocol_handle == attr_handle) {
found = true;
map_index = d;
break;
}
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT &&
s_dev->devices[d].hid_control_handle == attr_handle) {
found = true;
map_index = d;
break;
}
}
if (!found) {
hidd_cb_unlock();
return;
}
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) {
s_dev->protocol = value;
cb_param.protocol_mode.dev = s_dev->dev;
cb_param.protocol_mode.protocol_mode = value;
cb_param.protocol_mode.map_index = map_index;
/* bump refcount while holding lock */
dev_handle = s_dev;
atomic_fetch_add(&dev_handle->event_loop_ref, 1);
event_loop = dev_handle->event_loop_handle;
hidd_cb_unlock();
esp_event_post_to(event_loop, ESP_HIDD_EVENTS, ESP_HIDD_PROTOCOL_MODE_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
atomic_fetch_sub(&dev_handle->event_loop_ref, 1);
} else if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) {
s_dev->control = value;
cb_param.control.dev = s_dev->dev;
cb_param.control.control = value;
cb_param.control.map_index = map_index;
/* bump refcount while holding lock */
dev_handle = s_dev;
atomic_fetch_add(&dev_handle->event_loop_ref, 1);
event_loop = dev_handle->event_loop_handle;
hidd_cb_unlock();
esp_event_post_to(event_loop, ESP_HIDD_EVENTS, ESP_HIDD_CONTROL_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
atomic_fetch_sub(&dev_handle->event_loop_ref, 1);
} else {
hidd_cb_unlock();
}
}
static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length)
{
hidd_le_report_item_t *p_rpt;
esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
int rc;
struct os_mbuf *om;
struct os_mbuf *om = NULL;
if (!dev || s_dev != dev) {
return ESP_FAIL;
}
@@ -386,6 +621,7 @@ static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_
}
rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL);
if(rc != 0) {
os_mbuf_free_chain(om);
return ESP_FAIL;
}
/* free the mbuf */
@@ -413,7 +649,7 @@ static int nimble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint
hidd_le_report_item_t *p_rpt;
esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
int rc;
struct os_mbuf *om;
struct os_mbuf *om = NULL;
if (!dev || s_dev != dev) {
return ESP_FAIL;
}
@@ -432,6 +668,7 @@ static int nimble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint
}
rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL);
if(rc != 0) {
os_mbuf_free_chain(om);
return ESP_FAIL;
}
/* free the mbuf */
@@ -481,7 +718,7 @@ static void ble_hidd_dev_free(void)
static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
struct os_mbuf *om;
struct os_mbuf *om = NULL;
uint8_t data;
int rc;
@@ -504,12 +741,14 @@ static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg)
/* reset the protocol mode value */
data = ESP_HID_PROTOCOL_MODE_REPORT;
om = ble_hs_mbuf_from_flat(&data, 1);
if(om == NULL) {
ESP_LOGD(TAG, "No memory to allocate mbuf");
}
/* NOTE : om is freed by stack */
/* allocate a fresh mbuf for each device to avoid use-after-free */
for(int i = 0; i < s_dev->devices_len; i++) {
om = ble_hs_mbuf_from_flat(&data, 1);
if(om == NULL) {
ESP_LOGD(TAG, "No memory to allocate mbuf");
continue;
}
/* NOTE : om is freed by stack in ble_att_svr_write_local */
rc = ble_att_svr_write_local(s_dev->devices[i].hid_protocol_handle, om);
if (rc != 0) {
ESP_LOGE(TAG, "Write on Protocol Mode Failed: %d", rc);
@@ -540,7 +779,7 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
{
char buf[BLE_UUID_STR_LEN];
hidd_le_report_item_t *rpt = NULL;
struct os_mbuf *om;
struct os_mbuf *om = NULL;
uint16_t uuid16;
uint16_t report_info;
uint8_t report_type, report_id;
@@ -642,7 +881,6 @@ void nimble_host_reset(int reason)
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static struct ble_gap_event_listener nimble_gap_event_listener;
esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback)
{
int rc;
@@ -663,6 +901,13 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
s_dev->protocol = ESP_HID_PROTOCOL_MODE_REPORT;
s_dev->event_loop_handle = NULL;
s_dev->dev = dev_p;
if (s_hidd_cb_mutex == NULL) {
s_hidd_cb_mutex = xSemaphoreCreateMutex();
if (s_hidd_cb_mutex == NULL) {
ble_hidd_dev_free();
return ESP_ERR_NO_MEM;
}
}
esp_event_loop_args_t event_task_args = {
.queue_size = 5,
@@ -710,8 +955,20 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
ble_hs_cfg.reset_cb = nimble_host_reset;
ble_hs_cfg.sync_cb = nimble_host_synced;
ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb;
ble_svc_hid_register_report_write_cb(nimble_report_write_cb);
ble_svc_hid_register_char_write_cb(nimble_char_write_cb);
rc = nimble_hid_start_gatts();
if(rc != ESP_OK) {
ble_svc_hid_register_report_write_cb(NULL);
ble_svc_hid_register_char_write_cb(NULL);
/* Stop any GATT services that were initialized by nimble_hid_start_gatts */
nimble_hid_stop_gatts(s_dev);
/* Prevent NimBLE host callbacks from running against freed state */
ble_hs_cfg.sync_cb = NULL;
ble_hs_cfg.gatts_register_cb = NULL;
/* reset service index to initial state to avoid stale index on re-init */
service_index = -1;
ble_hidd_dev_free();
return rc;
}
ble_gap_event_listener_register(&nimble_gap_event_listener,
+203 -39
View File
@@ -9,6 +9,7 @@
#include "esp_hidh_private.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_hid_common.h"
@@ -17,6 +18,7 @@
#include "freertos/semphr.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "nimble/nimble_opt.h"
@@ -38,6 +40,7 @@
static const char *TAG = "NIMBLE_HIDH";
static SemaphoreHandle_t s_ble_hidh_cb_semaphore = NULL;
static SemaphoreHandle_t s_ble_hidh_op_mutex = NULL;
/* variables used for attribute discovery */
static int services_discovered;
@@ -55,12 +58,32 @@ static inline void SEND_CB(void)
xSemaphoreGive(s_ble_hidh_cb_semaphore);
}
static inline void LOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreTakeRecursive(s_ble_hidh_op_mutex, portMAX_DELAY);
}
}
static inline void UNLOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreGiveRecursive(s_ble_hidh_op_mutex);
}
}
static esp_event_loop_handle_t event_loop_handle;
static uint8_t *s_read_data_val = NULL;
static uint16_t s_read_data_len = 0;
static int s_read_status = 0;
static esp_event_handler_t s_event_callback;
struct read_ctx {
uint8_t **out_val;
uint16_t *out_len;
int status;
SemaphoreHandle_t sem;
uint8_t *data_val;
uint16_t data_len;
};
/**
* Utility function to log an array of bytes.
*/
@@ -97,66 +120,153 @@ nimble_on_read(uint16_t conn_handle,
struct ble_gatt_attr *attr,
void *arg)
{
struct read_ctx *ctx = (struct read_ctx *)arg;
int old_offset;
uint16_t chunk_len;
uint8_t *tmp;
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status,
conn_handle);
s_read_status = error->status;
switch(s_read_status) {
ctx->status = error->status;
switch(ctx->status) {
case 0:
if (attr == NULL || attr->om == NULL) {
ctx->status = BLE_HS_EINVAL;
xSemaphoreGive(ctx->sem);
return BLE_HS_EINVAL;
}
MODLOG_DFLT(DEBUG, " attr_handle=%d value=", attr->handle);
old_offset = s_read_data_len;
s_read_data_len += OS_MBUF_PKTLEN(attr->om);
s_read_data_val = realloc(s_read_data_val, s_read_data_len + 1); // 1 extra byte to store null char
ble_hs_mbuf_to_flat(attr->om, s_read_data_val + old_offset, OS_MBUF_PKTLEN(attr->om), NULL);
old_offset = ctx->data_len;
chunk_len = OS_MBUF_PKTLEN(attr->om);
tmp = realloc(ctx->data_val, old_offset + chunk_len + 1); // 1 extra byte to store null char
if (tmp == NULL) {
ctx->status = BLE_HS_ENOMEM;
free(ctx->data_val);
ctx->data_val = NULL;
ctx->data_len = 0;
xSemaphoreGive(ctx->sem);
return BLE_HS_ENOMEM;
}
ctx->data_val = tmp;
ctx->data_len = old_offset + chunk_len;
ble_hs_mbuf_to_flat(attr->om, ctx->data_val + old_offset, chunk_len, NULL);
print_mbuf(attr->om);
return 0;
case BLE_HS_EDONE:
s_read_data_val[s_read_data_len] = 0; // to insure strings are ended with \0 */
s_read_status = 0;
SEND_CB();
if (ctx->data_val == NULL) {
ctx->data_val = malloc(1);
if (ctx->data_val == NULL) {
ctx->status = BLE_HS_ENOMEM;
xSemaphoreGive(ctx->sem);
return BLE_HS_ENOMEM;
}
}
ctx->data_val[ctx->data_len] = 0; // to insure strings are ended with \0 */
ctx->status = 0;
xSemaphoreGive(ctx->sem);
return 0;
default: {
int rc = ctx->status;
xSemaphoreGive(ctx->sem);
return rc;
}
}
return 0;
}
static int read_char(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len)
{
s_read_data_val = NULL;
s_read_data_len = 0;
struct read_ctx ctx;
int rc;
if (out) {
*out = NULL;
}
if (out_len) {
*out_len = 0;
}
ctx.sem = xSemaphoreCreateBinary();
if (ctx.sem == NULL) {
return BLE_HS_ENOMEM;
}
ctx.out_val = out;
ctx.out_len = out_len;
ctx.status = 0;
ctx.data_val = NULL;
ctx.data_len = 0;
/* read long because the server may not support the large enough mtu */
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL);
LOCK_OPS();
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, &ctx);
if (rc != 0) {
ESP_LOGE(TAG, "read_char failed");
UNLOCK_OPS();
vSemaphoreDelete(ctx.sem);
return rc;
}
WAIT_CB();
if (s_read_status == 0) {
*out = s_read_data_val;
*out_len = s_read_data_len;
xSemaphoreTake(ctx.sem, portMAX_DELAY);
if (ctx.status == 0) {
if (out) {
*out = ctx.data_val;
} else {
free(ctx.data_val);
}
if (out_len) {
*out_len = ctx.data_len;
}
} else {
free(ctx.data_val);
}
return s_read_status;
vSemaphoreDelete(ctx.sem);
UNLOCK_OPS();
return ctx.status;
}
static int read_descr(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len)
{
struct read_ctx ctx;
int rc;
s_read_data_val = NULL;
s_read_data_len = 0;
if (out) {
*out = NULL;
}
if (out_len) {
*out_len = 0;
}
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL);
ctx.sem = xSemaphoreCreateBinary();
if (ctx.sem == NULL) {
return BLE_HS_ENOMEM;
}
ctx.out_val = out;
ctx.out_len = out_len;
ctx.status = 0;
ctx.data_val = NULL;
ctx.data_len = 0;
LOCK_OPS();
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, &ctx);
if (rc != 0) {
ESP_LOGE(TAG, "read_descr failed");
UNLOCK_OPS();
vSemaphoreDelete(ctx.sem);
return rc;
}
WAIT_CB();
if (s_read_status == 0) {
*out = s_read_data_val;
*out_len = s_read_data_len;
xSemaphoreTake(ctx.sem, portMAX_DELAY);
if (ctx.status == 0) {
if (out) {
*out = ctx.data_val;
} else {
free(ctx.data_val);
}
if (out_len) {
*out_len = ctx.data_len;
}
} else {
free(ctx.data_val);
}
return s_read_status;
vSemaphoreDelete(ctx.sem);
UNLOCK_OPS();
return ctx.status;
}
static int
@@ -463,14 +573,20 @@ static void read_device_services(esp_hidh_dev_t *dev)
else {
chr_end_handle = service_result[s].end_handle;
}
rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle,
chr_end_handle, desc_disced, descr_result);
WAIT_CB();
if(status != 0) {
ESP_LOGE(TAG, "failed to find discriptors for characteristic : %d",c);
assert(status == 0);
// Skip descriptor discovery if range is invalid (start >= end)
if (char_result[c].val_handle >= chr_end_handle) {
dcount = 0;
rc = ESP_OK;
} else {
rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle,
chr_end_handle, desc_disced, descr_result);
WAIT_CB();
if(status != 0) {
ESP_LOGE(TAG, "failed to find discriptors for characteristic : %d",c);
assert(status == 0);
}
dcount = dscs_discovered;
}
dcount = dscs_discovered;
if (rc == ESP_OK) {
for (uint16_t d = 0; d < dcount; d++) {
duuid = ble_uuid_u16(&descr_result[d].uuid.u);
@@ -572,12 +688,16 @@ static void register_for_notify(uint16_t conn_handle, uint16_t handle)
int rc;
value[0] = 1;
value[1] = 0;
LOCK_OPS();
rc = ble_gattc_write_flat(conn_handle, handle, value, sizeof value, on_subscribe,(void *)&conn_handle);
if (rc != 0) {
ESP_LOGE(TAG, "Error: Failed to subscribe to characteristic; "
"rc=%d\n", rc);
UNLOCK_OPS();
return;
}
WAIT_CB();
UNLOCK_OPS();
}
static int
@@ -600,13 +720,22 @@ on_write(uint16_t conn_handle,
}
static void write_char_descr(uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value)
{
ble_gattc_write_flat(conn_id, handle, value, value_len, on_write, &conn_id);
int rc;
LOCK_OPS();
rc = ble_gattc_write_flat(conn_id, handle, value, value_len, on_write, &conn_id);
if (rc != 0) {
ESP_LOGE(TAG, "Error: Failed to write descriptor; rc=%d\n", rc);
UNLOCK_OPS();
return;
}
WAIT_CB();
UNLOCK_OPS();
}
static void attach_report_listeners(esp_hidh_dev_t *dev)
{
if (dev == NULL) {
ESP_LOGE(TAG, "dev is NULL, returning");
return;
}
uint16_t ccc_data = 1;
@@ -614,10 +743,23 @@ static void attach_report_listeners(esp_hidh_dev_t *dev)
//subscribe to battery notifications
if (dev->ble.battery_handle) {
register_for_notify(dev->ble.conn_id, dev->ble.battery_handle);
uint8_t *rdata = NULL;
uint16_t rlen = 0;
if (event_loop_handle &&
read_char(dev->ble.conn_id, dev->ble.battery_handle, &rdata, &rlen) == 0 &&
rlen >= 1 && rdata != NULL) {
esp_hidh_event_data_t p = {0};
p.battery.dev = dev;
p.battery.level = rdata[0];
esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT,
&p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
}
free(rdata);
if (dev->ble.battery_ccc_handle) {
//Write CCC descr to enable notifications
write_char_descr(dev->ble.conn_id, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data);
write_char_descr(dev->ble.conn_id, dev->ble.battery_ccc_handle, sizeof(ccc_data), (uint8_t *)&ccc_data);
}
}
@@ -839,8 +981,10 @@ static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_in
}
*value_len = len;
memcpy(value, v, len);
free(v);
return ESP_OK;
}
free(v);
ESP_LOGE(TAG, "%s report %d read failed: 0x%x", esp_hid_report_type_str(report_type), report_id, s);
return ESP_FAIL;
}
@@ -933,12 +1077,27 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
if (ret != ESP_OK) {
if (event_loop_handle) {
esp_event_loop_delete(event_loop_handle);
event_loop_handle = NULL;
}
if (s_ble_hidh_cb_semaphore) {
vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL;
}
s_event_callback = NULL;
return ret;
}
s_ble_hidh_op_mutex = xSemaphoreCreateRecursiveMutex();
if (s_ble_hidh_op_mutex == NULL) {
if (event_loop_handle) {
esp_event_loop_delete(event_loop_handle);
event_loop_handle = NULL;
}
vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL;
s_event_callback = NULL;
return ESP_ERR_NO_MEM;
}
ble_hs_cfg.reset_cb = nimble_host_reset;
@@ -955,6 +1114,11 @@ esp_err_t esp_ble_hidh_deinit(void)
if (event_loop_handle) {
esp_event_loop_delete(event_loop_handle);
event_loop_handle = NULL;
}
if (s_ble_hidh_op_mutex) {
vSemaphoreDelete(s_ble_hidh_op_mutex);
s_ble_hidh_op_mutex = NULL;
}
vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL;