Merge branch 'idf/ble_audio_review_stage_1' into 'master'

fix(ble_audio): Miscellaneous fixes for ISO & LE Audio (stage 1)

See merge request espressif/esp-idf!48263
This commit is contained in:
Island
2026-05-13 10:39:28 +08:00
14 changed files with 328 additions and 71 deletions

View File

@@ -40,19 +40,26 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _rtn Number of retransmissions.
* @param _latency Maximum transport latency (in milliseconds).
* @param _pd Presentation delay (in microseconds).
*
* Backing buffers are sized to `CONFIG_BT_AUDIO_CODEC_CFG_MAX_*_SIZE` so
* the preset is safe to mutate via `esp_ble_audio_codec_cfg_*_set_*`.
*/
#define ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
_interval, _sdu, _rtn, _latency, _pd) \
static uint8_t codec_cfg_data_##_name[] = \
static uint8_t codec_cfg_data_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu); \
static uint8_t codec_cfg_meta_##_name[] = \
static uint8_t codec_cfg_meta_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context); \
static esp_ble_audio_bap_lc3_preset_t _name = \
ESP_BLE_AUDIO_BAP_LC3_PRESET(ESP_BLE_AUDIO_CODEC_CFG_LC3(codec_cfg_data_##_name, \
codec_cfg_meta_##_name), \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, \
_latency, _pd));
ESP_BLE_AUDIO_BAP_LC3_PRESET( \
ESP_BLE_AUDIO_CODEC_CFG_LC3_LEN( \
codec_cfg_data_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, \
_len, _frames_per_sdu)), \
codec_cfg_meta_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context))), \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, _latency, _pd));
/**
* @brief Helper to declare LC3 Unicast 8_1_1 codec configuration.

View File

@@ -77,6 +77,35 @@ typedef enum bt_audio_codec_cfg_target_phy esp_ble_audio_codec_cfg_
.meta = (_meta), \
})
/**
* @brief Variant of ESP_BLE_AUDIO_CODEC_CFG with explicit LVT content lengths.
*
* @param _id Codec ID.
* @param _cid Company ID.
* @param _vid Vendor ID.
* @param _data Codec Specific Data buffer in LVT format.
* @param _data_len Current LVT content length in `_data`.
* @param _meta Codec Specific Metadata buffer in LVT format.
* @param _meta_len Current LVT content length in `_meta`.
*/
#define ESP_BLE_AUDIO_CODEC_CFG_LEN(_id, _cid, _vid, _data, _data_len, _meta, _meta_len) \
((esp_ble_audio_codec_cfg_t){ \
/* Use HCI data path as default, can be overwritten by application */ \
.path_id = ESP_BLE_ISO_DATA_PATH_HCI, \
.ctlr_transcode = false, \
COND_CODE_1(IS_ENABLED(CONFIG_BT_BAP_UNICAST), \
(.target_latency = ESP_BLE_AUDIO_CODEC_CFG_TARGET_LATENCY_BALANCED, \
.target_phy = ESP_BLE_AUDIO_CODEC_CFG_TARGET_PHY_2M,), \
()) \
.id = (_id), \
.cid = (_cid), \
.vid = (_vid), \
.data_len = (_data_len), \
.data = (_data), \
.meta_len = (_meta_len), \
.meta = (_meta), \
})
/**
* @brief Helper to declare esp_ble_audio_codec_cap_t.
*
@@ -100,6 +129,23 @@ typedef enum bt_audio_codec_cfg_target_phy esp_ble_audio_codec_cfg_
.meta = (_meta), \
})
/**
* @brief Variant of ESP_BLE_AUDIO_CODEC_CAP with explicit LVT content lengths.
*/
#define ESP_BLE_AUDIO_CODEC_CAP_LEN(_id, _cid, _vid, _data, _data_len, _meta, _meta_len) \
((esp_ble_audio_codec_cap_t){ \
/* Use HCI data path as default, can be overwritten by application */ \
.path_id = ESP_BLE_ISO_DATA_PATH_HCI, \
.ctlr_transcode = false, \
.id = (_id), \
.cid = (_cid), \
.vid = (_vid), \
.data_len = (_data_len), \
.data = (_data), \
.meta_len = (_meta_len), \
.meta = (_meta), \
})
/*!< Supported sampling frequencies */
#define ESP_BLE_AUDIO_CODEC_CAP_TYPE_FREQ BT_AUDIO_CODEC_CAP_TYPE_FREQ
/*!< Supported frame durations */

View File

@@ -31,19 +31,26 @@ extern "C" {
* @param _rtn Maximum number of retransmissions.
* @param _latency Maximum latency in milliseconds.
* @param _pd Presentation delay in microseconds.
*
* Backing buffers are sized to `CONFIG_BT_AUDIO_CODEC_CFG_MAX_*_SIZE` so
* the preset is safe to mutate via `esp_ble_audio_codec_cfg_*_set_*`.
*/
#define ESP_BLE_AUDIO_GMAP_LC3_PRESET_DEFINE(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
_interval, _sdu, _rtn, _latency, _pd) \
static uint8_t codec_cfg_data_##_name[] = \
static uint8_t codec_cfg_data_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu); \
static uint8_t codec_cfg_meta_##_name[] = \
static uint8_t codec_cfg_meta_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context); \
static esp_ble_audio_bap_lc3_preset_t _name = \
ESP_BLE_AUDIO_BAP_LC3_PRESET(ESP_BLE_AUDIO_CODEC_CFG_LC3(codec_cfg_data_##_name, \
codec_cfg_meta_##_name), \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, \
_latency, _pd));
ESP_BLE_AUDIO_BAP_LC3_PRESET( \
ESP_BLE_AUDIO_CODEC_CFG_LC3_LEN( \
codec_cfg_data_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, \
_len, _frames_per_sdu)), \
codec_cfg_meta_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context))), \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, _latency, _pd));
/**
* @brief Gaming Reliable (GR) preset at 32kHz, 7.5ms frame duration.

View File

@@ -52,6 +52,13 @@ extern "C" {
#define ESP_BLE_AUDIO_CODEC_CAP_LC3(_data, _meta) \
ESP_BLE_AUDIO_CODEC_CAP(ESP_BLE_ISO_CODING_FORMAT_LC3, 0x0000, 0x0000, _data, _meta)
/**
* @brief Variant of ESP_BLE_AUDIO_CODEC_CAP_LC3 with explicit LVT content lengths.
*/
#define ESP_BLE_AUDIO_CODEC_CAP_LC3_LEN(_data, _data_len, _meta, _meta_len) \
ESP_BLE_AUDIO_CODEC_CAP_LEN(ESP_BLE_ISO_CODING_FORMAT_LC3, 0x0000, 0x0000, \
_data, _data_len, _meta, _meta_len)
/**
* @brief Helper to declare LC3 codec data configuration.
*
@@ -82,6 +89,13 @@ extern "C" {
#define ESP_BLE_AUDIO_CODEC_CFG_LC3(_data, _meta) \
ESP_BLE_AUDIO_CODEC_CFG(ESP_BLE_ISO_CODING_FORMAT_LC3, 0x0000, 0x0000, _data, _meta)
/**
* @brief Variant of ESP_BLE_AUDIO_CODEC_CFG_LC3 with explicit LVT content lengths.
*/
#define ESP_BLE_AUDIO_CODEC_CFG_LC3_LEN(_data, _data_len, _meta, _meta_len) \
ESP_BLE_AUDIO_CODEC_CFG_LEN(ESP_BLE_ISO_CODING_FORMAT_LC3, 0x0000, 0x0000, \
_data, _data_len, _meta, _meta_len)
#ifdef __cplusplus
}
#endif

View File

@@ -178,7 +178,7 @@ static const uint16_t ext_structs[] = {
sizeof(struct bt_bond_info),
};
#define LEA_VERSION (0x20260430)
#define LEA_VERSION (0x20260512)
struct lib_ext_cfgs {
/* BLE */
@@ -971,6 +971,12 @@ struct lib_ext_funcs {
void (*_log_wrn)(const char *format, ...);
void (*_log_err)(const char *format, ...);
/* Fatal assert: log + abort with tag/info/file/line/func context.
* Mirrors lib-side lib_ext_funcs._assert in init.h. ABI must match.
*/
void (*_assert)(const char *tag, size_t info,
const char *file, int line, const char *func);
/* Memory */
void *(*_malloc)(size_t size);
void *(*_calloc)(size_t n, size_t size);
@@ -1180,12 +1186,30 @@ static void log_error(const char *format, ...)
#endif /* (CONFIG_BT_AUDIO_LOG_LEVEL >= BT_ISO_LOG_ERROR) */
}
/* Fatal assert handler registered into lib_ext_funcs._assert.
* Always logged (no LOG_LEVEL gate) — this is the last message before
* abort, and the user needs the context to diagnose.
*/
static void assert_fatal(const char *tag, size_t info,
const char *file, int line, const char *func)
{
esp_log_write(ESP_LOG_ERROR, LEA_TAG,
BT_ISO_LOG_COLOR_E
"E (%lu) %s: LibAssert[%s][info=%u][%s:%d][%s]"
BT_ISO_LOG_RESET_COLOR "\n",
esp_log_timestamp(), LEA_TAG,
tag, (unsigned)info, file, line, func);
abort();
}
static const struct lib_ext_funcs ext_funcs = {
._log_dbg = (void *)log_debug,
._log_inf = (void *)log_info,
._log_wrn = (void *)log_warn,
._log_err = (void *)log_error,
._assert = (void *)assert_fatal,
._malloc = (void *)malloc,
._calloc = (void *)calloc,
._free = (void *)free,

View File

@@ -245,7 +245,7 @@ int bt_le_nimble_scan_start(const struct bt_le_scan_param *param, ble_gap_event_
scan_param.filter_duplicates = 0;
return nimble_err_to_errno(ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER,
&scan_param, cb, NULL));
&scan_param, cb, NULL));
}
int bt_le_nimble_scan_stop(void)

View File

@@ -34,10 +34,17 @@ struct gatt_nrp_node {
union {
struct {
struct bt_gatt_read_params *params;
/* read_by_uuid is multi-event (one per matching attr + EDONE);
* cb may return BT_GATT_ITER_STOP early, after which we must
* not invoke it again for trailing events on this procedure. */
bool iter_stopped;
} read_by_uuid;
struct {
struct bt_gatt_read_params *params;
/* Same multi-event story as read_by_uuid; per-fragment events
* may precede EDONE and the cb may STOP early. */
bool iter_stopped;
} read_long;
struct {
@@ -46,6 +53,13 @@ struct gatt_nrp_node {
struct {
struct bt_gatt_write_params *params;
/* Heap copy of params->data captured at insert time. Callers may pass
* a pointer to a stack-allocated buffer (e.g. NET_BUF_SIMPLE_DEFINE
* inside a helper that returns immediately after bt_gatt_write); when
* the request gets queued behind another in-flight GATT op, the
* caller's buffer is gone by the time we re-dispatch. Hold our own
* copy and dispatch from it. NULL when length == 0. */
uint8_t *data_copy;
} write_req;
struct {
@@ -53,7 +67,11 @@ struct gatt_nrp_node {
} subscribe;
struct {
/* Deep-copied at insert: callers may reuse the params slot or
* pass stack-backed data while we are queued/in-flight. */
struct bt_gatt_indicate_params *params;
struct bt_gatt_indicate_params params_copy;
uint8_t *data_copy;
} indicate;
};
@@ -70,13 +88,19 @@ static struct gatt_nrp {
},
};
static struct gatt_nrp *gatt_nrp_find(uint16_t conn_handle);
static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
struct bt_gatt_read_params *read_params;
struct gatt_nrp_node *nrp_node;
struct gatt_nrp *nrp;
struct bt_conn *conn;
sys_snode_t *node;
bool iter_stopped;
int rc = 0;
read_params = arg;
@@ -95,15 +119,21 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
rc = -ENOTCONN;
/* Note:
* Cannot remove NRP node or invoke callback without valid conn.
* Consider if bt_le_nimble_gatt_nrp_clear() should be invoked.
*/
goto end;
}
/* Shall be invoked before the read_params->func is invoked */
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_BY_UUID, read_params);
/* Peek (don't pop) the in-flight head; READ_BY_UUID delivers per-attr
* events followed by EDONE, so the head must survive across events.
* It is removed only on terminal status (EDONE / error) below. */
nrp = gatt_nrp_find(conn->handle);
assert(nrp);
node = sys_slist_peek_head(&nrp->list);
assert(node);
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
assert(nrp_node->type == GATTC_NRP_READ_BY_UUID);
assert(nrp_node->read_by_uuid.params == read_params);
iter_stopped = nrp_node->read_by_uuid.iter_stopped;
switch (error->status) {
case 0:
@@ -118,20 +148,28 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
read_params->by_uuid.start_handle = attr->handle;
}
read_params->func(conn, 0, read_params, attr->om->om_data, attr->om->om_len);
if (!iter_stopped) {
uint8_t cb_ret = read_params->func(conn, 0, read_params,
attr->om->om_data, attr->om->om_len);
if (cb_ret == BT_GATT_ITER_STOP) {
nrp_node->read_by_uuid.iter_stopped = true;
}
}
break;
case BLE_HS_EDONE:
read_params->func(conn, 0, read_params, NULL, 0);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_BY_UUID, read_params, 0);
if (!iter_stopped) {
read_params->func(conn, 0, read_params, NULL, 0);
}
break;
default:
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
if (error->status & BLE_HS_ERR_ATT_BASE) {
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_BY_UUID, read_params, 0);
if ((error->status & BLE_HS_ERR_ATT_BASE) && !iter_stopped) {
read_params->func(conn, (uint8_t)error->status, read_params, NULL, 0);
} else {
/* Note: for other errors, currently we just ignore it. */
}
rc = error->status;
@@ -150,7 +188,11 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
void *arg)
{
struct bt_gatt_read_params *read_params;
struct gatt_nrp_node *nrp_node;
struct gatt_nrp *nrp;
struct bt_conn *conn;
sys_snode_t *node;
bool iter_stopped;
int rc = 0;
read_params = arg;
@@ -170,8 +212,17 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
goto end;
}
/* Shall be invoked before the read_params->func is invoked */
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_LONG, read_params);
/* Peek (don't pop) — per-fragment events may precede EDONE; head is
* removed only on terminal status below. */
nrp = gatt_nrp_find(conn->handle);
assert(nrp);
node = sys_slist_peek_head(&nrp->list);
assert(node);
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
assert(nrp_node->type == GATTC_NRP_READ_LONG);
assert(nrp_node->read_long.params == read_params);
iter_stopped = nrp_node->read_long.iter_stopped;
switch (error->status) {
case 0:
@@ -181,20 +232,28 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
LOG_DBG("[N]GattcNrpHdl[%u][%u]Offset[%u]Len[%u]",
attr->handle, read_params->single.handle, attr->offset, attr->om->om_len);
read_params->func(conn, 0, read_params, attr->om->om_data, attr->om->om_len);
if (!iter_stopped) {
uint8_t cb_ret = read_params->func(conn, 0, read_params,
attr->om->om_data, attr->om->om_len);
if (cb_ret == BT_GATT_ITER_STOP) {
nrp_node->read_long.iter_stopped = true;
}
}
break;
case BLE_HS_EDONE:
read_params->func(conn, 0, read_params, NULL, 0);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_LONG, read_params, 0);
if (!iter_stopped) {
read_params->func(conn, 0, read_params, NULL, 0);
}
break;
default:
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
if (error->status & BLE_HS_ERR_ATT_BASE) {
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_LONG, read_params, 0);
if ((error->status & BLE_HS_ERR_ATT_BASE) && !iter_stopped) {
read_params->func(conn, (uint8_t)error->status, read_params, NULL, 0);
} else {
/* Note: for other errors, currently we just ignore it. */
}
rc = error->status;
@@ -234,7 +293,7 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
}
/* Shall be invoked before the read_params->func is invoked */
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_SINGLE, read_params);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_SINGLE, read_params, 0);
switch (error->status) {
case 0:
@@ -263,8 +322,6 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
if (error->status & BLE_HS_ERR_ATT_BASE) {
read_params->func(conn, (uint8_t)error->status, read_params, NULL, 0);
} else {
/* Note: for other errors, currently we just ignore it. */
}
rc = error->status;
@@ -353,7 +410,7 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
}
/* Shall be invoked before the write_params->func is invoked */
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_WRITE_REQ, write_params);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_WRITE_REQ, write_params, 0);
switch (error->status) {
case 0:
@@ -372,8 +429,6 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
if (error->status & BLE_HS_ERR_ATT_BASE) {
write_params->func(conn, (uint8_t)error->status, write_params);
} else {
/* Note: for other errors, currently we just ignore it. */
}
rc = error->status;
@@ -386,17 +441,18 @@ end:
return rc;
}
static int gattc_nrp_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
static int gattc_nrp_write(struct bt_conn *conn, struct bt_gatt_write_params *params,
const uint8_t *data, uint16_t length)
{
int rc;
LOG_DBG("[N]GattcNrpWr[%u][%u][%u]",
conn->handle, params->handle, params->length);
conn->handle, params->handle, length);
rc = ble_gattc_write_flat(conn->handle,
params->handle,
params->data,
params->length,
data,
length,
gattc_nrp_write_cb_safe,
params);
if (rc) {
@@ -430,7 +486,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
}
/* Should be invoked here */
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_SUBSCRIBE, sub_params);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_SUBSCRIBE, sub_params, 0);
switch (error->status) {
case 0:
@@ -521,10 +577,13 @@ void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
}
if (status) {
/* EDONE = peer confirmed → success; other non-zero = failure. */
uint8_t err = (status == BLE_HS_EDONE) ? 0 : status;
attr.handle = attr_handle;
params.attr = &attr;
bt_le_nimble_gatt_nrp_remove(conn, GATTS_NRP_INDICATE, &params);
bt_le_nimble_gatt_nrp_remove(conn, GATTS_NRP_INDICATE, &params, err);
}
}
@@ -536,8 +595,11 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para
assert(conn && params);
assert(params->attr);
assert(params->data);
assert(params->len);
/* 0-length indications are valid (spec); ble_hs_mbuf_from_flat accepts
* (NULL, 0). But (NULL, len>0) would manifest as a misleading -ENOMEM,
* so catch the programmer error here as a defense-in-depth guard.
*/
assert(params->len == 0 || params->data != NULL);
data.attr = params->attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
@@ -633,6 +695,11 @@ static void gatt_nrp_del(struct gatt_nrp *nrp)
LOG_DBG("[N]GattNrpFree[%p]", nrp_head);
if (nrp_head->type == GATTC_NRP_WRITE_REQ) {
free(nrp_head->write_req.data_copy);
} else if (nrp_head->type == GATTS_NRP_INDICATE) {
free(nrp_head->indicate.data_copy);
}
free(nrp_head);
}
@@ -671,15 +738,44 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
case GATTC_NRP_READ_SINGLE:
nrp_node->read_single.params = params;
break;
case GATTC_NRP_WRITE_REQ:
nrp_node->write_req.params = params;
case GATTC_NRP_WRITE_REQ: {
struct bt_gatt_write_params *wp = params;
nrp_node->write_req.params = wp;
nrp_node->write_req.data_copy = NULL;
if (wp->length > 0) {
assert(wp->data);
nrp_node->write_req.data_copy = malloc(wp->length);
if (nrp_node->write_req.data_copy == NULL) {
LOG_ERR("[N]GattNrpWrDataAllocFail[%u]", wp->length);
free(nrp_node);
return -ENOMEM;
}
memcpy(nrp_node->write_req.data_copy, wp->data, wp->length);
}
break;
}
case GATTC_NRP_SUBSCRIBE:
nrp_node->subscribe.params = params;
break;
case GATTS_NRP_INDICATE:
nrp_node->indicate.params = params;
case GATTS_NRP_INDICATE: {
struct bt_gatt_indicate_params *ip = params;
nrp_node->indicate.params_copy = *ip;
nrp_node->indicate.data_copy = NULL;
if (ip->len > 0) {
assert(ip->data);
nrp_node->indicate.data_copy = malloc(ip->len);
if (nrp_node->indicate.data_copy == NULL) {
LOG_ERR("[N]GattNrpIndDataAllocFail[%u]", ip->len);
free(nrp_node);
return -ENOMEM;
}
memcpy(nrp_node->indicate.data_copy, ip->data, ip->len);
}
nrp_node->indicate.params_copy.data = nrp_node->indicate.data_copy;
nrp_node->indicate.params = &nrp_node->indicate.params_copy;
break;
}
default:
assert(0);
}
@@ -692,11 +788,13 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
type == GATTC_NRP_READ_SINGLE) {
rc = gattc_nrp_read(conn, params);
} else if (type == GATTC_NRP_WRITE_REQ) {
rc = gattc_nrp_write(conn, params);
rc = gattc_nrp_write(conn, params,
nrp_node->write_req.data_copy,
((struct bt_gatt_write_params *)params)->length);
} else if (type == GATTC_NRP_SUBSCRIBE) {
rc = gattc_nrp_subscribe(conn, params);
} else if (type == GATTS_NRP_INDICATE) {
rc = gatts_nrp_indicate(conn, params);
rc = gatts_nrp_indicate(conn, nrp_node->indicate.params);
}
} else {
LOG_DBG("[N]GattNrpListNotEmpty");
@@ -710,6 +808,11 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
if (rc != BLE_HS_ENOTCONN) {
LOG_ERR("[N]GattNrpInsertFail[%d]", rc);
}
if (type == GATTC_NRP_WRITE_REQ) {
free(nrp_node->write_req.data_copy);
} else if (type == GATTS_NRP_INDICATE) {
free(nrp_node->indicate.data_copy);
}
free(nrp_node);
}
@@ -756,7 +859,7 @@ int bt_le_nimble_gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *param
return nimble_err_to_errno(rc);
}
int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *params)
int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *params, uint8_t err)
{
struct gatt_nrp_node *nrp_head;
struct gatt_nrp *nrp;
@@ -790,6 +893,8 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
break;
case GATTC_NRP_WRITE_REQ:
assert(nrp_head->write_req.params == params);
free(nrp_head->write_req.data_copy);
nrp_head->write_req.data_copy = NULL;
break;
case GATTC_NRP_SUBSCRIBE:
assert(nrp_head->subscribe.params == params);
@@ -805,11 +910,13 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
assert(0); /* Should not happen */
}
/* Note:
* The params may be shared by multiple connections. And currently
* in this case, the params will not be updated in the func callback.
*/
nrp_head->indicate.params->func(conn, nrp_head->indicate.params, 0);
/* params is the deep copy taken at insert time; cb sees the copy's
* address, not the caller's original (which may have been reused).
* err propagates the mapped NimBLE status (0 on EDONE, else err). */
nrp_head->indicate.params->func(conn, nrp_head->indicate.params, err);
free(nrp_head->indicate.data_copy);
nrp_head->indicate.data_copy = NULL;
break;
default:
assert(0);
@@ -853,7 +960,9 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
nrp_head->read_single.params->func(conn, rc, nrp_head->read_single.params, NULL, 0);
}
} else if (nrp_head->type == GATTC_NRP_WRITE_REQ) {
rc = gattc_nrp_write(conn, nrp_head->write_req.params);
rc = gattc_nrp_write(conn, nrp_head->write_req.params,
nrp_head->write_req.data_copy,
nrp_head->write_req.params->length);
if (rc) {
LOG_ERR("[N]GattcNrpWrFail[%d]", rc);
@@ -887,6 +996,11 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
*/
sys_slist_remove(&nrp->list, NULL, node);
if (nrp_head->type == GATTC_NRP_WRITE_REQ) {
free(nrp_head->write_req.data_copy);
} else if (nrp_head->type == GATTS_NRP_INDICATE) {
free(nrp_head->indicate.data_copy);
}
free(nrp_head);
}

View File

@@ -113,7 +113,9 @@ void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
int bt_le_nimble_gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params);
int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *params);
/* err is forwarded only to the INDICATE func cb; other NRP types invoke their
* own func with err in cb_safe and pass 0 here. */
int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *params, uint8_t err);
void bt_le_nimble_gatt_nrp_clear(uint16_t conn_handle);

View File

@@ -409,6 +409,8 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
_LIB_ONLY
int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params)
{
int err;
assert(params);
LOG_DBG("GattNtfCb[%u]", params->len);
@@ -418,7 +420,16 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
return -ENOTCONN;
}
return bt_le_nimble_gatts_notify(conn, params);
err = bt_le_nimble_gatts_notify(conn, params);
/* gatts_notify is synchronous (mbuf-copy + dispatch on return); fire the
* caller's completion cb here so state machines like PACS_FLAG_NOTIFY_RDY
* advance. */
if (err == 0 && params->func != NULL) {
params->func(conn, params->user_data);
}
return err;
}
_LIB_IDF

View File

@@ -7,8 +7,10 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "common/host.h"
@@ -28,7 +30,18 @@ void bt_le_host_lock(void)
#endif /* HOST_LOCK_DEBUG */
{
/* LOG_DBG("%s: %d", func, line); */
k_mutex_lock(&host_mutex, TIMEOUT_MS);
int err = k_mutex_lock(&host_mutex, TIMEOUT_MS);
if (err) {
/* 5s wait failed: the host stack is wedged. k_mutex_lock has
* already logged self/holder task names. Use libc abort() rather
* than assert(0) — assert is a no-op under NDEBUG, which would
* let the caller enter the critical section without the mutex
* held and cause races. abort() halts in every build.
*/
LOG_ERR("HostLockTimeout");
abort();
}
}
#if HOST_LOCK_DEBUG
@@ -38,6 +51,16 @@ void bt_le_host_unlock(void)
#endif /* HOST_LOCK_DEBUG */
{
/* LOG_DBG("%s: %d", func, line); */
/* Defense-in-depth: bt_le_host_lock now aborts on timeout, so this
* branch is unreachable in normal flow. Keep the check to catch any
* unbalanced unlock (callers releasing without prior lock).
*/
if (xSemaphoreGetMutexHolder(host_mutex.handle) != xTaskGetCurrentTaskHandle()) {
LOG_WRN("HostUnlockNotHolder");
return;
}
k_mutex_unlock(&host_mutex);
}

View File

@@ -28,8 +28,6 @@ static void iso_timer_cb(void *arg)
struct k_work *work = arg;
int err;
LOG_DBG("IsoTimerCb[%p]", work);
assert(work);
assert(work->timer);
assert(work->handler);
@@ -310,7 +308,5 @@ void bt_le_timer_handle_event(void *arg)
{
struct k_work *work = arg;
LOG_DBG("HdlTimerEvt[%p]", work);
k_work_submit_safe(work);
}

View File

@@ -1216,6 +1216,8 @@ struct bt_gatt_notify_params {
uint16_t len;
/** Notification Value callback */
bt_gatt_complete_func_t func;
/** Data passed to @ref func when the notification completes. */
void *user_data;
/** Att channel options. */
enum bt_att_chan_opt chan_opt;
};

View File

@@ -67,12 +67,23 @@ static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout)
assert(mutex);
assert(mutex->handle);
if (xSemaphoreTakeRecursive(mutex->handle, timeout) != pdTRUE) {
K_MUTEX_LOG_ERR("LockFail");
return -EIO;
if (xSemaphoreTakeRecursive(mutex->handle, timeout) == pdTRUE) {
return 0;
}
return 0;
#if !CONFIG_BT_ISO_NO_LOG && (CONFIG_BT_ISO_LOG_LEVEL >= BT_ISO_LOG_ERROR)
/* On timeout, dump who timed out and who is currently holding the
* mutex so the caller's log makes wedge diagnosis a one-look job
* instead of a multi-task scavenger hunt.
*/
TaskHandle_t holder = xSemaphoreGetMutexHolder(mutex->handle);
K_MUTEX_LOG_ERR("LockFail[self=%s][holder=%s]",
pcTaskGetName(NULL),
holder ? pcTaskGetName(holder) : "<none>");
#else
K_MUTEX_LOG_ERR("LockFail");
#endif
return -EIO;
}
static inline int k_mutex_unlock(struct k_mutex *mutex)