feat(ble_audio): Support BLE ISO & BLE Audio deinitialization

This commit is contained in:
Liu Linyan
2026-09-04 09:26:24 +08:00
parent 1aa418d673
commit ae452a0ce9
84 changed files with 1825 additions and 210 deletions
@@ -6,8 +6,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/logging/log.h>
#include "esp_ble_iso_common_api.h"
LOG_MODULE_REGISTER(ISO_API, CONFIG_BT_ISO_LOG_LEVEL);
esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size,
bool (*func)(uint8_t type,
const uint8_t *data,
@@ -128,6 +134,7 @@ esp_err_t esp_ble_iso_chan_connect(esp_ble_iso_connect_param_t *param,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL) {
LOG_WRN("AclUnknown[%u]", conn_handle);
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
@@ -266,6 +273,7 @@ esp_err_t esp_ble_iso_big_create(uint8_t adv_handle,
adv = bt_le_ext_adv_find(adv_handle);
if (adv == NULL) {
LOG_WRN("ExtAdvUnknown[%u]", adv_handle);
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
@@ -294,6 +302,7 @@ esp_err_t esp_ble_iso_big_sync(uint16_t sync_handle,
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_WRN("PaSyncUnknown[%u]", sync_handle);
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
@@ -434,6 +443,44 @@ unregister_gap:
return ESP_FAIL;
}
esp_err_t esp_ble_iso_common_deinit(const esp_ble_iso_deinit_info_t *info)
{
bool reset_ext_adv = true;
bool reset_pa_sync = true;
int err;
if (info) {
if (!info->reset_acl_conn) {
LOG_WRN("ConnKeepUnsupported");
return ESP_ERR_NOT_SUPPORTED;
}
reset_ext_adv = info->reset_ext_adv;
reset_pa_sync = info->reset_pa_sync;
}
if (bt_le_host_check_idle()) {
return ESP_ERR_INVALID_STATE;
}
err = bt_le_host_deinit();
if (err) {
return ESP_ERR_TIMEOUT;
}
if (reset_ext_adv) {
bt_le_ext_adv_state_reset();
}
if (reset_pa_sync) {
bt_le_per_adv_sync_state_reset();
}
bt_le_gap_app_cb_unregister();
return ESP_OK;
}
#if CONFIG_BT_BLUEDROID_ENABLED
uint8_t esp_ble_iso_bluedroid_get_gattc_if(void)
{
@@ -367,6 +367,10 @@ esp_err_t esp_ble_iso_cig_terminate(esp_ble_iso_cig_t *cig);
* @param conn_handle Connection handle.
* @param count Number of channels to connect.
*
* @note ISO only knows links whose connection event it saw, so one
* established before esp_ble_iso_common_init() returns
* ESP_ERR_NOT_FOUND until it is handed back.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_iso_chan_connect(esp_ble_iso_connect_param_t *param,
@@ -578,13 +582,38 @@ typedef struct {
/**
* @brief Post an application-layer GAP event for ISO internal usage.
*
* @note This function is only needed while using NimBLE Host.
* @note NimBLE-only: NimBLE has no global GAP callback, so ISO sees only what
* the application forwards here. An event left out is dropped silently.
* Bluedroid's ISO layer hooks the stack directly.
*
* @param type Event type.
* @param param Event parameters.
* @note Forward every event below that the application's flows can produce.
* Forwarding one ISO does not use is harmless; omitting one leaves the
* matching ISO state uncreated and fails later and elsewhere:
* - BLE_GAP_EVENT_EXT_DISC extended advertising report
* - BLE_GAP_EVENT_CONNECT ACL established or failed
* - BLE_GAP_EVENT_DISCONNECT ACL closed
* - BLE_GAP_EVENT_ENC_CHANGE encryption changed
* - BLE_GAP_EVENT_PERIODIC_SYNC PA sync established
* - BLE_GAP_EVENT_PERIODIC_SYNC_LOST PA sync lost
* - BLE_GAP_EVENT_PERIODIC_REPORT periodic advertising report
* - BLE_GAP_EVENT_PERIODIC_TRANSFER PA sync received over PAST
* - BLE_GAP_EVENT_PERIODIC_TRANSFER_V2 as above, PAwR
*
* BIGInfo needs no forwarding: ISO takes it from the controller itself
* and delivers it as ESP_BLE_ISO_GAP_EVENT_BIGINFO_RECV.
*
* @param type Event type, i.e. ble_gap_event::type.
* @param param Event parameters, i.e. the struct ble_gap_event pointer.
*/
void esp_ble_iso_gap_app_post_event(uint16_t type, void *param);
/** Which of ISO's records of application-owned objects deinit clears. */
typedef struct {
bool reset_ext_adv; /*!< Clear the advertising sets added for BIG */
bool reset_pa_sync; /*!< Clear the periodic advertising syncs */
bool reset_acl_conn; /*!< Clear the ACL links. Must be true - see below */
} esp_ble_iso_deinit_info_t;
/**
* @brief Initialize ISO common functionality.
*
@@ -594,6 +623,34 @@ void esp_ble_iso_gap_app_post_event(uint16_t type, void *param);
*/
esp_err_t esp_ble_iso_common_init(esp_ble_iso_init_info_t *info);
/**
* @brief Deinitialize ISO common functionality.
*
* The application must first disconnect all CIS/BIS and terminate every CIG/BIG.
* If any is still up this returns ESP_ERR_INVALID_STATE and releases nothing,
* logging each offending item at ERROR level.
*
* ACL links, advertising sets and periodic advertising syncs belong to the
* application: they neither block deinit nor get torn down, only ISO's record of
* them goes. @p info chooses which records to drop - a dropped one is logged at
* WARNING level and must be handed back after the next init. NULL drops all three.
*
* reset_acl_conn must be true for now: on Bluedroid the GATT teardown drops ISO's
* reference, so a link ISO alone held is disconnected while a shared one stays up,
* and ISO cannot yet tell the two apart.
*
* @param info Which records to clear, or NULL to clear all.
*
* @note Must not be called from an ISO callback (they run on the ISO task,
* which this stops).
*
* @return ESP_OK on success,
* ESP_ERR_INVALID_STATE if something ISO owns is still active,
* ESP_ERR_TIMEOUT if the ISO task could not be stopped.
* Nothing is released unless ESP_OK is returned.
*/
esp_err_t esp_ble_iso_common_deinit(const esp_ble_iso_deinit_info_t *info);
#if CONFIG_BT_BLUEDROID_ENABLED
/**
* @brief Get the engine's internal GATTC interface handle (Bluedroid only).
@@ -212,7 +212,7 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GapPostEvtFail[%d][%u]", err, qev->type);
ISO_POST_FAIL_LOG(err, "[B]GapPostEvtFail[%d][%u]", err, qev->type);
free(qev);
}
}
@@ -409,12 +409,8 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
err = bt_le_iso_task_post(q_type, qev, sizeof(*qev));
if (err) {
/* Floodable reports drop by design when the queue is full; only a
* failure on the reliable (normal-queue) path is a real error. */
if (q_type == ISO_QUEUE_ITEM_TYPE_GAP_EVENT) {
LOG_ERR("[B]GapPostEvtBtaFail[%d][%u]", err, qev->type);
} else {
LOG_DBG("[B]GapRptDrop[%u]", qev->type);
ISO_POST_FAIL_LOG(err, "[B]GapPostEvtBtaFail[%d][%u]", err, qev->type);
}
goto free;
}
@@ -422,26 +418,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
return;
free:
/* Mirror nimble/gap.c cleanup: free nested data buffers carried by
* specific event types before freeing the qev container itself. */
switch (qev->type) {
case BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV:
if (qev->ext_scan_recv.data) {
free(qev->ext_scan_recv.data);
qev->ext_scan_recv.data = NULL;
}
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_RECV:
if (qev->pa_sync_recv.data) {
free(qev->pa_sync_recv.data);
qev->pa_sync_recv.data = NULL;
}
break;
default:
break;
}
free(qev);
bt_le_gap_event_free(qev);
}
int bt_le_bluedroid_scan_start(const struct bt_le_scan_param *param)
@@ -476,7 +476,7 @@ static void gattc_connect_event_handler(tBTA_GATTC_CONNECT *connect)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcConnPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcConnPostFail[%d]", err);
free(qev);
}
}
@@ -496,7 +496,7 @@ static void gattc_disconnect_event_handler(tBTA_GATTC_DISCONNECT *disconnect)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcDiscPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcDiscPostFail[%d]", err);
free(qev);
}
}
@@ -516,7 +516,7 @@ static void gattc_open_event_handler(tBTA_GATTC_OPEN *open)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcOpenPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcOpenPostFail[%d]", err);
free(qev);
}
}
@@ -537,7 +537,7 @@ static void gattc_mtu_event_handler(tBTA_GATTC_CFG_MTU *cfg_mtu)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcMtuPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcMtuPostFail[%d]", err);
free(qev);
}
}
@@ -557,7 +557,7 @@ static void gattc_disc_cmpl_event_handler(tBTA_GATTC_DIS_CMPL *disc_cmpl)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcDiscCmplPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcDiscCmplPostFail[%d]", err);
free(qev);
}
}
@@ -589,11 +589,8 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcReadChrcPostFail[%d]", err);
if (qev->gattc_read_chrc.value) {
free(qev->gattc_read_chrc.value);
}
free(qev);
ISO_POST_FAIL_LOG(err, "[B]GattcReadChrcPostFail[%d]", err);
bt_le_gatt_event_free(qev);
}
}
@@ -614,7 +611,7 @@ static void gattc_write_chrc_event_handler(tBTA_GATTC_WRITE *write)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcWriteChrcPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcWriteChrcPostFail[%d]", err);
free(qev);
}
}
@@ -641,7 +638,7 @@ static void gatts_notify_tx_event_handler(tBTA_GATTS_REQ *req)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsNotifyTxPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsNotifyTxPostFail[%d]", err);
free(qev);
}
}
@@ -671,7 +668,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattcNotifyRxPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattcNotifyRxPostFail[%d]", err);
if (qev->gattc_notify_rx.value) {
free(qev->gattc_notify_rx.value);
}
@@ -697,7 +694,7 @@ static void gatts_connect_event_handler(tBTA_GATTS_CONN *connect)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsConnPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsConnPostFail[%d]", err);
free(qev);
}
}
@@ -717,7 +714,7 @@ static void gatts_disconnect_event_handler(tBTA_GATTS_CONN *disconnect)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsDiscPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsDiscPostFail[%d]", err);
free(qev);
}
}
@@ -738,7 +735,7 @@ static void gatts_mtu_event_handler(tBTA_GATTS_REQ *req)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsMtuPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsMtuPostFail[%d]", err);
free(qev);
}
}
@@ -763,7 +760,7 @@ static void gatts_read_req_handler(tBTA_GATTS_REQ *req)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsReadPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsReadPostFail[%d]", err);
free(qev);
}
}
@@ -798,11 +795,8 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsWritePostFail[%d]", err);
if (qev->gatts_write.value) {
free(qev->gatts_write.value);
}
free(qev);
ISO_POST_FAIL_LOG(err, "[B]GattsWritePostFail[%d]", err);
bt_le_gatt_event_free(qev);
}
}
@@ -822,13 +816,20 @@ static void gatts_exec_write_req_handler(tBTA_GATTS_REQ *req)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsExecPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]GattsExecPostFail[%d]", err);
free(qev);
}
}
static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
{
/* BTA_GATTS_AppDeregister is async, so its events land after deinit returns.
* Drop on the flag, not gatts_if - a re-register can hand back the same if. */
if (gatt_shutting_down) {
LOG_INF("[B]GattsDropEvt[%u]", event);
return;
}
switch (event) {
case BTA_GATTS_REG_EVT:
LOG_DBG("[B]GattsRegEvt[%u][%u]",
@@ -838,10 +839,6 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->reg_oper.uuid.uu.uuid128[0],
p_data->reg_oper.uuid.uu.uuid128[1]);
if (gatt_shutting_down) {
break;
}
if (p_data->reg_oper.status == BTA_GATT_OK &&
memcmp(&p_data->reg_oper.uuid, &gatts_app_uuid, sizeof(tBT_UUID)) == 0) {
gatts_if = p_data->reg_oper.server_if;
@@ -869,7 +866,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->conn.conn_params.timeout);
if (p_data->conn.server_if != gatts_if) {
LOG_ERR("[B]GattsConnUnknownIf[%u]", p_data->conn.server_if);
LOG_ERR("[B]GattsConnUnknownIf[%u][%u]", p_data->conn.server_if, gatts_if);
break;
}
@@ -885,7 +882,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->conn.remote_bda[4], p_data->conn.remote_bda[5]);
if (p_data->conn.server_if != gatts_if) {
LOG_ERR("[B]GattsDisconnUnknownIf[%u]", p_data->conn.server_if);
LOG_ERR("[B]GattsDisconnUnknownIf[%u][%u]", p_data->conn.server_if, gatts_if);
break;
}
@@ -913,8 +910,8 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->req_data.p_data ? p_data->req_data.p_data->mtu : 0);
if (BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id) != gatts_if) {
LOG_ERR("[B]GattsMtuUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id));
LOG_ERR("[B]GattsMtuUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id), gatts_if);
break;
}
@@ -955,7 +952,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->create.uuid.uu.uuid128[1]);
if (p_data->create.server_if != gatts_if) {
LOG_ERR("[B]GattsCreateUnknownIf[%u]", p_data->create.server_if);
LOG_ERR("[B]GattsCreateUnknownIf[%u][%u]", p_data->create.server_if, gatts_if);
break;
}
@@ -972,7 +969,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->add_result.attr_id, p_data->add_result.service_id);
if (p_data->add_result.server_if != gatts_if) {
LOG_ERR("[B]GattsAddInclSvcUnknownIf[%u]", p_data->add_result.server_if);
LOG_ERR("[B]GattsAddInclSvcUnknownIf[%u][%u]", p_data->add_result.server_if, gatts_if);
break;
}
@@ -990,7 +987,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->add_result.char_uuid.len, p_data->add_result.char_uuid.uu.uuid16);
if (p_data->add_result.server_if != gatts_if) {
LOG_ERR("[B]GattsAddCharUnknownIf[%u]", p_data->add_result.server_if);
LOG_ERR("[B]GattsAddCharUnknownIf[%u][%u]", p_data->add_result.server_if, gatts_if);
break;
}
@@ -1008,7 +1005,7 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->add_result.char_uuid.len, p_data->add_result.char_uuid.uu.uuid16);
if (p_data->add_result.server_if != gatts_if) {
LOG_ERR("[B]GattsAddDescrUnknownIf[%u]", p_data->add_result.server_if);
LOG_ERR("[B]GattsAddDescrUnknownIf[%u][%u]", p_data->add_result.server_if, gatts_if);
break;
}
@@ -1034,8 +1031,8 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
p_data->req_data.p_data->read_req.need_rsp);
if (BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id) != gatts_if) {
LOG_ERR("[B]GattsReadUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id));
LOG_ERR("[B]GattsReadUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id), gatts_if);
break;
}
@@ -1058,8 +1055,8 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
}
if (BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id) != gatts_if) {
LOG_ERR("[B]GattsWriteUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id));
LOG_ERR("[B]GattsWriteUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id), gatts_if);
break;
}
@@ -1078,8 +1075,8 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
}
if (BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id) != gatts_if) {
LOG_ERR("[B]GattsExecUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id));
LOG_ERR("[B]GattsExecUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id), gatts_if);
break;
}
@@ -1117,6 +1114,12 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
{
/* See gatts_app_cb. */
if (gatt_shutting_down) {
LOG_INF("[B]GattcDropEvt[%u]", event);
return;
}
switch (event) {
case BTA_GATTC_REG_EVT:
LOG_DBG("[B]GattcRegEvt[%u][%u]",
@@ -1126,10 +1129,6 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->reg_oper.app_uuid.uu.uuid128[0],
p_data->reg_oper.app_uuid.uu.uuid128[1]);
if (gatt_shutting_down) {
break;
}
if (p_data->reg_oper.status == BTA_GATT_OK &&
memcmp(&p_data->reg_oper.app_uuid, &gattc_app_uuid, sizeof(tBT_UUID)) == 0) {
gattc_if = p_data->reg_oper.client_if;
@@ -1156,7 +1155,7 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->connect.conn_params.latency, p_data->connect.conn_params.timeout);
if (p_data->connect.client_if != gattc_if) {
LOG_ERR("[B]GattcConnUnknownIf[%u]", p_data->connect.client_if);
LOG_ERR("[B]GattcConnUnknownIf[%u][%u]", p_data->connect.client_if, gattc_if);
break;
}
@@ -1172,7 +1171,7 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->disconnect.remote_bda[4], p_data->disconnect.remote_bda[5]);
if (p_data->disconnect.client_if != gattc_if) {
LOG_ERR("[B]GattcDisconnUnknownIf[%u]", p_data->disconnect.client_if);
LOG_ERR("[B]GattcDisconnUnknownIf[%u][%u]", p_data->disconnect.client_if, gattc_if);
break;
}
@@ -1189,7 +1188,7 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->open.remote_bda[4], p_data->open.remote_bda[5]);
if (p_data->open.client_if != gattc_if) {
LOG_ERR("[B]GattcOpenUnknownIf[%u]", p_data->open.client_if);
LOG_ERR("[B]GattcOpenUnknownIf[%u][%u]", p_data->open.client_if, gattc_if);
break;
}
@@ -1213,8 +1212,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->cfg_mtu.status, p_data->cfg_mtu.conn_id, p_data->cfg_mtu.mtu);
if (BTC_GATT_GET_GATT_IF(p_data->cfg_mtu.conn_id) != gattc_if) {
LOG_ERR("[B]GattcMtuUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->cfg_mtu.conn_id));
LOG_ERR("[B]GattcMtuUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->cfg_mtu.conn_id), gattc_if);
break;
}
@@ -1235,8 +1234,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->dis_cmpl.status, p_data->dis_cmpl.conn_id);
if (BTC_GATT_GET_GATT_IF(p_data->dis_cmpl.conn_id) != gattc_if) {
LOG_ERR("[B]GattcDiscSvcCmplUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->dis_cmpl.conn_id));
LOG_ERR("[B]GattcDiscSvcCmplUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->dis_cmpl.conn_id), gattc_if);
break;
}
@@ -1254,8 +1253,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->srvc_res.service_uuid.inst_id);
if (BTC_GATT_GET_GATT_IF(p_data->srvc_res.conn_id) != gattc_if) {
LOG_ERR("[B]GattcSearchResUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->srvc_res.conn_id));
LOG_ERR("[B]GattcSearchResUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->srvc_res.conn_id), gattc_if);
break;
}
break;
@@ -1267,8 +1266,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
BTA_GATTC_SERVICE_INFO_FROM_REMOTE_DEVICE) ? "Remote" : "NVS");
if (BTC_GATT_GET_GATT_IF(p_data->search_cmpl.conn_id) != gattc_if) {
LOG_ERR("[B]GattcSearchCmplUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->search_cmpl.conn_id));
LOG_ERR("[B]GattcSearchCmplUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->search_cmpl.conn_id), gattc_if);
break;
}
break;
@@ -1278,8 +1277,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->read.status, p_data->read.conn_id, p_data->read.handle);
if (BTC_GATT_GET_GATT_IF(p_data->read.conn_id) != gattc_if) {
LOG_ERR("[B]GattcReadCharUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->read.conn_id));
LOG_ERR("[B]GattcReadCharUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->read.conn_id), gattc_if);
break;
}
@@ -1307,8 +1306,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->write.handle, p_data->write.offset);
if (BTC_GATT_GET_GATT_IF(p_data->write.conn_id) != gattc_if) {
LOG_ERR("[B]GattcWriteCharUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->write.conn_id));
LOG_ERR("[B]GattcWriteCharUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->write.conn_id), gattc_if);
break;
}
@@ -1340,8 +1339,8 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
p_data->notify.bda[4], p_data->notify.bda[5]);
if (BTC_GATT_GET_GATT_IF(p_data->notify.conn_id) != gattc_if) {
LOG_ERR("[B]GattcNotifUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->notify.conn_id));
LOG_ERR("[B]GattcNotifUnknownIf[%u][%u]",
BTC_GATT_GET_GATT_IF(p_data->notify.conn_id), gattc_if);
break;
}
@@ -1405,9 +1404,9 @@ static void post_acl_connect_app_event(struct gatt_conn *gatt_conn)
qev->acl_connect.status = gatt_conn->status;
/* Populate identity unconditionally: connect_event_handler fills these
* fields before OPEN_EVT arrives, so failure events still carry valid
* conn_handle / role / dst to the application — matches NimBLE's
* BLE_GAP_EVENT_CONNECT, which always exposes the full descriptor. */
* fields before OPEN_EVT arrives, so a failure that got that far still
* carries valid conn_handle / role / dst to the application. One that
* did not is reported by post_acl_connect_fail_app_event() instead. */
qev->acl_connect.conn_handle = gatt_conn->conn_handle;
qev->acl_connect.role = gatt_conn->role;
qev->acl_connect.dst.type = gatt_conn->peer.type;
@@ -1415,7 +1414,29 @@ static void post_acl_connect_app_event(struct gatt_conn *gatt_conn)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]AclConnPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]AclConnPostFail[%d]", err);
free(qev);
}
}
/* A connect that never reached the link layer gets no CONNECT_EVT, so nothing
* would tell the application its request died. Identity stays zero, matching
* NimBLE, which fills the descriptor only on status 0. */
static void post_acl_connect_fail_app_event(uint8_t status)
{
struct bt_le_gap_app_param *qev;
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
BT_LE_ASSERT(qev);
qev->type = BT_LE_GAP_APP_PARAM_ACL_CONNECT;
qev->acl_connect.status = status;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
ISO_POST_FAIL_LOG(err, "[B]AclConnFailPostFail[%d]", err);
free(qev);
}
}
@@ -1435,7 +1456,7 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]AclDiscPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "[B]AclDiscPostFail[%d]", err);
free(qev);
}
}
@@ -1542,7 +1563,16 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattcOpenUnknownDev");
/* A non-zero status here is the application's own connect request failing
* before any link existed; a success without a slot is a real
* inconsistency and stays an error. */
if (event->status) {
LOG_WRN("[B]GattcOpenFailed[%u][0x%04x]", event->status, event->conn_id);
post_acl_connect_fail_app_event(event->status);
} else {
LOG_ERR("[B]GattcOpenUnknownDev[0x%04x]", event->conn_id);
}
return;
}
@@ -1928,7 +1958,7 @@ static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *e
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattsDisconnUnknownDev");
LOG_DBG("[B]GattsDisconnUnknownDev");
return;
}
@@ -3500,7 +3530,8 @@ int bt_le_bluedroid_gatt_init(void)
void bt_le_bluedroid_gatt_deinit(void)
{
/* Block late REG_EVT gives before tearing down the sems. */
/* Must precede the deregisters: it is what makes the event callbacks drop
* everything BTA still delivers for this registration. */
gatt_shutting_down = true;
if (gattc_if != 0) {
@@ -111,6 +111,7 @@ static void direct_hci_complete_cb(BT_HDR *response, void *context)
/* deinit may have set the shutdown flag and be about to delete the
* sem. Skip the give to avoid asserting on a NULL handle. */
if (direct_hci_shutting_down) {
LOG_INF("[B]DirectHciDropCmpl[0x%04x]", opcode);
return;
}
@@ -400,7 +400,7 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp)
BT_LE_ASSERT(cis_params);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_hdl = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_cis_hdls));
cis_params[i].cis_hdl = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_cis_hdls));
cis_params[i].acl_hdl = sys_get_le16(buf->data + 6 + i * sizeof(struct ble_hci_cis_hdls));
}
@@ -1089,7 +1089,9 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
err = bt_le_iso_task_post(q_type, qdata, qdata_len);
if (err) {
LOG_ERR("[B]IsoPostEvtFail[%d][%02x]", err, event);
if (q_type == ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT) {
ISO_POST_FAIL_LOG(err, "[B]IsoPostEvtFail[%d][%02x]", err, event);
}
free(qdata);
}
}
@@ -1330,8 +1332,12 @@ int bt_le_bluedroid_iso_init(void)
void bt_le_bluedroid_iso_deinit(void)
{
#if CONFIG_BT_ISO_UNICAST
/* Mirror bt_le_iso_init() which enables bit 32 only on unicast build. */
iso_disable_cis();
/* Core 6.0 Vol 4 Part E 7.8.115: LE Set Host Feature is Command Disallowed
* while any connection exists, and the ACL outlives ISO deinit. Skip - a
* stale host-support bit is harmless and the next init sets it again. */
if (bt_le_acl_conn_count() == 0) {
iso_disable_cis();
}
#endif /* CONFIG_BT_ISO_UNICAST */
#if CONFIG_BT_ISO_RX
@@ -262,12 +262,8 @@ void bt_le_nimble_gap_post_event(void *param)
err = bt_le_iso_task_post(q_type, qev, sizeof(*qev));
if (err) {
/* Floodable reports drop by design when the queue is full; only a
* failure on the reliable (normal-queue) path is a real error. */
if (q_type == ISO_QUEUE_ITEM_TYPE_GAP_EVENT) {
LOG_ERR("[N]GapPostEvtFail[%d][%u]", err, qev->type);
} else {
LOG_DBG("[N]GapRptDrop[%u]", qev->type);
ISO_POST_FAIL_LOG(err, "[N]GapPostEvtFail[%d][%u]", err, qev->type);
}
goto free;
}
@@ -275,24 +271,7 @@ void bt_le_nimble_gap_post_event(void *param)
return;
free:
switch (qev->type) {
case BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV:
if (qev->ext_scan_recv.data) {
free(qev->ext_scan_recv.data);
qev->ext_scan_recv.data = NULL;
}
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_RECV:
if (qev->pa_sync_recv.data) {
free(qev->pa_sync_recv.data);
qev->pa_sync_recv.data = NULL;
}
break;
default:
break;
}
free(qev);
bt_le_gap_event_free(qev);
}
int bt_le_nimble_scan_start(const struct bt_le_scan_param *param, ble_gap_event_fn *cb)
@@ -102,15 +102,8 @@ void bt_le_nimble_gatt_post_event(void *param)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[N]GattPostEvtFail[%d][%u]", err, qev->type);
if (qev->type == BT_LE_GATTC_NOTIFY_RX_EVENT &&
qev->gattc_notify_rx.value) {
free(qev->gattc_notify_rx.value);
qev->gattc_notify_rx.value = NULL;
}
free(qev);
ISO_POST_FAIL_LOG(err, "[N]GattPostEvtFail[%d][%u]", err, qev->type);
bt_le_gatt_event_free(qev);
}
}
@@ -140,7 +133,7 @@ int bt_le_nimble_gatt_post_disc_event(uint16_t conn_handle, ble_uuid16_t *uuid,
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[N]GattPostDiscEvtFail[%d][%u]", err, type);
ISO_POST_FAIL_LOG(err, "[N]GattPostDiscEvtFail[%d][%u]", err, type);
free(qev);
return err;
}
@@ -165,7 +158,7 @@ int bt_le_nimble_gatt_post_disc_cmpl_event(uint16_t conn_handle, uint8_t status)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[N]GattPostDiscCmplEvtFail[%d][%u][%02x]", err, conn_handle, status);
ISO_POST_FAIL_LOG(err, "[N]GattPostDiscCmplEvtFail[%d][%u][%02x]", err, conn_handle, status);
free(qev);
return err;
}
@@ -808,7 +808,9 @@ static void iso_evt_rx(uint8_t event, const void *data,
err = bt_le_iso_task_post(q_type, qdata, qdata_len);
if (err) {
LOG_ERR("[N]IsoPostEvtFail[%d][%02x]", err, event);
if (q_type == ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT) {
ISO_POST_FAIL_LOG(err, "[N]IsoPostEvtFail[%d][%02x]", err, event);
}
free(qdata);
}
}
@@ -70,6 +70,17 @@ struct bt_le_ext_adv *bt_le_ext_adv_find(uint8_t adv_handle)
}
_IDF_ONLY
void bt_le_ext_adv_state_reset(void)
{
for (size_t i = 0; i < ARRAY_SIZE(ext_adv_pool); i++) {
if (atomic_test_bit(ext_adv_pool[i].flags, BT_PER_ADV_PARAMS_SET)) {
LOG_WRN("DeinitDropExtAdv[%u]", ext_adv_pool[i].handle);
}
}
memset(ext_adv_pool, 0, sizeof(ext_adv_pool));
}
int bt_le_ext_adv_new_safe(uint8_t adv_handle, uint8_t addr_type,
const uint8_t *addr, uint8_t sid)
{
@@ -527,6 +527,32 @@ static void handle_bond_delete_event_safe(struct bt_le_gap_app_param *param)
bt_le_gap_app_cb_evt(&event);
}
void bt_le_gap_event_free(void *data)
{
struct bt_le_gap_app_param *qev = data;
if (qev == NULL) {
return;
}
switch (qev->type) {
case BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV:
if (qev->ext_scan_recv.data) {
free(qev->ext_scan_recv.data);
}
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_RECV:
if (qev->pa_sync_recv.data) {
free(qev->pa_sync_recv.data);
}
break;
default:
break;
}
free(qev);
}
void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
{
struct bt_le_gap_app_param *param;
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <zephyr/kernel.h>
@@ -46,6 +47,31 @@ void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count)
*count = ARRAY_SIZE(acl_conns);
}
size_t bt_le_acl_conn_count(void)
{
size_t count = 0;
for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) {
if (acl_conns[i].state != BT_CONN_DISCONNECTED) {
count++;
}
}
return count;
}
void bt_le_conn_reset(void)
{
LOG_DBG("ConnReset");
memset(acl_conns, 0, sizeof(acl_conns));
memset(conn_ltk, 0, sizeof(conn_ltk));
memset(iso_conns, 0, sizeof(struct bt_conn) * CONFIG_BT_ISO_MAX_CHAN);
sys_slist_init(&conn_cbs);
sys_slist_init(&auth_info_cbs);
}
_IDF_ONLY
bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
const bt_addr_le_t *peer)
@@ -382,6 +408,27 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
return (conn ? 0 : -ENOMEM);
}
_IDF_ONLY
int bt_le_acl_conn_new_safe(uint16_t conn_handle, uint8_t role, uint8_t addr_type,
const uint8_t *addr, uint8_t sec_level)
{
bt_addr_le_t dst;
int err;
if (addr == NULL) {
return -EINVAL;
}
dst.type = addr_type;
bt_addr_copy(&dst.a, (const bt_addr_t *)addr);
bt_le_host_lock();
err = bt_le_acl_conn_new(conn_handle, role, &dst, sec_level);
bt_le_host_unlock();
return err;
}
/* Point conn->le.keys at this ACL connection's LTK slot, filled with the bonded
* LTK the adapter captured. Used as key K by the lib's CSIS SIRK encryption. */
_IDF_ONLY
@@ -1641,3 +1641,34 @@ void bt_le_gatt_handle_event(uint8_t *data, size_t data_len)
bt_le_nimble_gatt_handle_event(data, data_len);
#endif
}
void bt_le_gatt_event_free(void *data)
{
struct bt_le_gatt_event_param *qev = data;
if (qev == NULL) {
return;
}
switch (qev->type) {
case BT_LE_GATTC_NOTIFY_RX_EVENT:
if (qev->gattc_notify_rx.value) {
free(qev->gattc_notify_rx.value);
}
break;
case BT_LE_GATTC_READ_CHRC_EVENT:
if (qev->gattc_read_chrc.value) {
free(qev->gattc_read_chrc.value);
}
break;
case BT_LE_GATTS_WRITE_EVENT:
if (qev->gatts_write.value) {
free(qev->gatts_write.value);
}
break;
default:
break;
}
free(qev);
}
+53 -2
View File
@@ -13,7 +13,12 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/iso.h>
#include <../host/conn_internal.h>
#include "common/host.h"
#include "common/conn.h"
#include "common/app/gap.h"
#include "common/app/gatt.h"
@@ -28,6 +33,8 @@ LOG_MODULE_REGISTER(ISO_HOST, CONFIG_BT_ISO_LOG_LEVEL);
static BT_ISO_CTRL_BSS_ATTR struct k_mutex host_mutex;
extern struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN];
#if HOST_LOCK_DEBUG
void bt_le_host_lock_debug(const char *func, int line)
#else /* HOST_LOCK_DEBUG */
@@ -69,6 +76,32 @@ void bt_le_host_unlock(void)
k_mutex_unlock(&host_mutex);
}
int bt_le_host_check_idle(void)
{
struct bt_iso_chan *chan;
size_t busy = 0;
/* Only what ISO created; the application's adv sets, sync and ACL are
* dropped by bt_le_host_deinit() rather than blocked on. Counts every
* offender so one attempt tells the caller the whole list. */
bt_le_host_lock();
for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
chan = iso_conns[i].iso.chan;
if (chan && chan->state != BT_ISO_STATE_DISCONNECTED) {
LOG_ERR("DeinitBusyIsoChan[%u][state=%u]", i, chan->state);
busy++;
}
}
busy += bt_le_iso_report_busy();
bt_le_host_unlock();
return busy ? -EBUSY : 0;
}
int bt_le_host_init(void)
{
int err;
@@ -77,6 +110,10 @@ int bt_le_host_init(void)
k_mutex_create(&host_mutex);
bt_le_conn_reset();
bt_le_iso_state_reset();
err = bt_le_scan_init();
if (err) {
goto delete_mutex;
@@ -125,13 +162,23 @@ delete_mutex:
return err;
}
void bt_le_host_deinit(void)
int bt_le_host_deinit(void)
{
int err;
LOG_DBG("HostDeinit");
bt_le_iso_task_deinit();
/* Everything below frees state the task dispatches into, so bail out while
* it is still alive rather than free underneath it. */
err = bt_le_iso_task_deinit();
if (err) {
return err;
}
bt_le_iso_deinit();
#if CONFIG_BT_BLUEDROID_ENABLED
/* No gap_deinit: BTM_BleGapRegisterCallback refuses NULL, so the callback
* stays. Harmless - task_post rejects once the task is gone. */
bt_le_bluedroid_gatt_deinit();
#else
bt_le_nimble_gattc_db_deinit();
@@ -139,5 +186,9 @@ void bt_le_host_deinit(void)
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
bt_le_scan_deinit();
/* Last: iso_task has exited and the callbacks above are gone, so nothing
* can take the mutex any more. */
k_mutex_delete(&host_mutex);
return 0;
}
@@ -22,6 +22,8 @@ int bt_le_ext_adv_new_safe(uint8_t adv_handle, uint8_t addr_type,
int bt_le_ext_adv_delete_safe(uint8_t adv_handle);
void bt_le_ext_adv_state_reset(void);
#ifdef __cplusplus
}
#endif
@@ -303,6 +303,8 @@ void bt_le_gap_app_biginfo_event(uint8_t *param);
void bt_le_gap_handle_event(uint8_t *data, size_t data_len);
void bt_le_gap_event_free(void *data);
void bt_le_gap_app_post_event(uint16_t type, void *param);
#ifdef __cplusplus
@@ -30,6 +30,10 @@ extern "C" {
void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count);
size_t bt_le_acl_conn_count(void);
void bt_le_conn_reset(void);
struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle);
int bt_le_acl_conn_new(uint16_t conn_handle,
@@ -37,6 +41,9 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
bt_addr_le_t *dst,
uint8_t sec_level);
int bt_le_acl_conn_new_safe(uint16_t conn_handle, uint8_t role, uint8_t addr_type,
const uint8_t *addr, uint8_t sec_level);
int bt_le_acl_conn_delete(uint16_t conn_handle);
void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk);
@@ -297,6 +297,8 @@ void bt_le_acl_conn_bond_deleted_gatt_listener(uint8_t id, const bt_addr_le_t *p
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len);
void bt_le_gatt_event_free(void *data);
#ifdef __cplusplus
}
#endif
@@ -44,7 +44,9 @@ void bt_le_host_unlock(void);
int bt_le_host_init(void);
void bt_le_host_deinit(void);
int bt_le_host_check_idle(void);
int bt_le_host_deinit(void);
#ifdef __cplusplus
}
@@ -75,6 +75,10 @@ int bt_le_iso_rx(const uint8_t *data, uint16_t len, void *arg);
int bt_le_iso_disconnect(uint16_t conn_handle, uint8_t reason);
void bt_le_iso_state_reset(void);
size_t bt_le_iso_report_busy(void);
int bt_le_iso_init(void);
void bt_le_iso_deinit(void);
@@ -48,6 +48,15 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
uint16_t conn_handle,
struct bt_le_per_adv_sync **out_sync);
int bt_le_per_adv_sync_new_safe(uint16_t sync_handle,
uint8_t sid,
uint8_t phy,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
uint16_t conn_handle,
struct bt_le_per_adv_sync **out_sync);
int bt_le_per_adv_sync_delete(uint16_t sync_handle);
int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle);
@@ -62,6 +71,8 @@ int bt_le_scan_init(void);
void bt_le_scan_deinit(void);
void bt_le_per_adv_sync_state_reset(void);
#ifdef __cplusplus
}
#endif
@@ -116,10 +116,15 @@ struct iso_queue_item {
#define ISO_CRITICAL_QUEUE_LEN 32
#define ISO_NORMAL_QUEUE_LEN 64
#define ISO_FLOODABLE_QUEUE_LEN 32
/* The set must be able to hold one token per item across all three queues. */
/* Not a tier. One-deep and written only by deinit, so the stop signal can never
* hit a full queue - which is what lets the task block on the set indefinitely
* instead of polling for iso_task_stopping. */
#define ISO_CTRL_QUEUE_LEN 1
/* The set must be able to hold one token per item across all queues. */
#define ISO_QUEUE_SET_LEN (ISO_CRITICAL_QUEUE_LEN + \
ISO_NORMAL_QUEUE_LEN + \
ISO_FLOODABLE_QUEUE_LEN)
ISO_FLOODABLE_QUEUE_LEN + \
ISO_CTRL_QUEUE_LEN)
#define ISO_QUEUE_ITEM_SIZE sizeof(struct iso_queue_item)
#if CONFIG_BT_ISO_DISPATCH_MONITOR
@@ -132,9 +137,21 @@ void bt_le_iso_dispatch_stats_dump(void);
int bt_le_iso_task_post(enum iso_queue_item_type type,
void *data, size_t data_len);
/* -ESHUTDOWN is expected for as long as an ACL, scan or PA sync outlives the
* deinit that stopped it; anything else is a real drop. A macro so each file
* logs under its own LOG_MODULE_REGISTER. */
#define ISO_POST_FAIL_LOG(_err, ...) \
do { \
if ((_err) == -ESHUTDOWN) { \
LOG_DBG(__VA_ARGS__); \
} else { \
LOG_ERR(__VA_ARGS__); \
} \
} while (0)
int bt_le_iso_task_init(void);
void bt_le_iso_task_deinit(void);
int bt_le_iso_task_deinit(void);
#ifdef __cplusplus
}
@@ -170,6 +170,24 @@ int bt_le_per_adv_sync_cb_register(struct bt_le_per_adv_sync_cb *cb)
return 0;
}
_LIB_ONLY
int bt_le_per_adv_sync_cb_unregister(struct bt_le_per_adv_sync_cb *cb)
{
LOG_DBG("PaSyncCbUnreg");
if (cb == NULL) {
LOG_ERR("PaSyncCbNull");
return -EINVAL;
}
if (!sys_slist_find_and_remove(&pa_sync_cbs, &cb->node)) {
LOG_ERR("PaSyncCbNotReg[%p]", cb);
return -ENOENT;
}
return 0;
}
_LIB_ONLY
int bt_le_per_adv_sync_get_info(struct bt_le_per_adv_sync *per_adv_sync,
struct bt_le_per_adv_sync_info *info)
@@ -322,6 +340,23 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
}
_IDF_ONLY
int bt_le_per_adv_sync_new_safe(uint16_t sync_handle,
uint8_t sid,
uint8_t phy,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
uint16_t conn_handle,
struct bt_le_per_adv_sync **out_sync)
{
int err;
bt_le_host_lock();
err = bt_le_per_adv_sync_new(sync_handle, sid, phy, interval, addr_type,
addr, conn_handle, out_sync);
bt_le_host_unlock();
return err;
}
int bt_le_per_adv_sync_delete(uint16_t sync_handle)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
@@ -551,6 +586,17 @@ static void past_features_unset(void)
}
_IDF_ONLY
void bt_le_per_adv_sync_state_reset(void)
{
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags, BT_PER_ADV_SYNC_SYNCED)) {
LOG_WRN("DeinitDropPaSync[%u][%04x]", i, per_adv_sync_pool[i].handle);
}
}
memset(per_adv_sync_pool, 0, sizeof(per_adv_sync_pool));
}
int bt_le_scan_init(void)
{
LOG_DBG("ScanInit");
+146 -16
View File
@@ -20,11 +20,25 @@
#include "common/host.h"
#include "common/iso.h"
#include "common/gatt.h"
#include "common/app/gap.h"
#include "common/app/gatt.h"
LOG_MODULE_REGISTER(ISO_TASK, CONFIG_BT_ISO_LOG_LEVEL);
/* Nothing to poll for - iso_ctrl_queue wakes the task for deinit. The dispatch
* monitor is the exception: its periodic dump is driven from this loop, so it
* needs a wakeup even while no event arrives. */
#if CONFIG_BT_ISO_DISPATCH_MONITOR
#define ISO_TASK_WAIT (ISO_STATS_DUMP_PERIOD_US / 1000 / portTICK_PERIOD_MS)
#else /* CONFIG_BT_ISO_DISPATCH_MONITOR */
#define ISO_TASK_WAIT portMAX_DELAY
#endif /* CONFIG_BT_ISO_DISPATCH_MONITOR */
/* Generous: expiry means a dispatch handler is wedged, which is a bug
* elsewhere. Deinit reports it upward rather than freeing under a live task. */
#define ISO_TASK_STOP_TIMEOUT (2000 / portTICK_PERIOD_MS)
/* Three priority tiers share one task via a queue set. The task drains
* critical before normal before floodable, so a flood of GAP reports cannot
* delay the latency-critical ISO data path. See common/task.h for the mapping.
@@ -32,10 +46,17 @@ LOG_MODULE_REGISTER(ISO_TASK, CONFIG_BT_ISO_LOG_LEVEL);
static BT_ISO_CTRL_BSS_ATTR QueueHandle_t iso_critical_queue;
static BT_ISO_CTRL_BSS_ATTR QueueHandle_t iso_normal_queue;
static BT_ISO_CTRL_BSS_ATTR QueueHandle_t iso_floodable_queue;
/* Not a tier: deinit-only wakeup, see ISO_CTRL_QUEUE_LEN. */
static BT_ISO_CTRL_BSS_ATTR QueueHandle_t iso_ctrl_queue;
static BT_ISO_CTRL_BSS_ATTR QueueSetHandle_t iso_queue_set;
static BT_ISO_CTRL_BSS_ATTR TaskHandle_t iso_task_handle;
/* Gate + handshake for deinit. iso_task_stopping also rejects new posts, so a
* producer cannot strand a payload on a queue nobody will drain. */
static BT_ISO_CTRL_BSS_ATTR volatile bool iso_task_stopping;
static BT_ISO_CTRL_BSS_ATTR SemaphoreHandle_t iso_task_stopped;
extern void bt_le_timer_handle_event(void *arg, size_t gen);
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
@@ -43,6 +64,7 @@ extern void bt_le_timer_handle_event(void *arg, size_t gen);
* OTS, so the shim lives there. Declared instead of included to keep esp_ble_iso
* free of audio headers; both live in the bt component, so the link resolves. */
extern void bt_le_l2cap_handle_event(void *data, size_t data_len);
extern void bt_le_l2cap_event_free(void *data);
#endif
#if CONFIG_BT_ISO_DISPATCH_MONITOR
@@ -90,6 +112,52 @@ void bt_le_iso_dispatch_stats_dump(void)
}
#endif /* CONFIG_BT_ISO_DISPATCH_MONITOR */
static void iso_item_release(const struct iso_queue_item *item)
{
switch (item->type) {
case ISO_QUEUE_ITEM_TYPE_TIMER_EVENT:
/* data is the k_work, data_len its generation counter - not a block. */
break;
case ISO_QUEUE_ITEM_TYPE_GATT_EVENT:
bt_le_gatt_event_free(item->data);
break;
case ISO_QUEUE_ITEM_TYPE_GAP_EVENT:
case ISO_QUEUE_ITEM_TYPE_EXT_ADV_REPORT:
case ISO_QUEUE_ITEM_TYPE_PER_ADV_REPORT:
bt_le_gap_event_free(item->data);
break;
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
case ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT:
bt_le_l2cap_event_free(item->data);
break;
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
default:
if (item->data) {
free(item->data);
}
break;
}
}
/* Runs on iso_task after the loop exits, so no producer can be mid-dispatch and
* the queues are provably empty when iso_queues_destroy() deletes them. */
static void iso_queues_drain(void)
{
struct iso_queue_item item = {0};
while (xQueueReceive(iso_critical_queue, &item, 0) == pdTRUE) {
iso_item_release(&item);
}
while (xQueueReceive(iso_normal_queue, &item, 0) == pdTRUE) {
iso_item_release(&item);
}
while (xQueueReceive(iso_floodable_queue, &item, 0) == pdTRUE) {
iso_item_release(&item);
}
}
static void iso_dispatch_item(const struct iso_queue_item *item)
{
#if CONFIG_BT_ISO_DISPATCH_MONITOR
@@ -127,9 +195,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
bt_le_iso_handle_rx_data(item->data, item->data_len);
break;
default:
if (item->data) {
free(item->data);
}
iso_item_release(item);
BT_LE_ASSERT(0);
break;
}
@@ -147,13 +213,11 @@ static void iso_task(void *p)
#endif /* CONFIG_BT_ISO_DISPATCH_MONITOR */
struct iso_queue_item item = {0};
while (1) {
/* Block until any tier has data. The returned member handle is ignored:
* we always service by strict priority below (critical > normal >
* floodable), processing one item per wakeup and re-checking critical
* first on the next loop. A pdFALSE receive is tolerated as a benign
* side effect of servicing queues outside xQueueSelectFromSet. */
(void)xQueueSelectFromSet(iso_queue_set, portMAX_DELAY);
while (!iso_task_stopping) {
/* The returned handle is ignored: service by strict priority instead
* (critical > normal > floodable), one item per wakeup. A pdFALSE
* receive is benign - that is what a deinit wakeup looks like. */
(void)xQueueSelectFromSet(iso_queue_set, ISO_TASK_WAIT);
if (xQueueReceive(iso_critical_queue, &item, 0) == pdTRUE) {
iso_dispatch_item(&item);
@@ -170,6 +234,14 @@ static void iso_task(void *p)
}
#endif /* CONFIG_BT_ISO_DISPATCH_MONITOR */
}
/* Draining here rather than in the deinit caller keeps payload ownership on
* a single task: no producer is mid-post and no consumer is mid-dispatch. */
iso_queues_drain();
xSemaphoreGive(iso_task_stopped);
vTaskDelete(NULL);
}
int bt_le_iso_task_post(enum iso_queue_item_type type,
@@ -180,6 +252,13 @@ int bt_le_iso_task_post(enum iso_queue_item_type type,
TickType_t wait;
int ret;
/* No consumer before init or after deinit began, so accepting would strand
* the payload (callers free on failure). Distinct from the -1 below: this
* one persists, a full queue is transient. */
if (iso_task_handle == NULL || iso_task_stopping) {
return -ESHUTDOWN;
}
item.type = type;
item.data = data;
item.data_len = data_len;
@@ -249,6 +328,7 @@ static void iso_queues_destroy(void)
iso_queue_destroy_one(&iso_critical_queue);
iso_queue_destroy_one(&iso_normal_queue);
iso_queue_destroy_one(&iso_floodable_queue);
iso_queue_destroy_one(&iso_ctrl_queue);
if (iso_queue_set) {
vQueueDelete(iso_queue_set);
@@ -262,19 +342,31 @@ int bt_le_iso_task_init(void)
LOG_DBG("IsoTaskInit");
/* Reset here, not at definition, so a deinit/re-init cycle starts clean. */
iso_task_stopping = false;
iso_task_stopped = xSemaphoreCreateBinary();
if (iso_task_stopped == NULL) {
LOG_ERR("IsoTaskSemCreateFail");
return -EIO;
}
iso_critical_queue = xQueueCreate(ISO_CRITICAL_QUEUE_LEN, ISO_QUEUE_ITEM_SIZE);
iso_normal_queue = xQueueCreate(ISO_NORMAL_QUEUE_LEN, ISO_QUEUE_ITEM_SIZE);
iso_floodable_queue = xQueueCreate(ISO_FLOODABLE_QUEUE_LEN, ISO_QUEUE_ITEM_SIZE);
iso_ctrl_queue = xQueueCreate(ISO_CTRL_QUEUE_LEN, ISO_QUEUE_ITEM_SIZE);
iso_queue_set = xQueueCreateSet(ISO_QUEUE_SET_LEN);
if (iso_critical_queue == NULL || iso_normal_queue == NULL ||
iso_floodable_queue == NULL || iso_queue_set == NULL) {
iso_floodable_queue == NULL || iso_ctrl_queue == NULL ||
iso_queue_set == NULL) {
LOG_ERR("IsoQCreateFail");
goto fail;
}
if (xQueueAddToSet(iso_critical_queue, iso_queue_set) != pdPASS ||
xQueueAddToSet(iso_normal_queue, iso_queue_set) != pdPASS ||
xQueueAddToSet(iso_floodable_queue, iso_queue_set) != pdPASS) {
xQueueAddToSet(iso_floodable_queue, iso_queue_set) != pdPASS ||
xQueueAddToSet(iso_ctrl_queue, iso_queue_set) != pdPASS) {
LOG_ERR("IsoQSetAddFail");
goto fail;
}
@@ -295,22 +387,60 @@ int bt_le_iso_task_init(void)
fail:
iso_queues_destroy();
vSemaphoreDelete(iso_task_stopped);
iso_task_stopped = NULL;
return -EIO;
}
void bt_le_iso_task_deinit(void)
int bt_le_iso_task_deinit(void)
{
struct iso_queue_item item = {0};
LOG_DBG("IsoTaskDeinit");
if (iso_task_handle) {
vTaskDelete(iso_task_handle);
iso_task_handle = NULL;
if (iso_task_handle == NULL) {
return 0;
}
/* This blocks on the task's own exit, so calling it from iso_task would
* wait for itself forever. */
if (xTaskGetCurrentTaskHandle() == iso_task_handle) {
LOG_ERR("IsoTaskDeinitFromSelf");
return -EDEADLK;
}
/* Stops new posts as well, so the queues can only shrink from here. */
iso_task_stopping = true;
/* Setting the flag cannot wake a task blocked on the set. Posted after it so
* whichever select consumes this token re-checks the flag as true; deinit is
* the sole producer of a one-deep queue, so the send cannot fail. */
(void)xQueueSend(iso_ctrl_queue, &item, 0);
if (xSemaphoreTake(iso_task_stopped, ISO_TASK_STOP_TIMEOUT) != pdTRUE) {
/* A dispatch handler is wedged. Deleting the queues now would pull them
* out from under a live task, so leave everything in place and let the
* caller abort the teardown instead. */
LOG_ERR("IsoTaskStopTimeout");
iso_task_stopping = false;
/* Take the wakeup back: nothing reads this queue, so leaving it there
* would make the next attempt's send fail and never wake the task. */
(void)xQueueReceive(iso_ctrl_queue, &item, 0);
return -ETIMEDOUT;
}
iso_task_handle = NULL;
#if CONFIG_BT_ISO_DISPATCH_MONITOR
/* Task is gone: no concurrent writer, safe to read the stats. */
bt_le_iso_dispatch_stats_dump();
#endif /* CONFIG_BT_ISO_DISPATCH_MONITOR */
/* Drained by the task before it exited, so these are empty. */
iso_queues_destroy();
vSemaphoreDelete(iso_task_stopped);
iso_task_stopped = NULL;
return 0;
}
+68
View File
@@ -2445,6 +2445,23 @@ int bt_iso_big_register_cb(struct bt_iso_big_cb *cb)
return 0;
}
int bt_iso_big_unregister_cb(struct bt_iso_big_cb *cb)
{
CHECKIF(cb == NULL) {
LOG_ERR("BigCbNull");
return -EINVAL;
}
if (!sys_slist_find_and_remove(&iso_big_cbs, &cb->_node)) {
LOG_ERR("BigCbNotReg[%p]", cb);
return -EINVAL;
}
return 0;
}
#if defined(CONFIG_BT_ISO_BROADCASTER)
static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big,
struct bt_iso_big_create_param *param)
@@ -3553,3 +3570,54 @@ void bt_iso_reset_safe(void)
bt_iso_reset();
bt_le_host_unlock();
}
void bt_le_iso_state_reset(void)
{
/* Clear the CIG/BIG pools, the registered server and the BIG callback list.
* Not bt_iso_reset(), which is the HCI-reset path and tears down live
* channels. Call from init, before anything registers. */
LOG_DBG("IsoStateReset");
#if CONFIG_BT_ISO_CENTRAL
memset(cigs, 0, sizeof(cigs));
#endif /* CONFIG_BT_ISO_CENTRAL */
#if CONFIG_BT_ISO_PERIPHERAL
iso_server = NULL;
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#if CONFIG_BT_ISO_BROADCAST
memset(bigs, 0, sizeof(bigs));
sys_slist_init(&iso_big_cbs);
#endif /* CONFIG_BT_ISO_BROADCAST */
}
size_t bt_le_iso_report_busy(void)
{
size_t busy = 0;
/* Number of CIG/BIG slots still allocated; each is logged at ERROR. */
#if CONFIG_BT_ISO_CENTRAL
/* A CIS disconnect only moves the CIG to INACTIVE; nothing but
* bt_iso_cig_terminate() frees the slot and the controller's CIG. */
for (size_t i = 0; i < ARRAY_SIZE(cigs); i++) {
if (cigs[i].state != BT_ISO_CIG_STATE_IDLE) {
LOG_ERR("DeinitBusyCig[%u][state=%u]", i, cigs[i].state);
busy++;
}
}
#endif /* CONFIG_BT_ISO_CENTRAL */
#if CONFIG_BT_ISO_BROADCAST
for (size_t i = 0; i < ARRAY_SIZE(bigs); i++) {
if (atomic_test_bit(bigs[i].flags, BT_BIG_INITIALIZED)) {
LOG_ERR("DeinitBusyBig[%u]", i);
busy++;
}
}
#endif /* CONFIG_BT_ISO_BROADCAST */
return busy;
}
@@ -20,11 +20,11 @@ void bt_le_assert(const char *tag, size_t info,
/* Use instead of assert(): CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE sets
* -DNDEBUG, which turns every assert() into a no-op and lets execution fall
* through onto the very pointer it was guarding. This one always checks. */
#define BT_LE_ASSERT(_cond) \
do { \
if (!(_cond)) { \
bt_le_assert(#_cond, 0, __FILE__, __LINE__, __func__); \
} \
#define BT_LE_ASSERT(_cond) \
do { \
if (!(_cond)) { \
bt_le_assert(#_cond, 0, __FILE_NAME__, __LINE__, __func__); \
} \
} while (0)
#ifdef __cplusplus
+1 -1
View File
@@ -34,7 +34,7 @@ static void iso_timer_cb(void *arg)
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, work->gen);
if (err) {
LOG_ERR("TimerCbPostFail[%d]", err);
ISO_POST_FAIL_LOG(err, "TimerCbPostFail[%d]", err);
}
}
@@ -422,6 +422,19 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
*/
int bt_le_per_adv_sync_cb_register(struct bt_le_per_adv_sync_cb *cb);
/**
* @brief Unregister periodic advertising sync callbacks.
*
* Counterpart of @ref bt_le_per_adv_sync_cb_register(). The list is boot-scoped,
* so a module that stops listening must remove itself or keep being invoked.
*
* @param cb Callback struct previously registered.
*
* @retval 0 Success.
* @retval -ENOENT if @p cb was not registered.
*/
int bt_le_per_adv_sync_cb_unregister(struct bt_le_per_adv_sync_cb *cb);
/** LE scan parameters */
struct bt_le_scan_param {
/** Scan type. @ref BT_LE_SCAN_TYPE_ACTIVE or @ref BT_LE_SCAN_TYPE_PASSIVE. */