From af9ae1c0aae95e7523e97325a28f64251ee344f0 Mon Sep 17 00:00:00 2001 From: Liu Linyan Date: Sat, 25 Jul 2026 19:50:04 +0800 Subject: [PATCH] fix(ble_audio): Miscellaneous fixes for ISO & LE Audio (stage 2) --- components/bt/esp_ble_audio/CMakeLists.txt | 12 + components/bt/esp_ble_audio/Kconfig.in | 3 + .../esp_ble_audio/api/esp_ble_audio_bap_api.c | 54 +- .../esp_ble_audio/api/esp_ble_audio_cap_api.c | 16 +- .../api/esp_ble_audio_codec_api.c | 32 +- .../api/esp_ble_audio_common_api.c | 2 +- .../api/esp_ble_audio_csip_api.c | 4 +- .../api/esp_ble_audio_gmap_api.c | 15 +- .../esp_ble_audio/api/esp_ble_audio_has_api.c | 17 +- .../api/esp_ble_audio_media_proxy_api.c | 33 +- .../api/esp_ble_audio_micp_api.c | 17 +- .../esp_ble_audio/api/esp_ble_audio_pbp_api.c | 2 +- .../esp_ble_audio/api/esp_ble_audio_tbs_api.c | 23 +- .../api/esp_ble_audio_tmap_api.c | 21 +- .../esp_ble_audio/api/esp_ble_audio_vcp_api.c | 17 +- .../api/include/esp_ble_audio_common_api.h | 2 +- .../host/adapter/bluedroid/profiles/vcs.c | 13 +- .../host/adapter/bluedroid/server.c | 34 +- .../host/adapter/nimble/profiles/ascs.c | 21 +- .../host/adapter/nimble/profiles/bass.c | 19 +- .../host/adapter/nimble/profiles/csis.c | 37 +- .../host/adapter/nimble/profiles/gmas.c | 4 +- .../host/adapter/nimble/profiles/has.c | 33 +- .../host/adapter/nimble/profiles/mcs.c | 92 +- .../host/adapter/nimble/profiles/mics.c | 56 +- .../host/adapter/nimble/profiles/pacs.c | 24 +- .../host/adapter/nimble/profiles/tbs.c | 28 +- .../host/adapter/nimble/profiles/tmas.c | 2 +- .../host/adapter/nimble/profiles/vcs.c | 71 +- .../host/adapter/nimble/server.c | 78 +- .../host/common/include/common/init.h | 2 + .../bt/esp_ble_audio/host/common/init.c | 53 +- .../host/services/ots/Kconfig.ots.in | 4 + .../services/ots/adapter/bluedroid/l2cap.c | 802 +++++++++++++++++ .../host/services/ots/adapter/l2cap.c | 829 ++++++++++++++++++ .../host/services/ots/adapter/l2cap.h | 117 +++ .../host/services/ots/adapter/nimble/l2cap.c | 506 +++++++++++ .../bt/esp_ble_audio/host/services/ots/ots.c | 116 ++- .../host/services/ots/ots_client.c | 205 ++++- .../host/services/ots/ots_client_internal.h | 1 + .../host/services/ots/ots_dir_list.c | 10 +- .../host/services/ots/ots_dir_list_internal.h | 2 + .../host/services/ots/ots_internal.h | 8 +- .../host/services/ots/ots_l2cap.c | 51 +- .../host/services/ots/ots_l2cap_internal.h | 10 +- .../host/services/ots/ots_oacp.c | 101 ++- .../host/services/ots/ots_oacp_internal.h | 1 + .../host/services/ots/ots_obj_manager.c | 3 +- .../services/ots/ots_obj_manager_internal.h | 1 + .../host/services/ots/ots_olcp.c | 39 +- .../host/services/ots/ots_olcp_internal.h | 1 + .../include/zephyr/bluetooth/audio/bap.h | 4 + .../include/zephyr/bluetooth/audio/gmap.h | 6 +- .../include/zephyr/bluetooth/audio/has.h | 1 + .../zephyr/bluetooth/audio/media_proxy.h | 1 + .../include/zephyr/bluetooth/audio/micp.h | 1 + .../include/zephyr/bluetooth/audio/tbs.h | 2 + .../include/zephyr/bluetooth/audio/tmap.h | 1 + .../include/zephyr/bluetooth/audio/vcp.h | 1 + components/bt/esp_ble_audio/lib/lib | 2 +- components/bt/esp_ble_iso/CMakeLists.txt | 16 +- .../esp_ble_iso/api/esp_ble_iso_common_api.c | 30 +- .../api/include/esp_ble_iso_common_api.h | 2 +- .../esp_ble_iso/host/adapter/bluedroid/gap.c | 49 +- .../host/adapter/bluedroid/gatt/gatt.c | 396 ++++++--- .../bluedroid/include/bluedroid/l2cap.h | 33 + .../esp_ble_iso/host/adapter/bluedroid/iso.c | 56 +- .../bt/esp_ble_iso/host/adapter/nimble/gap.c | 18 +- .../host/adapter/nimble/gatt/gatt.c | 25 +- .../host/adapter/nimble/gatt/gatt.db.c | 62 +- .../host/adapter/nimble/gatt/gatt.nrp.c | 110 +-- .../host/adapter/nimble/include/nimble/gatt.h | 7 +- .../bt/esp_ble_iso/host/adapter/nimble/iso.c | 62 +- .../esp_ble_iso/host/adapter/nimble/l2cap.c | 336 ------- .../bt/esp_ble_iso/host/common/app/gap.c | 72 +- components/bt/esp_ble_iso/host/common/conn.c | 151 ++-- components/bt/esp_ble_iso/host/common/gatt.c | 122 +-- components/bt/esp_ble_iso/host/common/hci.c | 2 +- components/bt/esp_ble_iso/host/common/host.c | 26 +- .../host/common/include/common/app/gap.h | 5 +- .../host/common/include/common/conn.h | 2 - .../host/common/include/common/gatt.h | 5 +- .../host/common/include/common/host.h | 2 +- .../host/common/include/common/iso.h | 1 + .../host/common/include/common/l2cap.h | 64 -- .../host/common/include/common/scan.h | 2 + .../host/common/include/common/task.h | 1 + components/bt/esp_ble_iso/host/common/iso.c | 53 +- components/bt/esp_ble_iso/host/common/l2cap.c | 533 ----------- components/bt/esp_ble_iso/host/common/scan.c | 23 +- components/bt/esp_ble_iso/host/common/task.c | 26 +- components/bt/esp_ble_iso/host/iso/iso.c | 102 ++- components/bt/esp_ble_iso/host/utils/assert.c | 30 + .../host/utils/include/utils/assert.h | 32 + components/bt/esp_ble_iso/host/utils/keys.c | 6 +- components/bt/esp_ble_iso/host/utils/timer.c | 61 +- .../bt/esp_ble_iso/include/zephyr/autoconf.h | 2 - .../include/zephyr/bluetooth/conn.h | 39 +- .../include/zephyr/bluetooth/iso.h | 2 + .../bt/esp_ble_iso/include/zephyr/kernel.h | 56 +- .../esp_ble_iso/include/zephyr/logging/log.h | 6 +- .../esp_ble_iso/include/zephyr/sys/__assert.h | 8 +- .../bt/esp_ble_iso/include/zephyr/toolchain.h | 2 - .../bap/broadcast_sink/README.md | 5 +- .../bap/broadcast_sink/main/main.c | 53 +- .../bap/unicast_client/README.md | 4 +- .../bap/unicast_client/main/main.c | 138 ++- .../bap/unicast_client/main/stream_tx.c | 6 + .../esp_ble_audio/cap/acceptor/README.md | 16 +- .../acceptor/main/cap_acceptor_broadcast.c | 64 +- .../esp_ble_audio/tmap/bmr/README.md | 3 +- .../tmap/bmr/main/bap_broadcast_sink.c | 51 +- .../tmap/peripheral/main/bap_unicast_sr.c | 10 +- 113 files changed, 4547 insertions(+), 2104 deletions(-) create mode 100644 components/bt/esp_ble_audio/host/services/ots/adapter/bluedroid/l2cap.c create mode 100644 components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.c create mode 100644 components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.h create mode 100644 components/bt/esp_ble_audio/host/services/ots/adapter/nimble/l2cap.c create mode 100644 components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/l2cap.h delete mode 100644 components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c delete mode 100644 components/bt/esp_ble_iso/host/common/include/common/l2cap.h delete mode 100644 components/bt/esp_ble_iso/host/common/l2cap.c create mode 100644 components/bt/esp_ble_iso/host/utils/assert.c create mode 100644 components/bt/esp_ble_iso/host/utils/include/utils/assert.h diff --git a/components/bt/esp_ble_audio/CMakeLists.txt b/components/bt/esp_ble_audio/CMakeLists.txt index 8ed17039ce2..607eed952da 100644 --- a/components/bt/esp_ble_audio/CMakeLists.txt +++ b/components/bt/esp_ble_audio/CMakeLists.txt @@ -161,13 +161,25 @@ endif() if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT) list(APPEND ble_audio_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/host/services" "${CMAKE_CURRENT_LIST_DIR}/host/services/ots" ) list(APPEND ble_audio_srcs "${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_l2cap.c" + "${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/l2cap.c" ) + if(CONFIG_BT_NIMBLE_ENABLED) + list(APPEND ble_audio_srcs + "${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/nimble/l2cap.c" + ) + else() + list(APPEND ble_audio_srcs + "${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/bluedroid/l2cap.c" + ) + endif() + if(CONFIG_BT_OTS) list(APPEND ble_audio_srcs "${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_oacp.c" diff --git a/components/bt/esp_ble_audio/Kconfig.in b/components/bt/esp_ble_audio/Kconfig.in index 7a81131457a..f4da9615a9f 100644 --- a/components/bt/esp_ble_audio/Kconfig.in +++ b/components/bt/esp_ble_audio/Kconfig.in @@ -6,6 +6,9 @@ config BT_AUDIO bool # hidden + # esp_ble_audio needs esp_ble_iso's headers () and symbols + # (bt_gatt_*/bt_conn_*/k_*), and both are gated on BT_ISO. + select BT_ISO help This option enables Bluetooth Audio support. The specific features that are available may depend on other features diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c index d7a401a61b7..3e458ea8ad5 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c @@ -6,11 +6,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + #include "esp_ble_audio_bap_api.h" #if CONFIG_BT_BAP_UNICAST_SERVER esp_err_t esp_ble_audio_bap_unicast_server_register(const esp_ble_audio_bap_unicast_server_register_param_t *param) { + esp_err_t ret = ESP_OK; int err; if (param == NULL || @@ -20,20 +23,26 @@ esp_err_t esp_ble_audio_bap_unicast_server_register(const esp_ble_audio_bap_unic return ESP_ERR_INVALID_ARG; } - err = bt_bap_unicast_server_register_safe(param); + bt_le_host_lock(); + + err = bt_bap_unicast_server_register(param); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_ascs_init(); if (err) { - bt_bap_unicast_server_unregister_safe(); - return ESP_FAIL; + bt_bap_unicast_server_unregister(); + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_bap_unicast_server_unregister(void) @@ -150,6 +159,8 @@ esp_err_t esp_ble_audio_bap_unicast_group_reconfig(esp_ble_audio_bap_unicast_gro int err; if (unicast_group == NULL || param == NULL || + param->params == NULL || + param->params_count == 0 || param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { return ESP_ERR_INVALID_ARG; } @@ -727,22 +738,29 @@ esp_err_t esp_ble_audio_bap_broadcast_sink_delete(esp_ble_audio_bap_broadcast_si #if CONFIG_BT_BAP_SCAN_DELEGATOR esp_err_t esp_ble_audio_bap_scan_delegator_register(esp_ble_audio_bap_scan_delegator_cb_t *cb) { + esp_err_t ret = ESP_OK; int err; - err = bt_bap_scan_delegator_register_safe(cb); + bt_le_host_lock(); + + err = bt_bap_scan_delegator_register(cb); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_bass_init(); if (err) { - bt_bap_scan_delegator_unregister_safe(); - return ESP_FAIL; + bt_bap_scan_delegator_unregister(); + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_bap_scan_delegator_unregister(void) @@ -798,7 +816,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_add_src(const esp_ble_audio_bap_scan_ param->sid > BT_GAP_SID_MAX || param->pa_state > ESP_BLE_AUDIO_BAP_PA_STATE_NO_PAST || param->encrypt_state > ESP_BLE_AUDIO_BAP_BIG_ENC_STATE_BAD_CODE || - param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS || + (param->num_subgroups > 0 && param->subgroups == NULL)) { return ESP_ERR_INVALID_ARG; } @@ -818,7 +837,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_mod_src(const esp_ble_audio_bap_scan_ if (param == NULL || param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX || - param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS || + (param->num_subgroups > 0 && param->subgroups == NULL)) { return ESP_ERR_INVALID_ARG; } @@ -959,7 +979,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_add_src(uint16_t conn_handle, param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX || param->addr.type > BT_ADDR_LE_RANDOM || param->adv_sid > BT_GAP_SID_MAX || - param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS || + (param->num_subgroups > 0 && param->subgroups == NULL)) { return ESP_ERR_INVALID_ARG; } @@ -989,7 +1010,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_mod_src(uint16_t conn_handle, int err; if (param == NULL || - param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) { + param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS || + (param->num_subgroups > 0 && param->subgroups == NULL)) { return ESP_ERR_INVALID_ARG; } @@ -1288,7 +1310,7 @@ esp_err_t esp_ble_audio_bap_base_foreach_subgroup(const esp_ble_audio_bap_base_t } err = bt_bap_base_foreach_subgroup(base, func, user_data); - if (err) { + if (err && err != -ECANCELED) { return ESP_FAIL; } @@ -1411,7 +1433,7 @@ esp_err_t esp_ble_audio_bap_base_subgroup_foreach_bis(const esp_ble_audio_bap_ba } err = bt_bap_base_subgroup_foreach_bis(subgroup, func, user_data); - if (err) { + if (err && err != -ECANCELED) { return ESP_FAIL; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c index a6959bac8da..7d8647efd10 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c @@ -128,7 +128,10 @@ esp_err_t esp_ble_audio_cap_unicast_group_create(const esp_ble_audio_cap_unicast { int err; - if (param == NULL || unicast_group == NULL) { + if (param == NULL || unicast_group == NULL || + param->params == NULL || + param->params_count == 0 || + param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { return ESP_ERR_INVALID_ARG; } @@ -145,7 +148,10 @@ esp_err_t esp_ble_audio_cap_unicast_group_reconfig(esp_ble_audio_cap_unicast_gro { int err; - if (unicast_group == NULL || param == NULL) { + if (unicast_group == NULL || param == NULL || + param->params == NULL || + param->params_count == 0 || + param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { return ESP_ERR_INVALID_ARG; } @@ -255,7 +261,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_start(const esp_ble_audio_ca int err; if (param == NULL || param->count == 0 || param->stream_params == NULL || - param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { + param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) { return ESP_ERR_INVALID_ARG; } @@ -272,7 +278,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_update(const esp_ble_audio_c int err; if (param == NULL || param->count == 0 || param->stream_params == NULL || - param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { + param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) { return ESP_ERR_INVALID_ARG; } @@ -289,7 +295,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_stop(const esp_ble_audio_cap int err; if (param == NULL || param->count == 0 || param->streams == NULL || - param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) { + param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_codec_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_codec_api.c index 3dec72fa948..59891a22401 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_codec_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_codec_api.c @@ -299,7 +299,7 @@ esp_err_t esp_ble_audio_codec_cfg_set_val(esp_ble_audio_codec_cfg_t *codec_cfg, if (codec_cfg == NULL || codec_cfg->data == NULL || data == NULL || - data_len == 0 || data_len > UINT8_MAX) { + data_len == 0 || data_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -357,7 +357,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_val(esp_ble_audio_codec_cfg_t *codec_ if (codec_cfg == NULL || codec_cfg->meta == NULL || data == NULL || - data_len == 0 || data_len > UINT8_MAX) { + data_len == 0 || data_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -490,7 +490,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_program_info(esp_ble_audio_codec_cfg_ if (codec_cfg == NULL || codec_cfg->meta == NULL || program_info == NULL || - program_info_len == 0 || program_info_len > UINT8_MAX) { + program_info_len == 0 || program_info_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -565,7 +565,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_ccid_list(esp_ble_audio_codec_cfg_t * if (codec_cfg == NULL || codec_cfg->meta == NULL || ccid_list == NULL || - ccid_list_len == 0 || ccid_list_len > UINT8_MAX) { + ccid_list_len == 0 || ccid_list_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -643,7 +643,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_program_info_uri(esp_ble_audio_codec_ if (codec_cfg == NULL || codec_cfg->meta == NULL || program_info_uri == NULL || - program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX) { + program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -795,7 +795,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_broadcast_name(esp_ble_audio_codec_cf if (codec_cfg == NULL || codec_cfg->meta == NULL || broadcast_name == NULL || - broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX) { + broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -836,7 +836,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_extended(esp_ble_audio_codec_cfg_t *c if (codec_cfg == NULL || codec_cfg->meta == NULL || extended_meta == NULL || - extended_meta_len == 0 || extended_meta_len > UINT8_MAX) { + extended_meta_len == 0 || extended_meta_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -877,7 +877,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_vendor(esp_ble_audio_codec_cfg_t *cod if (codec_cfg == NULL || codec_cfg->meta == NULL || vendor_meta == NULL || - vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX) { + vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -918,7 +918,7 @@ esp_err_t esp_ble_audio_codec_cap_set_val(esp_ble_audio_codec_cap_t *codec_cap, if (codec_cap == NULL || codec_cap->data == NULL || data == NULL || - data_len == 0 || data_len > UINT8_MAX) { + data_len == 0 || data_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1181,7 +1181,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_val(esp_ble_audio_codec_cap_t *codec_ if (codec_cap == NULL || codec_cap->meta == NULL || data == NULL || - data_len == 0 || data_len > UINT8_MAX) { + data_len == 0 || data_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1313,7 +1313,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_program_info(esp_ble_audio_codec_cap_ if (codec_cap == NULL || codec_cap->meta == NULL || program_info == NULL || - program_info_len == 0 || program_info_len > UINT8_MAX) { + program_info_len == 0 || program_info_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1388,7 +1388,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_ccid_list(esp_ble_audio_codec_cap_t * if (codec_cap == NULL || codec_cap->meta == NULL || ccid_list == NULL || - ccid_list_len == 0 || ccid_list_len > UINT8_MAX) { + ccid_list_len == 0 || ccid_list_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1466,7 +1466,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_program_info_uri(esp_ble_audio_codec_ if (codec_cap == NULL || codec_cap->meta == NULL || program_info_uri == NULL || - program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX) { + program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1618,7 +1618,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_broadcast_name(esp_ble_audio_codec_ca if (codec_cap == NULL || codec_cap->meta == NULL || broadcast_name == NULL || - broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX) { + broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1659,7 +1659,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_extended(esp_ble_audio_codec_cap_t *c if (codec_cap == NULL || codec_cap->meta == NULL || extended_meta == NULL || - extended_meta_len == 0 || extended_meta_len > UINT8_MAX) { + extended_meta_len == 0 || extended_meta_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } @@ -1700,7 +1700,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_vendor(esp_ble_audio_codec_cap_t *cod if (codec_cap == NULL || codec_cap->meta == NULL || vendor_meta == NULL || - vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX) { + vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX - 1) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_common_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_common_api.c index 6fd5765848f..536a43c0e2a 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_common_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_common_api.c @@ -65,7 +65,7 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle) return ESP_OK; } -void esp_ble_audio_gap_app_post_event(uint8_t type, void *param) +void esp_ble_audio_gap_app_post_event(uint16_t type, void *param) { bt_le_gap_app_post_event(type, param); } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c index a0de9a5f417..3550b4d4ea7 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c @@ -276,7 +276,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_lock(const esp_ble_audio_csip_set_c { int err; - if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) { + if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) { return ESP_ERR_INVALID_ARG; } @@ -294,7 +294,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_release(const esp_ble_audio_csip_se { int err; - if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) { + if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_gmap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_gmap_api.c index c6282f61073..2fc423fb131 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_gmap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_gmap_api.c @@ -94,27 +94,30 @@ static bool valid_gmap_features(esp_ble_audio_gmap_role_t role, if ((role & ESP_BLE_AUDIO_GMAP_ROLE_UGG) != 0) { esp_ble_audio_gmap_ugg_feat_t ugg_feat = features.ugg_feat; + /* UGG TX (multiplex / 96kbps source) configures peer Sink ASEs → ASE_SNK. + * UGG RX (multisink) configures peer Source ASEs → ASE_SRC. + * (BT_AUDIO_TX/RX: client ASE_SNK = TX, ASE_SRC = RX.) */ if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTIPLEX) != 0 && - CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) { + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0) { /* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTIPLEX with - * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0. + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0. */ return false; } if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 && - CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) { + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0) { /* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_96KBPS_SOURCE with - * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0. + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0. */ return false; } if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTISINK) != 0 && - (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 || + (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT < 2 || CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) { /* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTISINK with - * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT or + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT or * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2. */ return false; diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c index 7bb847c555b..331be5b9703 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c @@ -144,28 +144,35 @@ esp_err_t esp_ble_audio_has_client_preset_prev(esp_ble_audio_has_t *has, bool sy #if CONFIG_BT_HAS esp_err_t esp_ble_audio_has_register(const esp_ble_audio_has_features_param_t *features) { + esp_err_t ret = ESP_OK; int err; if (features == NULL) { return ESP_ERR_INVALID_ARG; } - err = bt_has_register_safe(features); + bt_le_host_lock(); + + err = bt_has_register(features); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_has_init(); if (err) { - /* TODO: rollback register_safe once lib exposes an unregister API; + /* TODO: rollback register once lib exposes an unregister API; * retry will hit -EALREADY. Only reachable on GATT alloc failure. */ - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_has_preset_register(const esp_ble_audio_has_preset_register_param_t *param) diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c index 6169851617e..c1629079fa0 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c @@ -288,7 +288,8 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_set_next_track_id(esp_ble_audio_media_p { int err; - if (player == NULL || ESP_BLE_AUDIO_MCS_VALID_OBJ_ID(id) == false) { + if (player == NULL || (IS_ENABLED(CONFIG_BT_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) && + ESP_BLE_AUDIO_MCS_VALID_OBJ_ID(id) == false)) { return ESP_ERR_INVALID_ARG; } @@ -415,12 +416,19 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_get_media_state(esp_ble_audio_media_pla return ESP_OK; } +#define MCS_VALID_OP(opcode) \ + (IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PLAY, ESP_BLE_AUDIO_MCS_OPC_STOP) || \ + (opcode == ESP_BLE_AUDIO_MCS_OPC_MOVE_RELATIVE) || \ + IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_SEGMENT, ESP_BLE_AUDIO_MCS_OPC_GOTO_SEGMENT) || \ + IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_TRACK, ESP_BLE_AUDIO_MCS_OPC_GOTO_TRACK) || \ + IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_GROUP, ESP_BLE_AUDIO_MCS_OPC_GOTO_GROUP)) + esp_err_t esp_ble_audio_media_proxy_ctrl_send_command(esp_ble_audio_media_player_t *player, const esp_ble_audio_mpl_cmd_t *command) { int err; - if (player == NULL || command == NULL) { + if (player == NULL || command == NULL || MCS_VALID_OP(command->opcode) == false) { return ESP_ERR_INVALID_ARG; } @@ -453,7 +461,9 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_send_search(esp_ble_audio_media_player_ { int err; - if (player == NULL || search == NULL) { + if (player == NULL || search == NULL || + IN_RANGE(search->len, ESP_BLE_AUDIO_SEARCH_LEN_MIN, + ESP_BLE_AUDIO_SEARCH_LEN_MAX) == false) { return ESP_ERR_INVALID_ARG; } @@ -561,24 +571,31 @@ esp_err_t esp_ble_audio_media_proxy_pl_register(esp_ble_audio_media_proxy_pl_cal esp_err_t esp_ble_audio_media_proxy_pl_init(void) { + esp_err_t ret = ESP_OK; int err; - err = bt_media_proxy_pl_init_safe(); + bt_le_host_lock(); + + err = bt_media_proxy_pl_init(); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_media_proxy_pl_init(); if (err) { - /* TODO: rollback pl_init_safe once lib exposes an undo API; + /* TODO: rollback pl_init once lib exposes an undo API; * retry will hit -EALREADY. Only reachable on GATT alloc failure. */ - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #endif /* CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_media_proxy_pl_set_player_name(char *name) diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_micp_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_micp_api.c index 4c836ef448e..b7b26162037 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_micp_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_micp_api.c @@ -10,6 +10,7 @@ #if CONFIG_BT_MICP_MIC_DEV esp_err_t esp_ble_audio_micp_mic_dev_register(esp_ble_audio_micp_mic_dev_register_param_t *param) { + esp_err_t ret = ESP_OK; int err; if (param == NULL || @@ -17,22 +18,28 @@ esp_err_t esp_ble_audio_micp_mic_dev_register(esp_ble_audio_micp_mic_dev_registe return ESP_ERR_INVALID_ARG; } - err = bt_micp_mic_dev_register_safe(param); + bt_le_host_lock(); + + err = bt_micp_mic_dev_register(param); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_micp_mic_dev_init(); if (err) { - /* TODO: rollback register_safe once lib exposes an unregister API; + /* TODO: rollback register once lib exposes an unregister API; * retry will hit -EALREADY. Only reachable on GATT alloc failure. */ - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_micp_mic_dev_included_get(esp_ble_audio_micp_included_t *included) diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_pbp_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_pbp_api.c index be837df7ec0..3f7523fb281 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_pbp_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_pbp_api.c @@ -16,7 +16,7 @@ esp_err_t esp_ble_audio_pbp_get_announcement(const uint8_t meta[], size_t meta_l int err; if (pba_data_buf == NULL || - pba_data_buf->size < (meta_len + ESP_BLE_AUDIO_PBP_MIN_PBA_SIZE)) { + net_buf_simple_tailroom(pba_data_buf) < (meta_len + ESP_BLE_AUDIO_PBP_MIN_PBA_SIZE)) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c index 8169c4bb679..5dcc3a58a9a 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c @@ -337,27 +337,34 @@ static bool valid_register_param(const esp_ble_audio_tbs_register_param_t *param esp_err_t esp_ble_audio_tbs_register_bearer(const esp_ble_audio_tbs_register_param_t *param, uint8_t *bearer_index) { - int ret; + esp_err_t ret = ESP_OK; + int err; if (param == NULL || valid_register_param(param) == false || bearer_index == NULL) { return ESP_ERR_INVALID_ARG; } - ret = bt_tbs_register_bearer_safe(param); - if (ret < 0) { - return ESP_FAIL; + bt_le_host_lock(); + + err = bt_tbs_register_bearer(param); + if (err < 0) { + ret = ESP_FAIL; + goto end; } - *bearer_index = ret; + *bearer_index = err; #if BLE_AUDIO_SVC_DEFERRED_ADD if (param->gtbs ? bt_le_gtbs_init() : bt_le_tbs_init()) { - bt_tbs_unregister_bearer_safe(*bearer_index); - return ESP_FAIL; + bt_tbs_unregister_bearer(*bearer_index); + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_tbs_unregister_bearer(uint8_t bearer_index) diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_tmap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_tmap_api.c index 14f3197dc88..e2330ea0743 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_tmap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_tmap_api.c @@ -11,24 +11,31 @@ #if CONFIG_BT_TMAP esp_err_t esp_ble_audio_tmap_register(esp_ble_audio_tmap_role_t role) { + esp_err_t ret = ESP_OK; int err; - err = bt_tmap_register_safe(role); + bt_le_host_lock(); + + err = bt_tmap_register(role); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_tmas_init(); if (err) { - /* TODO: rollback register_safe once lib exposes an unregister API; + /* TODO: rollback register once lib exposes an unregister API; * retry will hit -EALREADY. Only reachable on GATT alloc failure. */ - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_tmap_discover(uint16_t conn_handle, @@ -38,6 +45,10 @@ esp_err_t esp_ble_audio_tmap_discover(uint16_t conn_handle, void *conn; int err; + if (tmap_cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + bt_le_host_lock(); conn = bt_le_acl_conn_find(conn_handle); diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c index 5e90d3bce7e..7ff0ef48500 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c @@ -26,6 +26,7 @@ esp_err_t esp_ble_audio_vcp_vol_rend_included_get(esp_ble_audio_vcp_included_t * esp_err_t esp_ble_audio_vcp_vol_rend_register(esp_ble_audio_vcp_vol_rend_register_param_t *param) { + esp_err_t ret = ESP_OK; int err; if (param == NULL) { @@ -44,22 +45,28 @@ esp_err_t esp_ble_audio_vcp_vol_rend_register(esp_ble_audio_vcp_vol_rend_registe return ESP_ERR_INVALID_ARG; } - err = bt_vcp_vol_rend_register_safe(param); + bt_le_host_lock(); + + err = bt_vcp_vol_rend_register(param); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #if BLE_AUDIO_SVC_DEFERRED_ADD err = bt_le_vcp_vol_rend_init(); if (err) { - /* TODO: rollback register_safe once lib exposes an unregister API; + /* TODO: rollback register once lib exposes an unregister API; * retry will hit -EALREADY. Only reachable on GATT alloc failure. */ - return ESP_FAIL; + ret = ESP_FAIL; + goto end; } #endif /* BLE_AUDIO_SVC_DEFERRED_ADD */ - return ESP_OK; +end: + bt_le_host_unlock(); + return ret; } esp_err_t esp_ble_audio_vcp_vol_rend_set_step(uint8_t volume_step) diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_common_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_common_api.h index 6ff3cc7e64a..0c2a20d0a55 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_common_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_common_api.h @@ -126,7 +126,7 @@ typedef struct { * @param type Event type. * @param param Event parameters. */ -void esp_ble_audio_gap_app_post_event(uint8_t type, void *param); +void esp_ble_audio_gap_app_post_event(uint16_t type, void *param); #if !CONFIG_BT_BLUEDROID_ENABLED /** diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/vcs.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/vcs.c index 0dd65e7e920..bc848dbd276 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/vcs.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/vcs.c @@ -76,13 +76,10 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc) return -EINVAL; } - inc_vocs_svc_count = vcp_included->vocs_cnt; - inc_aics_svc_count = vcp_included->aics_cnt; - bt_le_bluedroid_set_svc_in_progress(VOCS_IN_PROGRESS); /* VCS may include zero or more instances of VOCS */ - for (size_t i = 0; i < inc_vocs_svc_count; i++) { + for (size_t i = 0; i < vcp_included->vocs_cnt; i++) { inc_vocs_insts[i].svc_p = lib_vocs_svc_get(vcp_included->vocs[i]); if (!inc_vocs_insts[i].svc_p) { LOG_ERR("[B]VocsSvcGetFail[%u]", i); @@ -101,7 +98,7 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc) bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS); /* VCS may include zero or more instances of AICS */ - for (size_t i = 0; i < inc_aics_svc_count; i++) { + for (size_t i = 0; i < vcp_included->aics_cnt; i++) { inc_aics_insts[i].svc_p = lib_aics_svc_get(vcp_included->aics[i]); if (!inc_aics_insts[i].svc_p) { LOG_ERR("[B]AicsSvcGetFail[%u]", i); @@ -116,6 +113,12 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc) return err; } } + + /* Commit counts only after every instance initialized — mid-loop + * failure leaves them at 0 (reset on entry) so vcs_start() never + * iterates partially-initialized entries. */ + inc_vocs_svc_count = vcp_included->vocs_cnt; + inc_aics_svc_count = vcp_included->aics_cnt; } bt_le_bluedroid_set_svc_in_progress(VCS_IN_PROGRESS); diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c index 78f1e73718b..aafa82e44ff 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c @@ -173,15 +173,14 @@ static void inc_svc_add_cb(uint16_t service_id, uint16_t attr_id, uint8_t status int result = status; if (service_id != svc_handle) { + /* Stale after timeout; take already failed — ignore without give. */ LOG_ERR("[B]IncSvcAddMismatch[%u][%u][%u]", svc_in_progress, service_id, svc_handle); - result = -1; - goto end; + return; } inc_svc_handle = attr_id; -end: bt_le_bluedroid_gatts_sem_give(result); } @@ -190,9 +189,8 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id, { int result = status; - if (service_id != svc_handle || ((tBT_UUID *)uuid)->len != 2) { - /* uuid16 is only meaningful when len == 2; for 32/128-bit UUIDs the - * union access would log the first 2 bytes of a wider value. */ + if (service_id != svc_handle) { + /* Stale after timeout; take already failed — ignore without give. */ if (((tBT_UUID *)uuid)->len == 2) { LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][2][0x%04x]", svc_in_progress, service_id, svc_handle, @@ -202,6 +200,13 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id, svc_in_progress, service_id, svc_handle, ((tBT_UUID *)uuid)->len); } + return; + } + + if (((tBT_UUID *)uuid)->len != 2) { + LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][%u][nonU16]", + svc_in_progress, service_id, svc_handle, + ((tBT_UUID *)uuid)->len); result = -1; goto end; } @@ -214,14 +219,13 @@ end: static void svc_start_cb(uint16_t service_id, uint8_t status) { - int result = status; - if (service_id != svc_handle) { + /* Stale after timeout; take already failed — ignore without give. */ LOG_ERR("[B]SvcStartMismatch[%u][%u]", service_id, svc_handle); - result = -1; + return; } - bt_le_bluedroid_gatts_sem_give(result); + bt_le_bluedroid_gatts_sem_give(status); } static struct gatts_svc_cb svc_cb = { @@ -456,7 +460,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc) uint8_t inst_id; tBT_UUID uuid; - assert(svc); + BT_LE_ASSERT(svc); for (size_t i = 0; i < svc->attr_count; i++) { curr_attr = &svc->attrs[i]; @@ -470,7 +474,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc) return -1; } - assert(curr_attr->user_data); + BT_LE_ASSERT(curr_attr->user_data); bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid); inst_id = get_svc_inst_id(uuid.uu.uuid16); @@ -496,7 +500,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc) return -1; } - assert(curr_attr->user_data); + BT_LE_ASSERT(curr_attr->user_data); bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid); inst_id = get_svc_inst_id(uuid.uu.uuid16); @@ -561,7 +565,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc) next_attr = &svc->attrs[i + 1]; perm = bt_le_bluedroid_gatt_perm_convert(next_attr->perm); - assert(curr_attr->user_data); + BT_LE_ASSERT(curr_attr->user_data); chrc = curr_attr->user_data; bt_le_bluedroid_gatt_uuid_convert(chrc->uuid, &uuid); @@ -612,7 +616,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc) int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc) { - assert(svc); + BT_LE_ASSERT(svc); svc_handle = svc->attrs[0].handle; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/ascs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/ascs.c index dfba6d7a868..007c63c640f 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/ascs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/ascs.c @@ -62,7 +62,7 @@ static void ascs_svc_add_ase_cp_chr(struct ble_gatt_chr_def *chr) chr->arg = NULL; chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC; + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; chr->min_key_size = 16; chr->val_handle = &ase_control_point_handle; } @@ -79,7 +79,7 @@ static void ascs_svc_add_ase_snk_chr(struct ble_gatt_chr_def *chrs) chr->access_cb = bt_le_nimble_gatts_access_cb_safe; chr->arg = UINT_TO_POINTER(i); chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ - chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC; + chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; chr->min_key_size = 16; chr->val_handle = &ase_snk_handle[i]; } @@ -98,7 +98,7 @@ static void ascs_svc_add_ase_src_chr(struct ble_gatt_chr_def *chrs) chr->access_cb = bt_le_nimble_gatts_access_cb_safe; chr->arg = UINT_TO_POINTER(i); chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ - chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC; + chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; chr->min_key_size = 16; chr->val_handle = &ase_src_handle[i]; } @@ -128,7 +128,7 @@ int bt_le_nimble_ascs_attr_handle_set(void) return -ENODEV; } - assert(ase_control_point_handle >= 2); + BT_LE_ASSERT(ase_control_point_handle >= 2); start_handle = ase_control_point_handle - 2; /* server attr handle & char def handle */ #if CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0 end_handle = ase_src_handle[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT - 1] + 1; /* cccd attr handle */ @@ -202,6 +202,7 @@ static int ascs_svc_check(void) int bt_le_nimble_ascs_init(void) { + bool ascs_added = false; uint16_t chr_count; int rc; @@ -211,7 +212,7 @@ int bt_le_nimble_ascs_init(void) LOG_DBG("[N]AscsInit[%u]", chr_count); gatt_svc_ascs[0].characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def)); - assert(gatt_svc_ascs[0].characteristics); + BT_LE_ASSERT(gatt_svc_ascs[0].characteristics); ascs_svc_add_ase_cp_chr((void *)(gatt_svc_ascs[0].characteristics + 0)); @@ -234,6 +235,7 @@ int bt_le_nimble_ascs_init(void) LOG_ERR("[N]AscsAddSvcsFail[%d]", rc); goto free; } + ascs_added = true; rc = ascs_svc_check(); if (rc) { @@ -243,7 +245,12 @@ int bt_le_nimble_ascs_init(void) return 0; free: - free((void *)gatt_svc_ascs[0].characteristics); - gatt_svc_ascs[0].characteristics = NULL; + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ + if (!ascs_added) { + free((void *)gatt_svc_ascs[0].characteristics); + gatt_svc_ascs[0].characteristics = NULL; + } return rc; } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/bass.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/bass.c index 375659993e8..792ba8fa38d 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/bass.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/bass.c @@ -75,7 +75,7 @@ static void bass_svc_add_recv_state_chr(struct ble_gatt_chr_def *chrs) chr->access_cb = bt_le_nimble_gatts_access_cb_safe; chr->arg = UINT_TO_POINTER(i); chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ - chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC; + chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; chr->min_key_size = 16; chr->val_handle = &bass_recv_state_handle[i]; } @@ -104,7 +104,7 @@ int bt_le_nimble_bass_attr_handle_set(void) return -ENODEV; } - assert(bass_control_point_handle >= 2); + BT_LE_ASSERT(bass_control_point_handle >= 2); start_handle = bass_control_point_handle - 2; /* server attr handle & char def handle */ end_handle = bass_recv_state_handle[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT - 1] + 1; /* cccd attr handle */ @@ -154,7 +154,7 @@ static int bass_svc_check(void) for (size_t i = 0; i < bass_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(bass_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -172,6 +172,7 @@ static int bass_svc_check(void) int bt_le_nimble_bass_init(void) { + bool bass_added = false; uint16_t chr_count; int rc; @@ -181,7 +182,7 @@ int bt_le_nimble_bass_init(void) LOG_DBG("[N]BassInit[%u]", chr_count); gatt_svc_bass->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def)); - assert(gatt_svc_bass->characteristics); + BT_LE_ASSERT(gatt_svc_bass->characteristics); bass_svc_add_control_point_chr((void *)(gatt_svc_bass->characteristics + 0)); @@ -198,6 +199,7 @@ int bt_le_nimble_bass_init(void) LOG_ERR("[N]BassAddSvcsFail[%d]", rc); goto free; } + bass_added = true; rc = bass_svc_check(); if (rc) { @@ -207,7 +209,12 @@ int bt_le_nimble_bass_init(void) return 0; free: - free((void *)gatt_svc_bass->characteristics); - gatt_svc_bass->characteristics = NULL; + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ + if (!bass_added) { + free((void *)gatt_svc_bass->characteristics); + gatt_svc_bass->characteristics = NULL; + } return rc; } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c index 7b2533a77e1..12081f18de8 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c @@ -40,7 +40,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL); #if CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE #define CSIS_CHR_FLAGS_SIRK \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #else /* CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE */ #define CSIS_CHR_FLAGS_SIRK \ (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) @@ -48,14 +48,14 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL); #if CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE #define CSIS_CHR_FLAGS_SET_SIZE \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #else /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */ #define CSIS_CHR_FLAGS_SET_SIZE \ (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) #endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */ #define CSIS_CHR_FLAGS_SET_LOCK \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | \ + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC) #define CSIS_CHR_FLAGS_RANK \ @@ -63,7 +63,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL); #if CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE #define CSIS_CHR_FLAGS_SET_NAME \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #else /* CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE */ #define CSIS_CHR_FLAGS_SET_NAME \ (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) @@ -124,7 +124,7 @@ static int csis_svc_check(void) struct ble_gatt_svc_def *csis = &gatt_svc_csis[i]; struct bt_gatt_service *svc = csis_insts[i].svc_p; - assert(svc); + BT_LE_ASSERT(svc); for (const struct ble_gatt_chr_def *chr = csis->characteristics; chr && chr->uuid; chr++) { @@ -135,7 +135,7 @@ static int csis_svc_check(void) for (size_t j = 0; j < svc->attr_count; j++) { uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -161,8 +161,8 @@ int bt_le_nimble_csis_attr_handle_set(void) for (size_t i = 0; i < csis_svc_count; i++) { struct bt_gatt_service *zsvc = csis_insts[i].svc_p; - assert(zsvc); - assert(csis_insts[i].sirk_handle >= 2); + BT_LE_ASSERT(zsvc); + BT_LE_ASSERT(csis_insts[i].sirk_handle >= 2); /* SIRK is always the first characteristic, so its value handle anchors the range. */ start_handle = csis_insts[i].sirk_handle - 2; /* server attr handle & char def handle */ @@ -181,7 +181,7 @@ int bt_le_nimble_csis_attr_handle_set(void) const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)(zsvc->attrs + j)->uuid; uint16_t chr_handle = 0; - if (uuid->uuid.type != BT_UUID_TYPE_16) { + if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) { continue; } @@ -246,7 +246,7 @@ static void csis_svc_init(struct csis_inst *inst, svc->includes = NULL; svc->characteristics = bt_le_ext_calloc(CSIS_CHR_COUNT, sizeof(struct ble_gatt_chr_def)); - assert(svc->characteristics); + BT_LE_ASSERT(svc->characteristics); /* Build the NimBLE characteristics from the ones actually present in the Zephyr * service. Optional characteristics (set size, lock, rank) may be absent depending @@ -255,7 +255,7 @@ static void csis_svc_init(struct csis_inst *inst, for (size_t i = 0; i < zsvc->attr_count; i++) { const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)zsvc->attrs[i].uuid; - if (uuid->uuid.type != BT_UUID_TYPE_16) { + if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) { continue; } @@ -292,11 +292,12 @@ static void csis_svc_init(struct csis_inst *inst, * the terminator slot. Trips if the switch matches a 6th char: a new CSIS case added * without bumping the (5 + 1), or a duplicate UUID in the Zephyr service table. */ - assert(chr_cnt < CSIS_CHR_COUNT); + BT_LE_ASSERT(chr_cnt < CSIS_CHR_COUNT); } int bt_le_nimble_csis_init(void *svc, uint8_t count) { + bool csis_added = false; int rc; LOG_DBG("[N]CsisInit[%u]", count); @@ -325,6 +326,7 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count) LOG_ERR("[N]CsisAddSvcsFail[%d]", rc); goto free; } + csis_added = true; rc = csis_svc_check(); if (rc) { @@ -334,9 +336,14 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count) return 0; free: - for (size_t i = 0; i < csis_svc_count; i++) { - free((void *)gatt_svc_csis[i].characteristics); - gatt_svc_csis[i].characteristics = NULL; + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ + if (!csis_added) { + for (size_t i = 0; i < csis_svc_count; i++) { + free((void *)gatt_svc_csis[i].characteristics); + gatt_svc_csis[i].characteristics = NULL; + } } csis_svc_count = 0; return rc; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c index 9552de1c6eb..ad1176fbfff 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c @@ -50,7 +50,7 @@ static int gmas_build_svc(struct bt_gatt_service *gmas_svc) for (size_t i = 0; i < gmas_svc->attr_count; i++) { const struct bt_uuid_16 *u = (const struct bt_uuid_16 *)gmas_svc->attrs[i].uuid; - if (u->uuid.type != BT_UUID_TYPE_16) { + if (!u || u->uuid.type != BT_UUID_TYPE_16) { prev_decl = false; continue; } @@ -147,7 +147,7 @@ static int gmas_svc_check(void) for (size_t i = 0; i < gmas_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(gmas_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/has.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/has.c index cabc09f500c..7a0e1b07803 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/has.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/has.c @@ -60,7 +60,7 @@ static void has_svc_add_features_chr(struct ble_gatt_chr_def *chr) chr->arg = NULL; chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_HAS_FEATURES_NOTIFIABLE - chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC; + chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; #else /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC; #endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ @@ -78,9 +78,9 @@ static void has_svc_add_control_point_chr(struct ble_gatt_chr_def *chr) chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | - BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY; + BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; #else /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ - chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC; + chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; #endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ chr->min_key_size = 16; chr->val_handle = &has_control_point_handle; @@ -97,7 +97,7 @@ static void has_svc_add_preset_index_chr(struct ble_gatt_chr_def *chr) chr->access_cb = bt_le_nimble_gatts_access_cb_safe; chr->arg = NULL; chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */ - chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC; + chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; chr->min_key_size = 16; chr->val_handle = &has_preset_index_handle; } @@ -125,9 +125,15 @@ int bt_le_nimble_has_attr_handle_set(void) LOG_ERR("[N]HasSvcGetFail"); return -ENODEV; } - assert(has_svc->attr_count > 0); + BT_LE_ASSERT(has_svc->attr_count > 0); - end_handle = start_handle + has_svc->attr_count - 1; +#if CONFIG_BT_HAS_ACTIVE_PRESET_INDEX + BT_LE_ASSERT(has_preset_index_handle >= 2); + end_handle = has_preset_index_handle + 1; /* cccd attr handle */ +#else + BT_LE_ASSERT(has_control_point_handle >= 2); + end_handle = has_control_point_handle + 1; /* cccd attr handle */ +#endif LOG_DBG("[N]HasAttrHdlSet[%u][%u][%u]", start_handle, end_handle, has_svc->attr_count); @@ -175,7 +181,7 @@ static int has_svc_check(void) for (size_t i = 0; i < has_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(has_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -193,6 +199,7 @@ static int has_svc_check(void) int bt_le_nimble_has_init(void) { + bool has_added = false; uint8_t chr_count; int rc; @@ -206,7 +213,7 @@ int bt_le_nimble_has_init(void) LOG_DBG("[N]HasInit[%u]", chr_count); gatt_svc_has->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def)); - assert(gatt_svc_has->characteristics); + BT_LE_ASSERT(gatt_svc_has->characteristics); has_svc_add_features_chr((void *)(gatt_svc_has->characteristics + 0)); @@ -227,6 +234,7 @@ int bt_le_nimble_has_init(void) LOG_ERR("[N]HasAddSvcsFail[%d]", rc); goto free; } + has_added = true; rc = has_svc_check(); if (rc) { @@ -236,7 +244,12 @@ int bt_le_nimble_has_init(void) return 0; free: - free((void *)gatt_svc_has->characteristics); - gatt_svc_has->characteristics = NULL; + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ + if (!has_added) { + free((void *)gatt_svc_has->characteristics); + gatt_svc_has->characteristics = NULL; + } return rc; } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c index 80fde052b30..781536cbf8b 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c @@ -72,10 +72,10 @@ LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL); (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) #define INC_OTS_CHR_FLAGS_ACTION_CP \ - (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC) + (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_OTS_CHR_FLAGS_LIST_CP \ - (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC) + (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_ots_svc_count; @@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_player_name_handle, }, @@ -174,7 +174,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_NOTIFY, + .flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_track_changed_handle, }, { @@ -182,7 +182,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_track_title_handle, }, { @@ -190,7 +190,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_track_duration_handle, }, { @@ -199,7 +199,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_track_position_handle, @@ -209,7 +209,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_playback_speed_handle, @@ -218,7 +218,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_seeking_speed_handle, }, @@ -237,7 +237,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_current_track_obj_id_handle, @@ -247,7 +247,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_next_track_obj_id_handle, @@ -256,7 +256,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_parent_group_obj_id_handle, }, { @@ -265,7 +265,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_current_group_obj_id_handle, @@ -277,7 +277,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \ + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, .val_handle = &mcs_playing_order_handle, @@ -294,7 +294,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_media_state_handle, }, { @@ -303,7 +303,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC, + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_media_control_point_handle, }, { @@ -311,7 +311,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_media_control_opcodes_handle, }, @@ -322,7 +322,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC, + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_search_control_point_handle, }, { @@ -330,7 +330,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &mcs_search_results_obj_id_handle, }, @@ -366,9 +366,9 @@ static int inc_ots_attr_handle_set(void) uint16_t start_handle; uint16_t end_handle; - assert(ots && ots->service); + BT_LE_ASSERT(ots && ots->service); - assert(inc_ots_chr_feature_handle >= 2); + BT_LE_ASSERT(inc_ots_chr_feature_handle >= 2); start_handle = inc_ots_chr_feature_handle - 2; /* server attr handle & char def handle */ end_handle = inc_ots_chr_list_cp_handle + 1; /* cccd for chr Object List Control Point */ @@ -472,7 +472,7 @@ static int gmcs_svc_check(void) for (size_t i = 0; i < gmcs_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(gmcs_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -498,8 +498,8 @@ static int inc_ots_svc_check(void) * the service exist in the service defined by Zephyr. */ - assert(gatt_svc_inc_ots); - assert(ots && ots->service); + BT_LE_ASSERT(gatt_svc_inc_ots); + BT_LE_ASSERT(ots && ots->service); LOG_DBG("[N]IncOtsSvcCheck"); @@ -512,7 +512,7 @@ static int inc_ots_svc_check(void) for (size_t i = 0; i < ots->service->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(ots->service->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -551,7 +551,7 @@ static int inc_ots_svc_init(void) uint8_t chr_count; attrs = bt_ots_svc_decl_get(ots); - assert(attrs); + BT_LE_ASSERT(attrs); chr_count = 0; @@ -576,7 +576,7 @@ static int inc_ots_svc_init(void) /* An additional characteristic consist of all 0s indicating end of characteristics */ svc->characteristics = bt_le_ext_calloc(INC_OTS_CHR_COUNT + 1, sizeof(struct ble_gatt_chr_def)); - assert(svc->characteristics); + BT_LE_ASSERT(svc->characteristics); /* Characteristic - OTS Feature */ inc_ots_chr_init((void *)&svc->characteristics[0], @@ -632,6 +632,7 @@ static int inc_ots_svc_init(void) int bt_le_nimble_gmcs_init(bool ots_included) { + bool inc_ots_added = false; int rc; LOG_DBG("[N]GmcsInit[%u]", ots_included); @@ -642,14 +643,14 @@ int bt_le_nimble_gmcs_init(bool ots_included) /* Extra one for terminating the included service array with NULL */ gmcs_inc_svcs = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def *)); - assert(gmcs_inc_svcs); + BT_LE_ASSERT(gmcs_inc_svcs); /* Extra one for terminating the OTS service array */ gatt_svc_inc_ots = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def)); - assert(gatt_svc_inc_ots); + BT_LE_ASSERT(gatt_svc_inc_ots); ots = lib_mcs_get_ots(); - assert(ots && ots->service); + BT_LE_ASSERT(ots && ots->service); rc = inc_ots_svc_init(); if (rc) { @@ -669,6 +670,7 @@ int bt_le_nimble_gmcs_init(bool ots_included) LOG_ERR("[N]IncOtsAddSvcsFail[%d]", rc); goto free; } + inc_ots_added = true; rc = inc_ots_svc_check(); if (rc) { @@ -703,18 +705,23 @@ int bt_le_nimble_gmcs_init(bool ots_included) free: #if CONFIG_BT_OTS + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ if (ots_included) { inc_ots_svc_count = 0; free(gmcs_inc_svcs); gmcs_inc_svcs = NULL; - if (gatt_svc_inc_ots[0].characteristics) { - free((void *)gatt_svc_inc_ots[0].characteristics); - gatt_svc_inc_ots[0].characteristics = NULL; + if (!inc_ots_added) { + if (gatt_svc_inc_ots[0].characteristics) { + free((void *)gatt_svc_inc_ots[0].characteristics); + gatt_svc_inc_ots[0].characteristics = NULL; + } + free(gatt_svc_inc_ots); + gatt_svc_inc_ots = NULL; } - free(gatt_svc_inc_ots); - gatt_svc_inc_ots = NULL; } gatt_svc_gmcs[0].includes = NULL; #endif /* CONFIG_BT_OTS */ @@ -779,7 +786,7 @@ static int mcs_svc_check(void) for (size_t i = 0; i < mcs_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(mcs_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -798,6 +805,7 @@ static int mcs_svc_check(void) int bt_le_nimble_mcs_init(void) { uint8_t count = CONFIG_BT_MCS_INSTANCE_COUNT; + bool mcs_added = false; int rc; /* NULL when CONFIG_BT_MCS_INSTANCE_COUNT == 0: nothing discrete to add. */ @@ -811,7 +819,7 @@ int bt_le_nimble_mcs_init(void) /* One ble_gatt_svc_def per instance + a zeroed terminator. Persists for the * GATT DB lifetime (NimBLE references these defs until ble_gatts_start). */ gatt_svc_mcs = bt_le_ext_calloc(count + 1, sizeof(struct ble_gatt_svc_def)); - assert(gatt_svc_mcs); + BT_LE_ASSERT(gatt_svc_mcs); for (int i = 0; i < count; i++) { gatt_svc_mcs[i].type = BLE_GATT_SVC_TYPE_PRIMARY; @@ -837,6 +845,7 @@ int bt_le_nimble_mcs_init(void) LOG_ERR("[N]McsAddSvcsFail[%d]", rc); goto free; } + mcs_added = true; rc = mcs_svc_check(); if (rc) { @@ -846,7 +855,12 @@ int bt_le_nimble_mcs_init(void) return 0; free: - free(gatt_svc_mcs); - gatt_svc_mcs = NULL; + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ + if (!mcs_added) { + free(gatt_svc_mcs); + gatt_svc_mcs = NULL; + } return rc; } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mics.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mics.c index 714d60916f2..f54ca70ef56 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mics.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mics.c @@ -35,7 +35,7 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL); #define INC_AICS_CHR_COUNT (6 + 1) #define INC_AICS_CHR_FLAGS_STATE \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_AICS_CHR_FLAGS_GAIN \ (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) @@ -44,13 +44,13 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL); (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) #define INC_AICS_CHR_FLAGS_STATUS \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_AICS_CHR_FLAGS_CONTROL \ (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC) #define INC_AICS_CHR_FLAGS_DESCRIPTION \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \ + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC) static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count; @@ -92,7 +92,7 @@ static struct ble_gatt_svc_def gatt_svc_mics[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | \ + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ_ENC | \ BLE_GATT_CHR_F_WRITE_ENC, .min_key_size = 16, @@ -116,8 +116,8 @@ static int inc_aics_svc_check(void) * the service exist in the service defined by Zephyr. */ - assert(gatt_svc_inc_aics); - assert(inc_aics_insts); + BT_LE_ASSERT(gatt_svc_inc_aics); + BT_LE_ASSERT(inc_aics_insts); LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count); @@ -125,7 +125,7 @@ static int inc_aics_svc_check(void) struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i]; struct bt_gatt_service *svc = inc_aics_insts[i].svc_p; - assert(svc); + BT_LE_ASSERT(svc); for (const struct ble_gatt_chr_def *chr = aics->characteristics; chr && chr->uuid; chr++) { @@ -136,7 +136,7 @@ static int inc_aics_svc_check(void) for (size_t j = 0; j < svc->attr_count; j++) { uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -176,7 +176,7 @@ static int mics_svc_check(void) for (size_t i = 0; i < mics_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(mics_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -198,14 +198,14 @@ static int inc_aics_attr_handle_set(void) uint16_t start_handle; uint16_t end_handle; - assert(inc_aics_insts); + BT_LE_ASSERT(inc_aics_insts); LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count); for (size_t i = 0; i < inc_aics_svc_count; i++) { - assert(inc_aics_insts[i].svc_p); + BT_LE_ASSERT(inc_aics_insts[i].svc_p); - assert(inc_aics_insts[i].state_handle >= 2); + BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2); start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */ end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */ @@ -307,7 +307,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst, svc->includes = NULL; svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def)); - assert(svc->characteristics); + BT_LE_ASSERT(svc->characteristics); /* Characteristic - Audio Input State */ inc_aics_chr_init((void *)&svc->characteristics[0], @@ -349,6 +349,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst, int bt_le_nimble_mics_init(void *micp_inc) { struct bt_micp_included *micp_included; + bool inc_aics_added = false; uint8_t inc_count; int rc; @@ -371,16 +372,16 @@ int bt_le_nimble_mics_init(void *micp_inc) inc_count = inc_aics_svc_count + 1; mics_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *)); - assert(mics_inc_svcs); + BT_LE_ASSERT(mics_inc_svcs); /* MICS may include zero or more instances of AICS */ if (inc_aics_svc_count) { inc_aics_insts = bt_le_ext_calloc(inc_aics_svc_count, sizeof(struct inc_aics_inst)); - assert(inc_aics_insts); + BT_LE_ASSERT(inc_aics_insts); /* Extra one for terminating the AICS service array */ gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def)); - assert(gatt_svc_inc_aics); + BT_LE_ASSERT(gatt_svc_inc_aics); for (size_t i = 0; i < inc_aics_svc_count; i++) { inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]); @@ -406,6 +407,7 @@ int bt_le_nimble_mics_init(void *micp_inc) LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc); goto free; } + inc_aics_added = true; rc = inc_aics_svc_check(); if (rc) { @@ -439,22 +441,28 @@ int bt_le_nimble_mics_init(void *micp_inc) return 0; free: + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. That covers + * inc_aics_insts too: the registered chr_defs point at its handle fields. */ if (micp_included) { free(mics_inc_svcs); mics_inc_svcs = NULL; if (inc_aics_svc_count) { - free(inc_aics_insts); - inc_aics_insts = NULL; + if (!inc_aics_added) { + free(inc_aics_insts); + inc_aics_insts = NULL; - for (size_t i = 0; i < inc_aics_svc_count; i++) { - free((void *)gatt_svc_inc_aics[i].characteristics); - gatt_svc_inc_aics[i].characteristics = NULL; + for (size_t i = 0; i < inc_aics_svc_count; i++) { + free((void *)gatt_svc_inc_aics[i].characteristics); + gatt_svc_inc_aics[i].characteristics = NULL; + } + + free(gatt_svc_inc_aics); + gatt_svc_inc_aics = NULL; } - free(gatt_svc_inc_aics); - gatt_svc_inc_aics = NULL; - inc_aics_svc_count = 0; } } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/pacs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/pacs.c index 93e90d87246..a9060cd7ced 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/pacs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/pacs.c @@ -62,7 +62,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_PAC_SNK_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ @@ -77,13 +77,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \ + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_WRITE_ENC, #elif CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, #elif !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ @@ -99,7 +99,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_PAC_SRC_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ @@ -114,13 +114,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \ + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_WRITE_ENC, #elif CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC, #elif !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ @@ -134,7 +134,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, .val_handle = &pacs_ava_ctx_handle, }, { @@ -143,7 +143,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ @@ -173,13 +173,13 @@ int bt_le_nimble_pacs_attr_handle_set(void) } #if CONFIG_BT_PAC_SNK - assert(pacs_snk_handle >= 2); + BT_LE_ASSERT(pacs_snk_handle >= 2); start_handle = pacs_snk_handle - 2; /* server attr handle & char def handle */ #elif CONFIG_BT_PAC_SRC - assert(pacs_src_handle >= 2); + BT_LE_ASSERT(pacs_src_handle >= 2); start_handle = pacs_src_handle - 2; /* server attr handle & char def handle */ #else - assert(pacs_ava_ctx_handle >= 2); + BT_LE_ASSERT(pacs_ava_ctx_handle >= 2); start_handle = pacs_ava_ctx_handle - 2; /* server attr handle & char def handle */ #endif #if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE @@ -234,7 +234,7 @@ static int pacs_svc_check(void) for (size_t i = 0; i < pacs_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(pacs_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c index 4c67b780618..9ea3ffb3a3c 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c @@ -42,7 +42,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_UCI_VAL), @@ -56,21 +56,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_URI_LIST_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_STRENGTH_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_INTERVAL_VAL), @@ -86,7 +86,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_CCID_VAL), @@ -100,21 +100,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_URI_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_STATE_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_CONTROL_POINT_VAL), @@ -122,7 +122,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ .flags = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE | \ - BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC, + BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_OPTIONAL_OPCODES_VAL), @@ -136,21 +136,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_NOTIFY, + .flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_CALL_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { .uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_FRIENDLY_NAME_VAL), .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { 0, /* No more characteristics in this service. */ @@ -233,7 +233,7 @@ static int gtbs_svc_check(void) for (size_t i = 0; i < gtbs_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(gtbs_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -328,7 +328,7 @@ static int tbs_svc_check(void) for (size_t i = 0; i < tbs_list[0].attr_count; i++) { uuid = (const struct bt_uuid_16 *)(tbs_list[0].attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tmas.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tmas.c index 6e861c8013d..791f1f95a63 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tmas.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tmas.c @@ -112,7 +112,7 @@ static int tmas_svc_check(void) for (size_t i = 0; i < tmas_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(tmas_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/vcs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/vcs.c index b4565bda388..d0b496f093f 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/vcs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/vcs.c @@ -37,17 +37,17 @@ LOG_MODULE_REGISTER(LEA_VCS, CONFIG_BT_ISO_LOG_LEVEL); #define INC_VOCS_CHR_COUNT (4 + 1) #define INC_VOCS_CHR_FLAGS_STATE \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_VOCS_CHR_FLAGS_LOCATION \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \ + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC) #define INC_VOCS_CHR_FLAGS_CONTROL \ (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC) #define INC_VOCS_CHR_FLAGS_DESCRIPTION \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \ + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC) static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_vocs_svc_count; @@ -73,7 +73,7 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO #define INC_AICS_CHR_COUNT (6 + 1) #define INC_AICS_CHR_FLAGS_STATE \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_AICS_CHR_FLAGS_GAIN \ (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) @@ -82,13 +82,13 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC) #define INC_AICS_CHR_FLAGS_STATUS \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC) + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) #define INC_AICS_CHR_FLAGS_CONTROL \ (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC) #define INC_AICS_CHR_FLAGS_DESCRIPTION \ - (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \ + (BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC) static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count; @@ -129,7 +129,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = { .access_cb = bt_le_nimble_gatts_access_cb_safe, .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, .min_key_size = 16, }, { /* Volume Control Service -- Volume COntrol Point characteristic */ @@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = { .arg = NULL, .descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */ #if CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC, #else /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, #endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ @@ -170,7 +170,7 @@ static int inc_vocs_svc_check(void) * the service exist in the service defined by Zephyr. */ - assert(gatt_svc_inc_vocs); + BT_LE_ASSERT(gatt_svc_inc_vocs); LOG_DBG("[N]IncVocsSvcCheck[%u]", inc_vocs_svc_count); @@ -178,7 +178,7 @@ static int inc_vocs_svc_check(void) struct ble_gatt_svc_def *vocs = &gatt_svc_inc_vocs[i]; struct bt_gatt_service *svc = inc_vocs_insts[i].svc_p; - assert(svc); + BT_LE_ASSERT(svc); for (const struct ble_gatt_chr_def *chr = vocs->characteristics; chr && chr->uuid; chr++) { @@ -189,7 +189,7 @@ static int inc_vocs_svc_check(void) for (size_t j = 0; j < svc->attr_count; j++) { uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -215,7 +215,7 @@ static int inc_aics_svc_check(void) * the service exist in the service defined by Zephyr. */ - assert(gatt_svc_inc_aics); + BT_LE_ASSERT(gatt_svc_inc_aics); LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count); @@ -223,7 +223,7 @@ static int inc_aics_svc_check(void) struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i]; struct bt_gatt_service *svc = inc_aics_insts[i].svc_p; - assert(svc); + BT_LE_ASSERT(svc); for (const struct ble_gatt_chr_def *chr = aics->characteristics; chr && chr->uuid; chr++) { @@ -234,7 +234,7 @@ static int inc_aics_svc_check(void) for (size_t j = 0; j < svc->attr_count; j++) { uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -278,7 +278,7 @@ static int vcs_svc_check(void) for (size_t i = 0; i < vcs_svc->attr_count; i++) { uuid = (const struct bt_uuid_16 *)(vcs_svc->attrs + i)->uuid; - if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && + if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) && uuid->val == check->value) { chr_found = true; break; @@ -303,9 +303,9 @@ static int inc_vocs_attr_handle_set(void) LOG_DBG("[N]IncVocsAttrHdlSet[%u]", inc_vocs_svc_count); for (size_t i = 0; i < inc_vocs_svc_count; i++) { - assert(inc_vocs_insts[i].svc_p); + BT_LE_ASSERT(inc_vocs_insts[i].svc_p); - assert(inc_vocs_insts[i].state_handle >= 2); + BT_LE_ASSERT(inc_vocs_insts[i].state_handle >= 2); start_handle = inc_vocs_insts[i].state_handle - 2; /* server attr handle & char def handle */ end_handle = inc_vocs_insts[i].description_handle + 1; /* cccd for chr Audio Output Description */ @@ -339,9 +339,9 @@ static int inc_aics_attr_handle_set(void) LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count); for (size_t i = 0; i < inc_aics_svc_count; i++) { - assert(inc_aics_insts[i].svc_p); + BT_LE_ASSERT(inc_aics_insts[i].svc_p); - assert(inc_aics_insts[i].state_handle >= 2); + BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2); start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */ end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */ @@ -450,7 +450,7 @@ static void inc_vocs_svc_init(struct inc_vocs_inst *inst, svc->includes = NULL; svc->characteristics = bt_le_ext_calloc(INC_VOCS_CHR_COUNT, sizeof(struct ble_gatt_chr_def)); - assert(svc->characteristics); + BT_LE_ASSERT(svc->characteristics); /* Characteristic - Volume Offset State */ inc_vocs_chr_init((void *)&svc->characteristics[0], @@ -503,7 +503,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst, svc->includes = NULL; svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def)); - assert(svc->characteristics); + BT_LE_ASSERT(svc->characteristics); /* Characteristic - Audio Input State */ inc_aics_chr_init((void *)&svc->characteristics[0], @@ -545,6 +545,8 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst, int bt_le_nimble_vcs_init(void *vcp_inc) { struct bt_vcp_included *vcp_included; + bool inc_vocs_added = false; + bool inc_aics_added = false; uint8_t inc_count; int rc; @@ -571,13 +573,13 @@ int bt_le_nimble_vcs_init(void *vcp_inc) inc_count = inc_vocs_svc_count + inc_aics_svc_count + 1; vcs_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *)); - assert(vcs_inc_svcs); + BT_LE_ASSERT(vcs_inc_svcs); /* VCS may include zero or more instances of VOCS */ if (inc_vocs_svc_count) { /* Extra one for terminating the VOCS service array */ gatt_svc_inc_vocs = bt_le_ext_calloc(inc_vocs_svc_count + 1, sizeof(struct ble_gatt_svc_def)); - assert(gatt_svc_inc_vocs); + BT_LE_ASSERT(gatt_svc_inc_vocs); for (size_t i = 0; i < inc_vocs_svc_count; i++) { inc_vocs_svc_init(&inc_vocs_insts[i], &gatt_svc_inc_vocs[i]); @@ -603,6 +605,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc) LOG_ERR("[N]IncVocsAddSvcsFail[%d]", rc); goto free; } + inc_vocs_added = true; rc = inc_vocs_svc_check(); if (rc) { @@ -614,7 +617,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc) if (inc_aics_svc_count) { /* Extra one for terminating the AICS service array */ gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def)); - assert(gatt_svc_inc_aics); + BT_LE_ASSERT(gatt_svc_inc_aics); for (size_t i = 0; i < inc_aics_svc_count; i++) { inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]); @@ -640,6 +643,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc) LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc); goto free; } + inc_aics_added = true; rc = inc_aics_svc_check(); if (rc) { @@ -673,15 +677,20 @@ int bt_le_nimble_vcs_init(void *vcp_inc) return 0; free: + /* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and + * offers no per-service unregister, so an added service must be leaked + * rather than freed into a dangling entry of its global list. */ if (vcp_included) { if (inc_vocs_svc_count) { - for (size_t i = 0; i < inc_vocs_svc_count; i++) { - free((void *)gatt_svc_inc_vocs[i].characteristics); - gatt_svc_inc_vocs[i].characteristics = NULL; - } + if (!inc_vocs_added) { + for (size_t i = 0; i < inc_vocs_svc_count; i++) { + free((void *)gatt_svc_inc_vocs[i].characteristics); + gatt_svc_inc_vocs[i].characteristics = NULL; + } - free(gatt_svc_inc_vocs); - gatt_svc_inc_vocs = NULL; + free(gatt_svc_inc_vocs); + gatt_svc_inc_vocs = NULL; + } inc_vocs_svc_count = 0; } @@ -689,7 +698,7 @@ free: if (inc_aics_svc_count) { /* A VOCS-phase failure reaches here with the count already set * but gatt_svc_inc_aics not yet allocated (still NULL). */ - if (gatt_svc_inc_aics) { + if (!inc_aics_added && gatt_svc_inc_aics) { for (size_t i = 0; i < inc_aics_svc_count; i++) { free((void *)gatt_svc_inc_aics[i].characteristics); gatt_svc_inc_aics[i].characteristics = NULL; diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/server.c b/components/bt/esp_ble_audio/host/adapter/nimble/server.c index 2e589cac148..aa5d848272a 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/server.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/server.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -27,6 +26,24 @@ LOG_MODULE_REGISTER(LEA_GSRV, CONFIG_BT_ISO_LOG_LEVEL); +/* NimBLE access callbacks must return 0 or a positive ATT error (stored as + * uint8_t). Zephyr GATT handlers return BT_GATT_ERR(att) = -att. */ +static int to_nimble_att_err(int gatt_err) +{ + int att; + + if (gatt_err >= 0) { + return 0; + } + + att = -gatt_err; + if (att > 0 && att <= UINT8_MAX) { + return att; + } + + return BLE_ATT_ERR_UNLIKELY; +} + static ssize_t gatts_read_cb(void *arg, uint16_t offset, const void *data, uint16_t len) { struct os_mbuf *om; @@ -35,20 +52,21 @@ static ssize_t gatts_read_cb(void *arg, uint16_t offset, const void *data, uint1 ARG_UNUSED(offset); om = (struct os_mbuf *)arg; - assert(om); + BT_LE_ASSERT(om); LOG_DBG("[N]GattsRdCb[%u][%u]", offset, len); if (data == NULL || len == 0) { - rc = 0; - } else { - rc = os_mbuf_append(om, data, len); - if (rc) { - LOG_ERR("[N]MbufAppendFail[%d]", rc); - } + return 0; } - return (rc == 0 ? len : 0); + rc = os_mbuf_append(om, data, len); + if (rc) { + LOG_ERR("[N]MbufAppendFail[%d]", rc); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + return len; } static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, @@ -57,17 +75,17 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct bt_le_nimble_gatt_read_cb cb; const struct bt_gatt_attr *attr; struct bt_conn *conn; - uint8_t *data; + uint8_t *data = NULL; ssize_t rc; - assert(ctx); + BT_LE_ASSERT(ctx); LOG_DBG("[N]GattsAccessCb[%u][%u][%02x]", conn_handle, attr_handle, ctx->op); conn = bt_le_acl_conn_find(conn_handle); if (conn == NULL || conn->state != BT_CONN_CONNECTED) { LOG_ERR("[N]NotConn[%d]", __LINE__); - return -ENOTCONN; + return BLE_ATT_ERR_UNLIKELY; } switch (ctx->op) { @@ -75,12 +93,12 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, attr = bt_gatts_find_attr_by_handle(attr_handle); if (attr == NULL) { LOG_WRN("[N]RdAttrNotFound[%u]", attr_handle); - return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + return BLE_ATT_ERR_INVALID_HANDLE; } if (attr->read == NULL) { LOG_WRN("[N]RdNotPermit"); - return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED); + return BLE_ATT_ERR_READ_NOT_PERMITTED; } cb.read_cb = gatts_read_cb; @@ -89,7 +107,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, rc = attr->read(conn, attr, (void *)&cb, UINT16_MAX, 0); if (rc < 0) { LOG_WRN("[N]RdGattErr[%u][%d]", attr_handle, rc); - return BT_GATT_ERR(rc); + return to_nimble_att_err(rc); } return 0; @@ -98,12 +116,12 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, attr = bt_gatts_find_attr_by_handle(attr_handle); if (attr == NULL) { LOG_WRN("[N]WrAttrNotFound[%u]", attr_handle); - return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + return BLE_ATT_ERR_INVALID_HANDLE; } if (attr->write == NULL) { LOG_WRN("[N]WrNotPermit"); - return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED); + return BLE_ATT_ERR_WRITE_NOT_PERMITTED; } if (BT_UUID_16(attr->uuid)->val == BT_UUID_BASS_CONTROL_POINT_VAL) { @@ -120,14 +138,14 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, if (OS_MBUF_PKTLEN(ctx->om) > alloc_len) { LOG_WRN("[N]WrBassCtrlPtTooLong[%u > %u]", OS_MBUF_PKTLEN(ctx->om), alloc_len); - return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } data = bt_le_ext_calloc(1, alloc_len); - assert(data); + BT_LE_ASSERT(data); rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data); - assert(rc == 0); + BT_LE_ASSERT(rc == 0); rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0); @@ -136,20 +154,26 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle, } else { LOG_DBG("[N]Wr[%u]", OS_MBUF_PKTLEN(ctx->om)); - data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om)); - assert(data); + /* A zero-length write is a legal PDU, but calloc(0) returns NULL + * on IDF and would trip the assert; pass it on unallocated. */ + if (OS_MBUF_PKTLEN(ctx->om) > 0) { + data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om)); + BT_LE_ASSERT(data); - rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data); - assert(rc == 0); + rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data); + BT_LE_ASSERT(rc == 0); + } rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0); - free(data); - data = NULL; + if (data != NULL) { + free(data); + data = NULL; + } } if (rc < 0) { LOG_WRN("[N]WrGattErr[%u][%d]", attr_handle, rc); - return BT_GATT_ERR(rc); + return to_nimble_att_err(rc); } return 0; diff --git a/components/bt/esp_ble_audio/host/common/include/common/init.h b/components/bt/esp_ble_audio/host/common/include/common/init.h index 480e0bb9e1c..221f0f2cbb1 100644 --- a/components/bt/esp_ble_audio/host/common/include/common/init.h +++ b/components/bt/esp_ble_audio/host/common/include/common/init.h @@ -45,6 +45,8 @@ struct bt_le_audio_start_info { int bt_le_audio_init(void); +void bt_le_audio_deinit(void); + int bt_le_ascs_init(void); int bt_le_bass_init(void); diff --git a/components/bt/esp_ble_audio/host/common/init.c b/components/bt/esp_ble_audio/host/common/init.c index 485f3b42082..495b044b4d1 100644 --- a/components/bt/esp_ble_audio/host/common/init.c +++ b/components/bt/esp_ble_audio/host/common/init.c @@ -44,6 +44,7 @@ #include <../host/conn_internal.h> #include <../host/hci_core.h> +#include "utils/assert.h" #include "utils/mem.h" #if CONFIG_BT_BLUEDROID_ENABLED @@ -52,6 +53,10 @@ #include "nimble/init.h" #endif +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT +#include "ots/adapter/l2cap.h" +#endif + #include "../../../lib/include/audio.h" #include "esp_ble_audio_common_api.h" @@ -180,7 +185,7 @@ static const uint16_t ext_structs[] = { sizeof(struct bt_bond_info), }; -#define LEA_VERSION (0x20260724) +#define LEA_VERSION (0x20260802) struct lib_ext_cfgs { /* BLE */ @@ -1205,29 +1210,13 @@ 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, + ._assert = (void *)bt_le_assert, #if CONFIG_BT_AUDIO_HEAP_EXTERNAL_MEMORY ._malloc = (void *)bt_le_ext_malloc, @@ -1319,8 +1308,6 @@ static const struct lib_ext_funcs ext_funcs = { ._conn_index = (void *)bt_conn_index, ._conn_lookup_index = (void *)bt_conn_lookup_index, ._conn_get_dst = (void *)bt_conn_get_dst, - ._conn_ref = (void *)bt_conn_ref, - ._conn_unref = (void *)bt_conn_unref, ._gatt_svc_register = (void *)bt_gatt_service_register, ._gatt_svc_unregister = (void *)bt_gatt_service_unregister, @@ -2128,10 +2115,32 @@ int bt_le_audio_init(void) return err; } +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT + err = bt_le_l2cap_ots_init(); + if (err) { + return err; + } +#endif + #if CONFIG_BT_BLUEDROID_ENABLED - return bt_le_bluedroid_audio_init(); + err = bt_le_bluedroid_audio_init(); #else - return bt_le_nimble_audio_init(); + err = bt_le_nimble_audio_init(); +#endif + if (err) { +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT + bt_le_l2cap_ots_deinit(); +#endif + return err; + } + + return 0; +} + +void bt_le_audio_deinit(void) +{ +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT + bt_le_l2cap_ots_deinit(); #endif } diff --git a/components/bt/esp_ble_audio/host/services/ots/Kconfig.ots.in b/components/bt/esp_ble_audio/host/services/ots/Kconfig.ots.in index 0bcb78b191b..4cef13da510 100644 --- a/components/bt/esp_ble_audio/host/services/ots/Kconfig.ots.in +++ b/components/bt/esp_ble_audio/host/services/ots/Kconfig.ots.in @@ -7,6 +7,8 @@ config BT_OTS bool "Object Transfer Service (OTS) [EXPERIMENTAL]" select BT_OTS_SECONDARY_SVC + # BT_GATTS_ENABLE over BT_BLUEDROID_ENABLED: it implies the full dependency chain. + select BT_BLE_L2CAP_COC_ENABLED if BT_GATTS_ENABLE help Enable Object Transfer Service. @@ -69,6 +71,8 @@ endif # BT_OTS config BT_OTS_CLIENT bool "Object Transfer Service Client [Experimental]" + # BT_GATTC_ENABLE over BT_BLUEDROID_ENABLED: it implies the full dependency chain. + select BT_BLE_L2CAP_COC_ENABLED if BT_GATTC_ENABLE help This option enables support for the Object Transfer Service Client. diff --git a/components/bt/esp_ble_audio/host/services/ots/adapter/bluedroid/l2cap.c b/components/bt/esp_ble_audio/host/services/ots/adapter/bluedroid/l2cap.c new file mode 100644 index 00000000000..a9804e7fbc5 --- /dev/null +++ b/components/bt/esp_ble_audio/host/services/ots/adapter/bluedroid/l2cap.c @@ -0,0 +1,802 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include <../host/conn_internal.h> + +#include "osi/allocator.h" +#include "osi/thread.h" +#include "stack/l2c_api.h" +#include "stack/btm_api.h" +#include "stack/btu.h" +#include "l2c_int.h" + +#include "common/host.h" +#include "common/audio_attr.h" + +#include "ots/adapter/l2cap.h" + +LOG_MODULE_REGISTER(ISO_B2CAP, CONFIG_BT_ISO_LOG_LEVEL); + +/* BT_BLE_L2CAP_COC_MAX_CHAN sizes ble_rcb_pool[] (registered LE PSMs, not + * channels); EATT holds one slot for PSM 0x0027. */ +#if CONFIG_BT_BLE_EATT_ENABLE +_Static_assert(CONFIG_BT_BLE_L2CAP_COC_MAX_CHAN >= 2, + "OTS and EATT each need an LE CoC PSM slot"); +#endif /* CONFIG_BT_BLE_EATT_ENABLE */ + +/* L2CA callbacks identify a channel by lcid only; this table maps lcid back to + * (conn_handle, psm). One slot per connection (OTS is single-PSM). */ +struct ots_chan_slot { + uint16_t conn_handle; + uint16_t lcid; + uint16_t psm; + BD_ADDR peer_addr; + bool used; + bool pending; /* connect request sent, awaiting cfm */ + bool tx_pending; /* SDU in flight, awaiting decongestion */ +}; + +static BT_AUDIO_EXT_RAM_BSS_ATTR struct ots_chan_slot ots_chans[L2CAP_OTS_MAX_CHAN]; + +static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ots_reg_psm; + +/* ---- BTU executor types & state ------------------------------------------- + * All L2CA_* must run on BTU (single-threaded CCB). ISO entry points marshal + * via l2cap_btu_post (fire-and-forget) or l2cap_btu_invoke (sync, INIT/DEINIT + * only). ots_chans is BTU-only, so no host_lock. */ +enum l2cap_btu_cmd_type { + BTU_L2CAP_CMD_INIT, + BTU_L2CAP_CMD_DEINIT, + BTU_L2CAP_CMD_CONNECT, + BTU_L2CAP_CMD_DISCONNECT, + BTU_L2CAP_CMD_SEND, + BTU_L2CAP_CMD_ACCEPT_RSP, +}; + +struct l2cap_btu_cmd { + enum l2cap_btu_cmd_type type; + union { + struct { + uint16_t conn_handle; + } connect; + struct { + uint16_t conn_handle; + } disconnect; + struct { + uint16_t conn_handle; + BT_HDR *p_buf; + } send; + struct { + BD_ADDR bd_addr; + uint8_t id; + uint16_t lcid; + uint16_t result; + } accept_rsp; + }; + uint32_t gen; /* bumped per cycle so a late completion can be rejected */ + bool sync; /* true: give the sem on completion (INIT/DEINIT only) */ +}; + +/* Completion handoff for the sync invoke. Single set of globals, so it only works + * because INIT/DEINIT are the sole users and never overlap. */ +static BT_AUDIO_CTRL_BSS_ATTR struct k_sem btu_cmd_done; +static BT_AUDIO_CTRL_BSS_ATTR bool btu_cmd_done_init; +static BT_AUDIO_CTRL_BSS_ATTR uint32_t btu_done_gen; +static BT_AUDIO_CTRL_BSS_ATTR int btu_done_ret; + +static struct ots_chan_slot *ots_chan_find_used_by_lcid(uint16_t lcid) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].used && ots_chans[i].lcid == lcid) { + return &ots_chans[i]; + } + } + + return NULL; +} + +static struct ots_chan_slot *ots_chan_find_pending_by_addr(const BD_ADDR addr) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].pending && + memcmp(ots_chans[i].peer_addr, addr, sizeof(BD_ADDR)) == 0) { + return &ots_chans[i]; + } + } + + return NULL; +} + +/* psm is stamped here, not at cfm: a pending slot torn down by a link loss still + * has to post DISCONNECTED, and the upper layer keys that on the wire PSM. */ +static struct ots_chan_slot *ots_chan_alloc_pending(uint16_t conn_handle, const BD_ADDR addr) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (!ots_chans[i].used && !ots_chans[i].pending) { + memset(&ots_chans[i], 0, sizeof(ots_chans[i])); + ots_chans[i].conn_handle = conn_handle; + ots_chans[i].psm = L2CAP_LE_OTS_PSM; + memcpy(ots_chans[i].peer_addr, addr, sizeof(BD_ADDR)); + ots_chans[i].pending = true; + + return &ots_chans[i]; + } + } + + return NULL; +} + +static struct ots_chan_slot *ots_chan_find_pending_by_lcid(uint16_t lcid) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].pending && ots_chans[i].lcid == lcid) { + return &ots_chans[i]; + } + } + + return NULL; +} + +static struct ots_chan_slot *ots_chan_alloc_used(uint16_t conn_handle, uint16_t lcid, uint16_t psm) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (!ots_chans[i].used && !ots_chans[i].pending) { + memset(&ots_chans[i], 0, sizeof(ots_chans[i])); + ots_chans[i].conn_handle = conn_handle; + ots_chans[i].lcid = lcid; + ots_chans[i].psm = psm; + ots_chans[i].used = true; + + return &ots_chans[i]; + } + } + + return NULL; +} + +static void ots_chan_free(struct ots_chan_slot *slot) +{ + if (slot != NULL) { + memset(slot, 0, sizeof(*slot)); + } +} + +static void ots_cfg_init(tL2CAP_LE_CFG_INFO *cfg) +{ + /* mps/credits left at 0: the stack substitutes its CoC Kconfig defaults. */ + memset(cfg, 0, sizeof(*cfg)); + cfg->mtu = L2CAP_LE_OTS_MTU; +} + +/* All callbacks below run on BTU. */ + +#if CONFIG_BT_OTS +static void l2cap_connect_ind_cb(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id) +{ + struct gatt_conn *gatt_conn; + tL2CAP_LE_CFG_INFO cfg; + int err = -ENOTCONN; + + LOG_DBG("[B]L2capCocConnectInd[%04x][%04x][%u]", lcid, psm, id); + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, bd_addr, true); + if (gatt_conn != NULL) { + err = bt_le_l2cap_post_accept(gatt_conn->conn_handle, psm, lcid, id); + } + + if (err) { + /* Nothing will answer on iso_task, so reject here. */ + LOG_ERR("[B]L2capIndRefuse[%04x][%p][%d]", lcid, gatt_conn, err); + + ots_cfg_init(&cfg); + + L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_LE_ERR_NO_RESOURCES, 0, &cfg); + } +} +#endif /* CONFIG_BT_OTS */ + +/* Unwind the optimistic chan_add done by bt_l2cap_chan_connect()/l2cap_accept(): + * without a DISCONNECTED the upper layer waits for a result that never comes. */ +static void l2cap_cfm_fail(uint16_t conn_handle, const BD_ADDR addr) +{ + ots_chan_free(ots_chan_find_pending_by_addr(addr)); + + bt_le_l2cap_post_disconnected(conn_handle, L2CAP_LE_OTS_PSM); +} + +/* Fires for both roles. remote_cid has no public L2CA getter, and peer_addr + * is needed to match a pending slot, so the CCB is touched for those two only. */ +static void l2cap_connect_cfm_cb(UINT16 lcid, UINT16 result) +{ + struct gatt_conn *gatt_conn; + struct ots_chan_slot *slot; + tL2C_CCB *p_ccb; + tL2CAP_LE_CFG_INFO peer_cfg; + uint16_t conn_handle_h, psm_h; + + LOG_DBG("[B]L2capCocConnectCfm[%04x][%u]", lcid, result); + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || p_ccb->p_lcb == NULL) { + LOG_ERR("[B]L2capCfmNoCcb[%04x][%p]", lcid, p_ccb); + return; + } + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, p_ccb->p_lcb->remote_bd_addr, true); + if (gatt_conn == NULL) { + LOG_ERR("[B]L2capCfmNoConn[%04x]", lcid); + + /* No gatt_conn, so the pending slot is the only source of conn_handle. */ + slot = ots_chan_find_pending_by_addr(p_ccb->p_lcb->remote_bd_addr); + if (slot != NULL) { + l2cap_cfm_fail(slot->conn_handle, p_ccb->p_lcb->remote_bd_addr); + } + return; + } + + if (result != L2CAP_CONN_OK) { + LOG_ERR("[B]L2capCocConnectFail[%04x][%u]", lcid, result); + l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr); + return; + } + + if (ots_chan_find_used_by_lcid(lcid) != NULL) { + /* Duplicate cfm for a live channel: the first one already reported it. */ + LOG_ERR("[B]L2capCocChanExist[%04x]", lcid); + return; + } + + if (!L2CA_GetPeerLECocConfig(lcid, &peer_cfg)) { + LOG_ERR("[B]L2capCfmNoPeerCfg[%04x]", lcid); + l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr); + return; + } + + /* Outgoing connect: reuse the pending slot reserved at chan_connect. + * Incoming accept: no pending slot, allocate a fresh one. */ + slot = ots_chan_find_pending_by_addr(p_ccb->p_lcb->remote_bd_addr); + if (slot != NULL) { + slot->pending = false; + slot->lcid = lcid; + slot->psm = L2CAP_LE_OTS_PSM; + slot->used = true; + } else { + slot = ots_chan_alloc_used(gatt_conn->conn_handle, lcid, L2CAP_LE_OTS_PSM); + if (slot == NULL) { + LOG_ERR("[B]L2capCocNoSlot[%04x]", lcid); + l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr); + return; + } + } + + conn_handle_h = slot->conn_handle; + psm_h = slot->psm; + + LOG_INF("[B]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u]", + conn_handle_h, psm_h, + lcid, p_ccb->remote_cid, + L2CAP_LE_OTS_MTU, peer_cfg.mtu); + + bt_le_l2cap_post_connected(conn_handle_h, psm_h, + p_ccb->remote_cid, peer_cfg.mtu, + lcid, L2CAP_LE_OTS_MTU); +} + +/* Also fires for local disconnect, so DisconnectCfm needs no handler. */ +static void l2cap_disconnect_ind_cb(UINT16 lcid, BOOLEAN local_init) +{ + struct ots_chan_slot *slot; + uint16_t conn_handle_h, psm_h; + + LOG_DBG("[B]L2capCocDisconnectInd[%04x][%u]", lcid, local_init); + + slot = ots_chan_find_used_by_lcid(lcid); + if (slot == NULL) { + /* A link lost while the connect was still outstanding arrives here, not + * as a failed cfm (see the LP_DISCONNECT_IND case of l2c_csm's + * CST_W4_L2CAP_CONNECT_RSP). Without this the pending slot would leak and + * block every later OTS connect on that conn_handle. */ + slot = ots_chan_find_pending_by_lcid(lcid); + if (slot == NULL) { + LOG_ERR("[B]L2capDisconnectInvCocChan[%04x]", lcid); + return; + } + } + + conn_handle_h = slot->conn_handle; + psm_h = slot->psm; + ots_chan_free(slot); + + LOG_INF("[B]L2capCocDisconnect[%u][%04x]", conn_handle_h, psm_h); + + bt_le_l2cap_post_disconnected(conn_handle_h, psm_h); +} + +static void l2cap_data_ind_cb(UINT16 lcid, BT_HDR *p_buf) +{ + struct ots_chan_slot *slot; + uint16_t conn_handle_h, psm_h; + + if (p_buf == NULL) { + LOG_ERR("[B]L2capRecvNullBuf[%04x]", lcid); + return; + } + + LOG_DBG("[B]L2capCocRecv[%04x][%u]", lcid, p_buf->len); + + slot = ots_chan_find_used_by_lcid(lcid); + if (slot == NULL) { + LOG_ERR("[B]L2capRecvOnInvCocChan[%04x]", lcid); + osi_free(p_buf); + return; + } + + conn_handle_h = slot->conn_handle; + psm_h = slot->psm; + + bt_le_l2cap_post_received(conn_handle_h, psm_h, + p_buf->data + p_buf->offset, p_buf->len); + + /* SDU ownership was handed to this callback; the stack osi_malloc'd it. */ + osi_free(p_buf); +} + +/* Decongestion doubles as TX-done but can fire twice per SDU; tx_pending + * collapses the pair into one ops->sent. */ +static void l2cap_congestion_cb(UINT16 lcid, BOOLEAN congested) +{ + struct ots_chan_slot *slot; + uint16_t conn_handle_h, psm_h; + + LOG_DBG("[B]L2capCocCongestion[%04x][%u]", lcid, congested); + + slot = ots_chan_find_used_by_lcid(lcid); + if (slot == NULL) { + LOG_ERR("[B]L2capCongestionInvCocChan[%04x]", lcid); + return; + } + + if (congested || !slot->tx_pending) { + return; + } + + slot->tx_pending = false; + conn_handle_h = slot->conn_handle; + psm_h = slot->psm; + + bt_le_l2cap_post_sent(conn_handle_h, psm_h); +} + +/* ---- BTU executor functions ---------------------------------------------- */ + +static int l2cap_btu_do_accept_rsp(struct l2cap_btu_cmd *cmd) +{ + tL2CAP_LE_CFG_INFO cfg; + + ots_cfg_init(&cfg); + + if (!L2CA_ConnectLECocRsp(cmd->accept_rsp.bd_addr, cmd->accept_rsp.id, + cmd->accept_rsp.lcid, cmd->accept_rsp.result, 0, &cfg)) { + LOG_ERR("[B]L2capAcceptFail[%04x]", cmd->accept_rsp.lcid); + return -EIO; + } + + return 0; +} + +static void l2cap_btu_exec(void *ctx) +{ + struct l2cap_btu_cmd *cmd = ctx; + tL2CAP_LE_CFG_INFO cfg; + uint16_t lcid; + int ret = -EINVAL; + + switch (cmd->type) { + case BTU_L2CAP_CMD_INIT: { + tL2CAP_APPL_INFO appl = {0}; +#if CONFIG_BT_OTS + appl.pL2CA_ConnectInd_Cb = l2cap_connect_ind_cb; +#endif /* CONFIG_BT_OTS */ + appl.pL2CA_ConnectCfm_Cb = l2cap_connect_cfm_cb; + appl.pL2CA_DisconnectInd_Cb = l2cap_disconnect_ind_cb; + appl.pL2CA_DataInd_Cb = l2cap_data_ind_cb; + appl.pL2CA_CongestionStatus_Cb = l2cap_congestion_cb; + + ots_reg_psm = L2CA_RegisterLECoc(L2CAP_LE_OTS_PSM, &appl); + if (ots_reg_psm == 0) { + ret = -EIO; + break; + } + + BTM_SetSecurityLevel(TRUE, "BLE_L2CAP_OTS", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, ots_reg_psm, BTM_SEC_PROTO_L2CAP, 0); + BTM_SetSecurityLevel(FALSE, "BLE_L2CAP_OTS", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, ots_reg_psm, BTM_SEC_PROTO_L2CAP, 0); + ret = 0; + break; + } + case BTU_L2CAP_CMD_DEINIT: + /* Tear our channels down first: L2CA_DeregisterLECoc only inspects each + * link's first CCB, so a CoC CCB sitting behind another one survives and + * is left pointing at the released RCB (p_ccb->p_rcb->api use-after-free). */ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + /* pending too: its CCB is live from ConnectLECocReq onwards, and + LECocDisconnect cancels a not-yet-open channel locally. */ + if (ots_chans[i].used || ots_chans[i].pending) { + L2CA_LECocDisconnect(ots_chans[i].lcid); + } + } + + if (ots_reg_psm) { + L2CA_DeregisterLECoc(ots_reg_psm); + ots_reg_psm = 0; + } + ret = 0; + break; + case BTU_L2CAP_CMD_CONNECT: { + struct gatt_conn *gatt_conn; + struct ots_chan_slot *slot; + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(cmd->connect.conn_handle); + if (gatt_conn == NULL) { + LOG_ERR("[B]L2capNoConnInfo[%u]", cmd->connect.conn_handle); + ret = -ENOTCONN; + break; + } + + /* Reject if this connection already has an OTS channel. */ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if ((ots_chans[i].used || ots_chans[i].pending) && + ots_chans[i].conn_handle == cmd->connect.conn_handle) { + LOG_WRN("[B]L2capOtsChanExist[%u]", cmd->connect.conn_handle); + ret = -EALREADY; + break; + } + } + if (ret == -EALREADY) { + break; + } + + slot = ots_chan_alloc_pending(cmd->connect.conn_handle, gatt_conn->peer.val); + if (slot == NULL) { + LOG_ERR("[B]L2capOtsNoSlot[%u]", cmd->connect.conn_handle); + ret = -ENOMEM; + break; + } + + ots_cfg_init(&cfg); + + /* Returns 0 on failure, LCID on success. Keep the LCID: if the link dies + * before the cfm the stack reports DisconnectInd, and by then the CCB is + * released so the LCID is the only way back to this slot. */ + lcid = L2CA_ConnectLECocReq(ots_reg_psm, gatt_conn->peer.val, &cfg); + if (lcid == 0) { + ret = -EIO; + ots_chan_free(slot); + /* Post DISCONNECTED to clean up the optimistic chan_add. */ + bt_le_l2cap_post_disconnected(cmd->connect.conn_handle, L2CAP_LE_OTS_PSM); + break; + } + + slot->lcid = lcid; + ret = 0; + break; + } + case BTU_L2CAP_CMD_DISCONNECT: { + struct ots_chan_slot *slot = NULL; + + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].used && ots_chans[i].conn_handle == cmd->disconnect.conn_handle) { + slot = &ots_chans[i]; + break; + } + } + if (slot == NULL) { + LOG_WRN("[B]L2capNoOtsChan"); + ret = -ENOTCONN; + break; + } + + ret = L2CA_LECocDisconnect(slot->lcid) ? 0 : -EIO; + break; + } + case BTU_L2CAP_CMD_SEND: { + struct ots_chan_slot *slot = NULL; + + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].used && ots_chans[i].conn_handle == cmd->send.conn_handle) { + slot = &ots_chans[i]; + break; + } + } + if (slot == NULL) { + LOG_WRN("[B]L2capNoOtsChan"); + /* Slot gone (disconnect won the race); free p_buf ourselves. */ + osi_free(cmd->send.p_buf); + ret = -ENOTCONN; + break; + } + + /* Armed before the write: a non-congested SDU completes inside it and + * raises the decongestion cb synchronously (same BTU task). */ + slot->tx_pending = true; + /* On DW_FAILED the stack frees p_buf; on SUCCESS/CONGESTED it owns it. */ + ret = (L2CA_LECocDataWrite(slot->lcid, cmd->send.p_buf) == L2CAP_DW_FAILED) + ? -EIO : 0; + if (ret != 0) { + slot->tx_pending = false; + } + break; + } + case BTU_L2CAP_CMD_ACCEPT_RSP: + ret = l2cap_btu_do_accept_rsp(cmd); + break; + default: + ret = -EINVAL; + break; + } + + if (cmd->sync) { + btu_done_ret = ret; + btu_done_gen = cmd->gen; + k_sem_give(&btu_cmd_done); + } + + free(cmd); +} + +/* Post cmd to BTU fire-and-forget. Returns 0 on success, -EIO if BTU is + * unavailable (cmd is freed on failure). Never blocks. No self-delivery check is + * needed - unlike the sync invoke, posting to our own queue cannot deadlock. */ +static int l2cap_btu_post(struct l2cap_btu_cmd *cmd) +{ + osi_thread_t *btu = btu_get_current_thread(); + + cmd->sync = false; + + if (btu == NULL || !osi_thread_post(btu, l2cap_btu_exec, cmd, 0, 0)) { + LOG_ERR("[B]L2capBtuPostFail[%u]", cmd->type); + free(cmd); + return -EIO; + } + + return 0; +} + +void bt_le_bluedroid_l2cap_accept_result(uint16_t conn_handle, uint8_t l2cap_id, + uint16_t chan_handle, uint16_t result) +{ + struct gatt_conn *gatt_conn; + struct l2cap_btu_cmd *cmd; + + LOG_DBG("[B]L2capAcceptResult[%04x][%04x]", chan_handle, result); + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn_handle); + if (gatt_conn == NULL) { + LOG_ERR("[B]L2capAcceptNoConn[%u]", conn_handle); + return; + } + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capAcceptNoCmd"); + return; + } + + cmd->type = BTU_L2CAP_CMD_ACCEPT_RSP; + cmd->accept_rsp.id = l2cap_id; + cmd->accept_rsp.lcid = chan_handle; + cmd->accept_rsp.result = result; + memcpy(cmd->accept_rsp.bd_addr, gatt_conn->peer.val, sizeof(BD_ADDR)); + + l2cap_btu_post(cmd); +} + +int bt_le_bluedroid_l2cap_chan_connect(uint16_t conn_handle) +{ + struct l2cap_btu_cmd *cmd; + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capConnectNoCmd"); + return -ENOMEM; + } + + cmd->type = BTU_L2CAP_CMD_CONNECT; + cmd->connect.conn_handle = conn_handle; + + /* Result comes back as CONNECTED, or DISCONNECTED if BTU's connect fails. */ + return l2cap_btu_post(cmd); +} + +int bt_le_bluedroid_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) +{ + struct l2cap_btu_cmd *cmd; + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capDisconnectNoCmd"); + return -ENOMEM; + } + + cmd->type = BTU_L2CAP_CMD_DISCONNECT; + cmd->disconnect.conn_handle = chan->conn->handle; + + /* Result comes back as DISCONNECTED via l2cap_disconnect_ind_cb. */ + return l2cap_btu_post(cmd); +} + +int bt_le_bluedroid_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct l2cap_btu_cmd *cmd; + BT_HDR *p_buf; + int ret; + + /* osi_malloc, not bt_le_int_malloc: the stack frees this SDU with osi_free, + * and it never reaches DMA - the TX path copies it into a K-frame buffer. */ + p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + buf->len); + if (p_buf == NULL) { + LOG_ERR("[B]L2capNoBufForSend[%u]", buf->len); + return -ENOMEM; + } + + /* offset stays 0: the CoC TX path builds its own K-frames, no headroom here. */ + memset(p_buf, 0, sizeof(*p_buf)); + p_buf->len = buf->len; + memcpy(p_buf->data, buf->data, buf->len); + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capSendNoCmd"); + osi_free(p_buf); + return -ENOMEM; + } + + cmd->type = BTU_L2CAP_CMD_SEND; + cmd->send.conn_handle = chan->conn->handle; + cmd->send.p_buf = p_buf; + + /* Completion comes back as ops->sent; on DW_FAILED nothing does, and the + * eventual DISCONNECTED aborts the transfer. */ + ret = l2cap_btu_post(cmd); + if (ret != 0) { + /* BTU unavailable: p_buf not queued, free it. buf stays with caller. */ + osi_free(p_buf); + return ret; + } + + /* Posted: payload is in p_buf, release caller's buf else ot_chan_tx_pool + * (1 entry) leaks. */ + net_buf_unref(buf); + + return 0; +} + +/* Run cmd on BTU and wait — INIT/DEINIT only. Hot-path ops use + * l2cap_btu_post (fire-and-forget). cmd is freed by l2cap_btu_exec. */ +static int l2cap_btu_invoke(struct l2cap_btu_cmd *cmd) +{ + osi_thread_t *btu = btu_get_current_thread(); + uint32_t gen = ++btu_done_gen; /* unique per cycle; stale gives are ignored */ + + cmd->gen = gen; + cmd->sync = true; + + /* Self-delivery would deadlock: run inline and drain the give. */ + if (btu != NULL && strcmp(osi_thread_name(btu), pcTaskGetName(NULL)) == 0) { + l2cap_btu_exec(cmd); + k_sem_take(&btu_cmd_done, 0); + return btu_done_ret; + } + + k_sem_reset(&btu_cmd_done); + + if (btu == NULL || !osi_thread_post(btu, l2cap_btu_exec, cmd, 0, 0)) { + LOG_WRN("[B]L2capBtuUnavailable[%u]", cmd->type); + l2cap_btu_exec(cmd); + k_sem_take(&btu_cmd_done, 0); + return btu_done_ret; + } + + if (k_sem_take(&btu_cmd_done, K_SEM_SHORT) != 0) { + LOG_ERR("[B]L2capBtuTimeout[%u]", cmd->type); + /* cmd still on the BTU queue; bump gen so a late completion is rejected. */ + btu_done_gen++; + return -ETIMEDOUT; + } + + if (btu_done_gen != gen) { + LOG_ERR("[B]L2capBtuStaleGen[%u][%u]", gen, btu_done_gen); + return -ETIMEDOUT; + } + + return btu_done_ret; +} + +int bt_le_bluedroid_l2cap_init(void) +{ + struct l2cap_btu_cmd *cmd; + int ret; + + LOG_DBG("[B]L2capInit"); + + memset(ots_chans, 0, sizeof(ots_chans)); + + if (!btu_cmd_done_init) { + k_sem_create(&btu_cmd_done); + btu_cmd_done_init = true; + } + + /* Drop any stale give so the first invoke doesn't match an old completion. */ + k_sem_reset(&btu_cmd_done); + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capInitNoCmd"); + return -ENOMEM; + } + + cmd->type = BTU_L2CAP_CMD_INIT; + + ret = l2cap_btu_invoke(cmd); + if (ret == -ETIMEDOUT) { + /* The command is still queued and will register the PSM once BTU drains + * it, while we report failure. Queue a DEINIT behind it (the BTU queue + * is FIFO) so the registration cannot outlive this failed init. */ + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capInitNoUndoCmd"); + return ret; + } + + cmd->type = BTU_L2CAP_CMD_DEINIT; + l2cap_btu_post(cmd); + } + + return ret; +} + +void bt_le_bluedroid_l2cap_deinit(void) +{ + struct l2cap_btu_cmd *cmd; + int ret; + + LOG_DBG("[B]L2capDeinit"); + + /* If init never ran, nothing to tear down and the sem is unsafe to touch. */ + if (!btu_cmd_done_init || ots_reg_psm == 0) { + memset(ots_chans, 0, sizeof(ots_chans)); + return; + } + + cmd = bt_le_ext_calloc(1, sizeof(*cmd)); + if (cmd == NULL) { + LOG_ERR("[B]L2capDeinitNoCmd"); + memset(ots_chans, 0, sizeof(ots_chans)); + return; + } + + cmd->type = BTU_L2CAP_CMD_DEINIT; + + ret = l2cap_btu_invoke(cmd); + if (ret == 0) { + memset(ots_chans, 0, sizeof(ots_chans)); + } else { + /* Timed out: DEINIT still queued. Leave ots_chans so the BTU handler's + * disconnect loop runs before PSM deregister. */ + LOG_ERR("[B]L2capDeinitTimeout"); + } +} diff --git a/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.c b/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.c new file mode 100644 index 00000000000..d33bdd98f59 --- /dev/null +++ b/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.c @@ -0,0 +1,829 @@ +/* + * SPDX-FileCopyrightText: 2015-2016 Intel Corporation + * SPDX-FileCopyrightText: 2023 Nordic Semiconductor + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include <../host/conn_internal.h> + +#include "common/host.h" +#include "common/audio_attr.h" +#include "common/app/gap.h" + +#include "ots/adapter/l2cap.h" + +LOG_MODULE_REGISTER(ISO_L2CAP, CONFIG_BT_ISO_LOG_LEVEL); + +#define L2CAP_LE_MIN_MTU 23 +#define L2CAP_ECRED_MIN_MTU 64 + +#define L2CAP_LE_CID_DYN_START 0x0040 +#define L2CAP_LE_CID_DYN_END 0x007F +#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END) + +#define L2CAP_LE_PSM_FIXED_START 0x0001 +#define L2CAP_LE_PSM_FIXED_END 0x007F +#define L2CAP_LE_PSM_DYN_START 0x0080 +#define L2CAP_LE_PSM_DYN_END 0x00FF +#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END) + +static BT_AUDIO_EXT_RAM_BSS_ATTR sys_slist_t l2cap_servers; + +/* OTS internals, declared here rather than including the OTS private headers. */ +#if CONFIG_BT_OTS +extern int bt_gatt_ots_conn_cb_register(void); +extern void bt_gatt_ots_conn_cb_unregister(void); +extern int bt_gatt_ots_instances_prepare(void); +#endif /* CONFIG_BT_OTS */ +#if CONFIG_BT_OTS_CLIENT +extern int bt_gatt_ots_client_conn_cb_register(void); +extern void bt_gatt_ots_client_conn_cb_unregister(void); +#endif /* CONFIG_BT_OTS_CLIENT */ +extern int bt_gatt_ots_l2cap_init(void); + +static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { + if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) { + return chan; + } + } + + return NULL; +} + +__attribute__((unused)) +static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { + if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) { + return chan; + } + } + + return NULL; +} + +static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm) +{ + struct bt_l2cap_chan *chan; + + SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { + if (BT_L2CAP_LE_CHAN(chan)->psm == psm) { + return chan; + } + } + + return NULL; +} + +static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm) +{ + LOG_DBG("L2capChanAdd[%04x]", psm); + + /* Attach channel to the connection */ + if (sys_slist_find(&conn->channels, &chan->node, NULL)) { + LOG_WRN("L2capChanExists[%04x]", psm); + return false; + } + + sys_slist_append(&conn->channels, &chan->node); + chan->conn = conn; + + /* Set channel PSM */ + BT_L2CAP_LE_CHAN(chan)->psm = psm; + + return true; +} + +static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm) +{ + struct bt_l2cap_server *server = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) { + if (server->psm == psm) { + break; + } + } + + return server; +} + +static inline uint16_t err_to_result(int err) +{ + switch (err) { + case -ENOMEM: + return L2CAP_LE_ERR_NO_RESOURCES; + case -EACCES: + return L2CAP_LE_ERR_AUTHORIZATION; + case -EPERM: + return L2CAP_LE_ERR_KEY_SIZE; + case -ENOTSUP: + /* This handle the cases where a fixed channel is registered but + * for some reason (e.g. controller not supporting a feature) + * cannot be used. + */ + return L2CAP_LE_ERR_PSM_NOT_SUPP; + default: + return L2CAP_LE_ERR_UNACCEPT_PARAMS; + } +} + +static int l2cap_accept(uint16_t conn_handle, uint16_t psm, + uint16_t scid, uint16_t mtu, + uint16_t mps, uint16_t credits, + uint16_t *result) +{ + struct bt_l2cap_server *server; + struct bt_l2cap_chan *chan; + struct bt_conn *conn; + int err; + + ARG_UNUSED(credits); + + LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps); + + conn = bt_le_acl_conn_find(conn_handle); + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); + + *result = L2CAP_LE_ERR_INVALID_PARAMS; + return -ENOTCONN; + } + + /* Check if there is a server registered */ + server = l2cap_server_lookup_psm(psm); + if (server == NULL) { + LOG_ERR("L2capSrvNotReg[%04x]", psm); + + *result = L2CAP_LE_ERR_PSM_NOT_SUPP; + return -ENOTSUP; + } + + if (!L2CAP_LE_CID_IS_DYN(scid)) { + LOG_ERR("L2capNotDynScid[%04x]", scid); + + *result = L2CAP_LE_ERR_INVALID_SCID; + return -EINVAL; + } + + chan = l2cap_lookup_tx_cid(conn, scid); + if (chan) { + LOG_WRN("L2capScidUsed[%04x]", scid); + + *result = L2CAP_LE_ERR_SCID_IN_USE; + return -EALREADY; + } + + /* Every event is routed back by (conn, psm), so a second channel for the + * same pair would deliver to whichever chan l2cap_lookup_psm() hits first. */ + if (l2cap_lookup_psm(conn, psm) != NULL) { + LOG_WRN("L2capPsmChanExists[%u][%04x]", conn_handle, psm); + + *result = L2CAP_LE_ERR_NO_RESOURCES; + return -EALREADY; + } + + if (server->accept == NULL) { + LOG_ERR("L2capSrvAcceptNull"); + + *result = L2CAP_LE_ERR_INVALID_PARAMS; + return -EIO; + } + + err = server->accept(conn, server, &chan); + if (err) { + LOG_ERR("L2capSrvAcceptFail[%d]", err); + + *result = err_to_result(err); + return -EIO; + } + + if (chan == NULL) { + LOG_ERR("L2capSrvAcceptNullChan"); + *result = L2CAP_LE_ERR_NO_RESOURCES; + return -ENOMEM; + } + + if (l2cap_chan_add(conn, chan, psm) == false) { + *result = L2CAP_LE_ERR_NO_RESOURCES; + return -ENOMEM; + } + + *result = L2CAP_LE_SUCCESS; + return 0; +} + +#if CONFIG_BT_BLUEDROID_ENABLED +static void l2cap_handle_accept(const struct bt_le_l2cap_event *qev) +{ + uint16_t result = L2CAP_LE_ERR_NO_RESOURCES; + + /* chan_handle is our local CID; the peer's is unknown until the channel is + * up, so it stands in for scid (dynamic-CID check and duplicate detection). + * mtu/mps are logging-only in l2cap_accept(). */ + (void)l2cap_accept(qev->conn_handle, qev->psm, qev->accept.chan_handle, + 0, 0, 0, &result); + + /* Forward result, not a bool: l2cap_accept() distinguishes authentication, + * key size and SCID-in-use, and the peer is entitled to the exact reason. */ + bt_le_bluedroid_l2cap_accept_result(qev->conn_handle, qev->accept.l2cap_id, + qev->accept.chan_handle, result); +} +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ + +static void l2cap_connected(uint16_t conn_handle, uint16_t psm, + uint16_t tx_cid, uint16_t tx_mtu, + uint16_t rx_cid, uint16_t rx_mtu) +{ + struct bt_l2cap_chan *chan; + struct bt_conn *conn; + + LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]", + conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu); + + conn = bt_le_acl_conn_find(conn_handle); + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); + return; + } + + chan = l2cap_lookup_psm(conn, psm); + if (chan == NULL) { + LOG_ERR("L2capPsmNotFound[%04x]", psm); + return; + } + + BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid; + BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu; + BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid; + BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu; + + if (chan->ops->connected) { + chan->ops->connected(chan); + } +} + +static void l2cap_disconnected(uint16_t conn_handle, uint16_t psm) +{ + struct bt_l2cap_chan *chan; + struct bt_conn *conn; + + LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm); + + conn = bt_le_acl_conn_find(conn_handle); + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); + return; + } + + chan = l2cap_lookup_psm(conn, psm); + if (chan == NULL) { + LOG_ERR("L2capPsmNotFound[%04x]", psm); + return; + } + + /* Unlink first so no later event rediscovers it by PSM. The callback must not + * free chan: it reads chan->conn, and clearing conn after returns the pool ctx. */ + sys_slist_find_and_remove(&conn->channels, &chan->node); + + if (chan->ops->disconnected) { + chan->ops->disconnected(chan); + } + + chan->conn = NULL; +} + +static void l2cap_received(uint16_t conn_handle, uint16_t psm, + uint8_t *data, uint16_t len) +{ + struct bt_l2cap_chan *chan; + struct net_buf buf = {0}; + struct bt_conn *conn; + + LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len); + + conn = bt_le_acl_conn_find(conn_handle); + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); + return; + } + + chan = l2cap_lookup_psm(conn, psm); + if (chan == NULL) { + LOG_ERR("L2capPsmNotFound[%04x]", psm); + return; + } + + buf.data = data; + buf.len = len; + + if (chan->ops->recv) { + chan->ops->recv(chan, &buf); + } +} + +static void l2cap_sent(uint16_t conn_handle, uint16_t psm) +{ + struct bt_l2cap_chan *chan; + struct bt_conn *conn; + + LOG_DBG("L2capSent[%u][%04x]", conn_handle, psm); + + conn = bt_le_acl_conn_find(conn_handle); + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_INF("L2capSentNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); + return; + } + + chan = l2cap_lookup_psm(conn, psm); + if (chan == NULL) { + LOG_ERR("L2capPsmNotFound[%04x]", psm); + return; + } + + if (chan->ops->sent) { + chan->ops->sent(chan); + } +} + +static int l2cap_post_event(struct bt_le_l2cap_event *qev) +{ + int err; + + err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT, qev, sizeof(*qev)); + if (err) { + LOG_ERR("L2capPostFail[%d][%u]", err, qev->type); + if (qev->type == BT_LE_L2CAP_EVENT_RECEIVED) { + free(qev->received.data); + } + free(qev); + } + + return err; +} + +/* Alloc + stamp the common header. NULL on OOM rather than abort: L2CAP is off + * the ISO data path, so losing one event beats taking the whole stack down. */ +static struct bt_le_l2cap_event *l2cap_event_alloc(uint8_t type, uint16_t conn_handle, + uint16_t psm) +{ + struct bt_le_l2cap_event *qev; + + qev = bt_le_ext_calloc(1, sizeof(*qev)); + if (qev == NULL) { + return NULL; + } + + qev->type = type; + qev->conn_handle = conn_handle; + qev->psm = psm; + + return qev; +} + +_IDF_ONLY +int bt_le_l2cap_post_connected(uint16_t conn_handle, uint16_t psm, + uint16_t tx_cid, uint16_t tx_mtu, + uint16_t rx_cid, uint16_t rx_mtu) +{ + struct bt_le_l2cap_event *qev; + + qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_CONNECTED, conn_handle, psm); + if (qev == NULL) { + LOG_ERR("L2capConnectedNoMem[%u][%04x]", conn_handle, psm); + return -ENOMEM; + } + + qev->connected.tx_cid = tx_cid; + qev->connected.tx_mtu = tx_mtu; + qev->connected.rx_cid = rx_cid; + qev->connected.rx_mtu = rx_mtu; + + return l2cap_post_event(qev); +} + +_IDF_ONLY +int bt_le_l2cap_post_disconnected(uint16_t conn_handle, uint16_t psm) +{ + struct bt_le_l2cap_event *qev; + + qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_DISCONNECTED, conn_handle, psm); + if (qev == NULL) { + LOG_ERR("L2capDisconnectedNoMem[%u][%04x]", conn_handle, psm); + return -ENOMEM; + } + + return l2cap_post_event(qev); +} + +_IDF_ONLY +int bt_le_l2cap_post_received(uint16_t conn_handle, uint16_t psm, + const uint8_t *data, uint16_t len) +{ + struct bt_le_l2cap_event *qev; + + qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_RECEIVED, conn_handle, psm); + if (qev == NULL) { + LOG_ERR("L2capReceivedNoMem[%u][%04x][%u]", conn_handle, psm, len); + return -ENOMEM; + } + + qev->received.len = len; + + if (len) { + qev->received.data = bt_le_ext_calloc(1, len); + if (qev->received.data == NULL) { + LOG_ERR("L2capReceivedNoDataMem[%u][%04x][%u]", conn_handle, psm, len); + free(qev); + return -ENOMEM; + } + memcpy(qev->received.data, data, len); + } + + return l2cap_post_event(qev); +} + +_IDF_ONLY +int bt_le_l2cap_post_sent(uint16_t conn_handle, uint16_t psm) +{ + struct bt_le_l2cap_event *qev; + + qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_SENT, conn_handle, psm); + if (qev == NULL) { + LOG_ERR("L2capSentNoMem[%u][%04x]", conn_handle, psm); + return -ENOMEM; + } + + return l2cap_post_event(qev); +} + +#if CONFIG_BT_BLUEDROID_ENABLED +_IDF_ONLY +int bt_le_l2cap_post_accept(uint16_t conn_handle, uint16_t psm, + uint16_t chan_handle, uint8_t l2cap_id) +{ + struct bt_le_l2cap_event *qev; + + qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_ACCEPT, conn_handle, psm); + if (qev == NULL) { + LOG_ERR("L2capAcceptNoMem[%u][%04x][%04x]", conn_handle, psm, chan_handle); + return -ENOMEM; + } + + qev->accept.chan_handle = chan_handle; + qev->accept.l2cap_id = l2cap_id; + + return l2cap_post_event(qev); +} +#else /* !CONFIG_BT_BLUEDROID_ENABLED */ +_IDF_ONLY +int bt_le_l2cap_accept_safe(uint16_t conn_handle, uint16_t psm, + uint16_t scid, uint16_t mtu, + uint16_t mps, uint16_t credits, + uint16_t *result) +{ + int err; + bt_le_host_lock(); + err = l2cap_accept(conn_handle, psm, scid, mtu, mps, credits, result); + bt_le_host_unlock(); + return err; +} +#endif /* !CONFIG_BT_BLUEDROID_ENABLED */ + +_IDF_ONLY +void bt_le_l2cap_handle_event(void *data, size_t data_len) +{ + struct bt_le_l2cap_event *qev = data; + + if (qev == NULL) { + LOG_ERR("L2capEvtNull"); + return; + } + + if (data_len != sizeof(*qev)) { + LOG_ERR("L2capEvtBadLen[%u]", (unsigned)data_len); + free(qev); + return; + } + + bt_le_host_lock(); + + switch (qev->type) { + case BT_LE_L2CAP_EVENT_CONNECTED: + l2cap_connected(qev->conn_handle, qev->psm, + qev->connected.tx_cid, qev->connected.tx_mtu, + qev->connected.rx_cid, qev->connected.rx_mtu); + break; + case BT_LE_L2CAP_EVENT_DISCONNECTED: + l2cap_disconnected(qev->conn_handle, qev->psm); + break; + case BT_LE_L2CAP_EVENT_RECEIVED: + l2cap_received(qev->conn_handle, qev->psm, + qev->received.data, qev->received.len); + free(qev->received.data); + break; + case BT_LE_L2CAP_EVENT_SENT: + l2cap_sent(qev->conn_handle, qev->psm); + break; +#if CONFIG_BT_BLUEDROID_ENABLED + case BT_LE_L2CAP_EVENT_ACCEPT: + l2cap_handle_accept(qev); + break; +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ + default: + LOG_ERR("L2capEvtUnknown[%u]", qev->type); + break; + } + + bt_le_host_unlock(); + + free(qev); +} + +_IDF_ONLY +int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm) +{ + int err; + + LOG_DBG("L2capChanConnect[%04x]", psm); + + if (chan == NULL) { + LOG_ERR("L2capChanNull"); + return -EINVAL; + } + + if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) { + LOG_ERR("L2capInvPsm[%04x]", psm); + return -EINVAL; + } + + /* Caller holds bt_le_host_lock. chan_add is done BEFORE the adapter call + * (optimistic): the Bluedroid adapter posts fire-and-forget, so the + * CONNECTED/DISCONNECTED event needs the chan already in conn->channels. + * On failure the adapter posts DISCONNECTED, matching the Zephyr + * connect-fail contract. */ + if (conn == NULL || conn->state != BT_CONN_CONNECTED) { + LOG_ERR("L2capChanNotConn[%p]", conn); + return -ENOTCONN; + } + + /* Same (conn, psm) uniqueness as l2cap_accept(): event routing keys on it. + * l2cap_chan_add only rejects re-adding this very chan object. */ + if (l2cap_lookup_psm(conn, psm) != NULL) { + LOG_WRN("L2capPsmChanExists[%u][%04x]", conn->handle, psm); + return -EALREADY; + } + + if (!l2cap_chan_add(conn, chan, psm)) { + return -EALREADY; + } + +#if CONFIG_BT_BLUEDROID_ENABLED + err = bt_le_bluedroid_l2cap_chan_connect(conn->handle); +#else + err = bt_le_nimble_l2cap_chan_connect(conn->handle); +#endif + if (err) { + /* Roll back the optimistic chan_add. */ + sys_slist_find_and_remove(&conn->channels, &chan->node); + chan->conn = NULL; + return err; + } + + return 0; +} + +_IDF_ONLY +int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) +{ + int err; + + LOG_DBG("L2capChanDisconnect"); + + if (chan == NULL) { + LOG_ERR("L2capChanNull"); + return -EINVAL; + } + + if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) { + LOG_ERR("L2capChanNotConn[%p]", chan->conn); + return -ENOTCONN; + } + +#if CONFIG_BT_BLUEDROID_ENABLED + err = bt_le_bluedroid_l2cap_chan_disconnect(chan); +#else + err = bt_le_nimble_l2cap_chan_disconnect(chan); +#endif + if (err) { + /* If the disconnect failed, remove the channel from the connection. + * Otherwise the removal will be handled by the disconnect callback. */ + if (chan->conn != NULL) { + sys_slist_find_and_remove(&chan->conn->channels, &chan->node); + chan->conn = NULL; + } + } + + return err; +} + +_IDF_ONLY +int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + int err; + + if (chan == NULL || buf == NULL) { + LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf); + return -EINVAL; + } + + LOG_DBG("L2capChanSend[%u]", buf->len); + + /* Caller holds bt_le_host_lock. The Bluedroid adapter posts fire-and-forget; + * returns 0 (buf ownership transferred) or error (buf stays with caller). + * TX completion via ops->sent. */ + if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) { + LOG_ERR("L2capChanNotConn[%p]", chan->conn); + return -ENOTCONN; + } + + if (buf->len > BT_L2CAP_LE_CHAN(chan)->tx.mtu) { + LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len, + BT_L2CAP_LE_CHAN(chan)->tx.mtu); + return -EMSGSIZE; + } + +#if CONFIG_BT_BLUEDROID_ENABLED + err = bt_le_bluedroid_l2cap_chan_send(chan, buf); +#else + err = bt_le_nimble_l2cap_chan_send(chan, buf); +#endif + + return err; +} + +_IDF_ONLY +int bt_l2cap_server_register(struct bt_l2cap_server *server) +{ + LOG_DBG("L2capSrvReg"); + + if (server == NULL) { + LOG_ERR("L2capSrvNull"); + return -EINVAL; + } + + if (server->accept == NULL) { + LOG_ERR("L2capSrvAcceptNull"); + return -EINVAL; + } + + if (server->sec_level > BT_SECURITY_L4) { + LOG_ERR("L2capInvSecLevel[%u]", server->sec_level); + return -EINVAL; + } + + /* Init path (no concurrency). l2cap_servers is shared with the accept + * lookup in the event handler (iso_task). */ + if (server->psm) { + if (server->psm < L2CAP_LE_PSM_FIXED_START || + server->psm > L2CAP_LE_PSM_DYN_END) { + LOG_ERR("L2capInvPsm[%04x]", server->psm); + return -EINVAL; + } + + /* Check if given PSM is already in use */ + if (l2cap_server_lookup_psm(server->psm)) { + LOG_WRN("L2capPsmReg"); + return -EADDRINUSE; + } + + LOG_DBG("L2capSrvPsm[%04x]", server->psm); + } else { + uint16_t psm; + + for (psm = L2CAP_LE_PSM_DYN_START; + psm <= L2CAP_LE_PSM_DYN_END; psm++) { + if (l2cap_server_lookup_psm(psm) == NULL) { + break; + } + } + + if (psm > L2CAP_LE_PSM_DYN_END) { + LOG_ERR("L2capNoFreeDynPsm"); + return -EADDRNOTAVAIL; + } + + LOG_DBG("L2capPsmNew[%04x]", psm); + + server->psm = psm; + } + + if (server->sec_level < BT_SECURITY_L1) { + server->sec_level = BT_SECURITY_L1; + } + + sys_slist_append(&l2cap_servers, &server->node); + + return 0; +} + +int bt_le_l2cap_ots_init(void) +{ + int err; + + LOG_DBG("L2capOtsInit"); + + /* No unregister API, so a re-init would otherwise find the stale entry and + * fail bt_l2cap_server_register() with -EADDRINUSE. */ + sys_slist_init(&l2cap_servers); + +#if CONFIG_BT_OTS + err = bt_gatt_ots_conn_cb_register(); + if (err) { + LOG_ERR("L2capOtsConnCbRegFail[%d]", err); + return err; + } + + err = bt_gatt_ots_instances_prepare(); + if (err) { + LOG_ERR("L2capPrepOtsInstsFail[%d]", err); + goto unreg_conn_cb; + } +#endif /* CONFIG_BT_OTS */ + +#if CONFIG_BT_OTS_CLIENT + err = bt_gatt_ots_client_conn_cb_register(); + if (err) { + LOG_ERR("L2capOtsCliConnCbRegFail[%d]", err); + goto unreg_conn_cb; + } +#endif /* CONFIG_BT_OTS_CLIENT */ + + err = bt_gatt_ots_l2cap_init(); + if (err) { + LOG_ERR("L2capOtsInitFail[%d]", err); + goto unreg_client_conn_cb; + } + +#if CONFIG_BT_BLUEDROID_ENABLED + err = bt_le_bluedroid_l2cap_init(); +#else + err = bt_le_nimble_l2cap_init(); +#endif + if (err) { + LOG_ERR("L2capOtsAdapterInitFail[%d]", err); + /* bt_l2cap_server_register() has no unregister, so drop the whole list + * rather than leave the OTS PSM claimed by a half-initialised service. */ + sys_slist_init(&l2cap_servers); + goto unreg_client_conn_cb; + } + + return 0; + +unreg_client_conn_cb: +#if CONFIG_BT_OTS_CLIENT + bt_gatt_ots_client_conn_cb_unregister(); +#endif /* CONFIG_BT_OTS_CLIENT */ +unreg_conn_cb: +#if CONFIG_BT_OTS + /* Leaving it registered would fail every later retry with -EEXIST. */ + bt_gatt_ots_conn_cb_unregister(); +#endif /* CONFIG_BT_OTS */ + return err; +} + +void bt_le_l2cap_ots_deinit(void) +{ + LOG_DBG("L2capOtsDeinit"); + + /* TODO(deinit): unassign OTS obj managers / clear instance->obj_manager. */ + + /* Symmetric with the registers in _init: conn_cbs is never reset elsewhere. */ +#if CONFIG_BT_OTS + bt_gatt_ots_conn_cb_unregister(); +#endif /* CONFIG_BT_OTS */ +#if CONFIG_BT_OTS_CLIENT + bt_gatt_ots_client_conn_cb_unregister(); +#endif /* CONFIG_BT_OTS_CLIENT */ + +#if CONFIG_BT_BLUEDROID_ENABLED + bt_le_bluedroid_l2cap_deinit(); +#else + bt_le_nimble_l2cap_deinit(); +#endif +} diff --git a/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.h b/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.h new file mode 100644 index 00000000000..ce8bbdf8e88 --- /dev/null +++ b/components/bt/esp_ble_audio/host/services/ots/adapter/l2cap.h @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2015-2016 Intel Corporation + * SPDX-FileCopyrightText: 2023 Nordic Semiconductor + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef OTS_ADAPTER_L2CAP_H_ +#define OTS_ADAPTER_L2CAP_H_ + +#include + +#include "sdkconfig.h" + +#include + +#if CONFIG_BT_BLUEDROID_ENABLED +#include "bluedroid/l2cap.h" +#else +#include "nimble/l2cap.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define L2CAP_LE_SUCCESS 0x0000 +#define L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002 +#define L2CAP_LE_ERR_NO_RESOURCES 0x0004 +#define L2CAP_LE_ERR_AUTHENTICATION 0x0005 +#define L2CAP_LE_ERR_AUTHORIZATION 0x0006 +#define L2CAP_LE_ERR_KEY_SIZE 0x0007 +#define L2CAP_LE_ERR_ENCRYPTION 0x0008 +#define L2CAP_LE_ERR_INVALID_SCID 0x0009 +#define L2CAP_LE_ERR_SCID_IN_USE 0x000A +#define L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B +#define L2CAP_LE_ERR_INVALID_PARAMS 0x000C + +#define L2CAP_LE_OTS_PSM 0x0025 +#define L2CAP_LE_OTS_MTU MIN(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, \ + CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU) +#define L2CAP_OTS_MAX_CHAN CONFIG_BT_MAX_CONN + +enum bt_le_l2cap_event_type { + BT_LE_L2CAP_EVENT_CONNECTED, + BT_LE_L2CAP_EVENT_DISCONNECTED, + BT_LE_L2CAP_EVENT_RECEIVED, + BT_LE_L2CAP_EVENT_SENT, +#if CONFIG_BT_BLUEDROID_ENABLED + /* Bluedroid answers an inbound connect request out-of-band via + * L2CA_ConnectLECocRsp(), so unlike NimBLE it needs no inline verdict. */ + BT_LE_L2CAP_EVENT_ACCEPT, +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ +}; + +struct bt_le_l2cap_event { + uint8_t type; + uint16_t conn_handle; + uint16_t psm; + + union { + struct { + uint16_t tx_cid; + uint16_t tx_mtu; + uint16_t rx_cid; + uint16_t rx_mtu; + } connected; + + struct { + uint8_t *data; /* heap copy, freed after dispatch */ + uint16_t len; + } received; + +#if CONFIG_BT_BLUEDROID_ENABLED + struct { + uint16_t chan_handle; + uint8_t l2cap_id; + } accept; +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ + }; +}; + +#if CONFIG_BT_BLUEDROID_ENABLED +int bt_le_l2cap_post_accept(uint16_t conn_handle, uint16_t psm, + uint16_t chan_handle, uint8_t l2cap_id); +#else /* !CONFIG_BT_BLUEDROID_ENABLED */ +int bt_le_l2cap_accept_safe(uint16_t conn_handle, uint16_t psm, + uint16_t scid, uint16_t mtu, + uint16_t mps, uint16_t credits, + uint16_t *result); +#endif /* !CONFIG_BT_BLUEDROID_ENABLED */ + +int bt_le_l2cap_post_connected(uint16_t conn_handle, uint16_t psm, + uint16_t tx_cid, uint16_t tx_mtu, + uint16_t rx_cid, uint16_t rx_mtu); + +int bt_le_l2cap_post_disconnected(uint16_t conn_handle, uint16_t psm); + +int bt_le_l2cap_post_received(uint16_t conn_handle, uint16_t psm, + const uint8_t *data, uint16_t len); + +/* Neither host reports a per-SDU TX completion for LE CoC, so each adapter + * synthesises it from its write's return code plus its credit-restored event. */ +int bt_le_l2cap_post_sent(uint16_t conn_handle, uint16_t psm); + +void bt_le_l2cap_handle_event(void *data, size_t data_len); + +int bt_le_l2cap_ots_init(void); + +void bt_le_l2cap_ots_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OTS_ADAPTER_L2CAP_H_ */ diff --git a/components/bt/esp_ble_audio/host/services/ots/adapter/nimble/l2cap.c b/components/bt/esp_ble_audio/host/services/ots/adapter/nimble/l2cap.c new file mode 100644 index 00000000000..46ce21c90cd --- /dev/null +++ b/components/bt/esp_ble_audio/host/services/ots/adapter/nimble/l2cap.c @@ -0,0 +1,506 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include <../host/conn_internal.h> + +#include "host/ble_hs.h" +#include "host/ble_l2cap.h" +#include "host/ble_hs_mbuf.h" +#include "host/ble_gap.h" + +#include "../../../nimble/host/src/ble_l2cap_priv.h" + +#include "common/host.h" +#include "common/audio_attr.h" +#include "nimble/hs_error.h" + +#include "ots/adapter/l2cap.h" + +LOG_MODULE_REGISTER(ISO_N2CAP, CONFIG_BT_ISO_LOG_LEVEL); + +_Static_assert(CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM && "At least one L2CAP coc shall be supported"); + +#define OTS_L2CAP_BUF_COUNT (3 * CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM) +#define OTS_L2CAP_MEM_SIZE OS_MEMPOOL_SIZE(OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2) + +/* Staging only - both directions copy through msys mbufs, so nothing here is + * DMA-reached and PSRAM is fine. */ +static BT_AUDIO_EXT_RAM_BSS_ATTR os_membuf_t ots_mem[OTS_L2CAP_MEM_SIZE]; +static BT_AUDIO_EXT_RAM_BSS_ATTR struct os_mempool ots_mbuf_mempool; +static BT_AUDIO_EXT_RAM_BSS_ATTR struct os_mbuf_pool ots_mbuf_pool; + +/* One slot per connection (OTS is single-PSM); pending bridges the async gap + * between ble_l2cap_connect and COC_CONNECTED. + * + * Read without host_lock on both sides: the event callback must not take it + * (NimBLE emits COC events under ble_hs_lock, so that would invert the ISO + * path's host_lock -> ble_hs_lock order). psm is cached so a stale slot never + * has to dereference chan; see chan_send for the residual window. */ +struct ots_chan_slot { + struct ble_l2cap_chan *chan; + uint16_t conn_handle; + uint16_t psm; + bool used; + bool pending; +}; + +static BT_AUDIO_EXT_RAM_BSS_ATTR struct ots_chan_slot ots_chans[L2CAP_OTS_MAX_CHAN]; + +static struct ots_chan_slot *ots_chan_find_used_by_chan(struct ble_l2cap_chan *chan) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].used && ots_chans[i].chan == chan) { + return &ots_chans[i]; + } + } + return NULL; +} + +static struct ots_chan_slot *ots_chan_find_used_by_conn(uint16_t conn_handle) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].used && ots_chans[i].conn_handle == conn_handle) { + return &ots_chans[i]; + } + } + return NULL; +} + +static struct ots_chan_slot *ots_chan_find_pending_by_conn(uint16_t conn_handle) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (ots_chans[i].pending && ots_chans[i].conn_handle == conn_handle) { + return &ots_chans[i]; + } + } + return NULL; +} + +static struct ots_chan_slot *ots_chan_alloc_pending(uint16_t conn_handle) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (!ots_chans[i].used && !ots_chans[i].pending) { + memset(&ots_chans[i], 0, sizeof(ots_chans[i])); + ots_chans[i].conn_handle = conn_handle; + ots_chans[i].pending = true; + return &ots_chans[i]; + } + } + return NULL; +} + +static struct ots_chan_slot *ots_chan_alloc_used(struct ble_l2cap_chan *chan, uint16_t conn_handle) +{ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if (!ots_chans[i].used && !ots_chans[i].pending) { + memset(&ots_chans[i], 0, sizeof(ots_chans[i])); + ots_chans[i].chan = chan; + ots_chans[i].conn_handle = conn_handle; + ots_chans[i].psm = chan->psm; + ots_chans[i].used = true; + return &ots_chans[i]; + } + } + return NULL; +} + +static void ots_chan_free(struct ots_chan_slot *slot) +{ + if (slot != NULL) { + memset(slot, 0, sizeof(*slot)); + } +} + +static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan) +{ + struct os_mbuf *sdu_rx; + int rc; + + LOG_DBG("[N]L2capOtsRecvReady"); + + sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); + if (sdu_rx == NULL) { + LOG_ERR("[N]L2capNoBufForL2capRecv"); + return -ENOMEM; + } + + rc = ble_l2cap_recv_ready(chan, sdu_rx); + if (rc) { + LOG_ERR("[N]L2capRecvFail[%d]", rc); + + os_mbuf_free_chain(sdu_rx); + return rc; + } + + return 0; +} + +static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg) +{ + struct ble_l2cap_chan_info chan_info; + uint16_t result = 0; + size_t sdu_len; + uint16_t psm; + uint8_t *sdu; + int err; + + LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type); + + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: { + struct ots_chan_slot *slot; + + if (event->connect.status) { + LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status); + ots_chan_free(ots_chan_find_pending_by_conn(event->connect.conn_handle)); + /* Unwind the optimistic chan_add done by bt_l2cap_chan_connect()/ + * l2cap_accept(), else the upper layer waits for a result forever. */ + bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM); + return 0; + } + + if (ots_chan_find_used_by_chan(event->connect.chan) != NULL) { + LOG_ERR("[N]L2capCocChanExist"); + return 0; + } + + /* Outgoing connect: reuse the pending slot reserved at chan_connect. + * Incoming accept: no pending slot, allocate a fresh one. */ + slot = ots_chan_find_pending_by_conn(event->connect.conn_handle); + if (slot != NULL) { + slot->pending = false; + slot->chan = event->connect.chan; + slot->psm = event->connect.chan->psm; + slot->used = true; + } else { + slot = ots_chan_alloc_used(event->connect.chan, event->connect.conn_handle); + if (slot == NULL) { + LOG_ERR("[N]L2capCocNoSlot"); + bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM); + return -EIO; + } + } + + if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) { + LOG_ERR("[N]L2capCocGetChanInfoFail"); + /* Roll back the slot so the next COC_CONNECTED isn't refused. */ + ots_chan_free(ots_chan_find_used_by_chan(event->connect.chan)); + bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM); + return -EIO; + } + + LOG_INF("[N]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]", + event->connect.conn_handle, chan_info.scid, chan_info.dcid, + chan_info.psm, chan_info.our_l2cap_mtu, chan_info.peer_l2cap_mtu, + chan_info.our_coc_mtu, chan_info.peer_coc_mtu); + + bt_le_l2cap_post_connected(event->connect.conn_handle, chan_info.psm, + chan_info.dcid, chan_info.peer_coc_mtu, + chan_info.scid, chan_info.our_coc_mtu); + return 0; + } + + case BLE_L2CAP_EVENT_COC_DISCONNECTED: { + struct ots_chan_slot *slot; + + slot = ots_chan_find_used_by_chan(event->disconnect.chan); + if (slot == NULL) { + LOG_ERR("[N]L2capDisconnectInvCocChan"); + return 0; + } + + psm = slot->psm; + /* Drop the slot before posting: NimBLE frees the chan once this returns, + * and the queue pairing then makes iso_task not find a dead chan. */ + ots_chan_free(slot); + + LOG_INF("[N]L2capCocDisconnect[%u][%04x]", event->disconnect.conn_handle, psm); + + bt_le_l2cap_post_disconnected(event->disconnect.conn_handle, psm); + return 0; + } + + case BLE_L2CAP_EVENT_COC_ACCEPT: + /* LE CoC allows asymmetric MTUs; peer_sdu_size is the peer's RX MTU + * (our TX ceiling) — harmless. (PTS uses 1024 > our 256; rejecting broke SCP.) */ + + LOG_DBG("[N]L2capCocAccept[%u][%04x][%04x][%u][%u][%u][%u]", + event->accept.conn_handle, event->accept.chan->psm, + event->accept.chan->dcid, event->accept.chan->coc_tx.mtu, + event->accept.chan->peer_coc_mps, event->accept.peer_sdu_size, + event->accept.chan->coc_tx.credits); + + err = bt_le_l2cap_accept_safe(event->accept.conn_handle, + event->accept.chan->psm, + event->accept.chan->dcid, + event->accept.chan->coc_tx.mtu, + event->accept.chan->peer_coc_mps, + event->accept.chan->coc_tx.credits, + &result); + if (err) { + return err; + } + + ARG_UNUSED(result); + + return ots_l2cap_recv_ready(event->accept.chan); + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: { + if (event->receive.sdu_rx == NULL) { + LOG_ERR("[N]L2capRecvNullSdu"); + return 0; + } + + LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]", + event->receive.conn_handle, event->receive.chan->psm, + event->receive.sdu_rx->om_len); + + sdu_len = OS_MBUF_PKTLEN(event->receive.sdu_rx); + + sdu = bt_le_ext_calloc(1, sdu_len); + if (sdu == NULL) { + LOG_ERR("[N]L2capRecvNoMem[%u]", (unsigned)sdu_len); + /* Drop the SDU but keep the channel usable: hand a fresh RX buffer + * back so the peer can retry rather than stalling on credits. */ + os_mbuf_free_chain(event->receive.sdu_rx); + return ots_l2cap_recv_ready(event->receive.chan); + } + + err = os_mbuf_copydata(event->receive.sdu_rx, 0, sdu_len, sdu); + if (err) { + LOG_ERR("[N]L2capRecvCopyFail[%d]", err); + } else { + bt_le_l2cap_post_received(event->receive.conn_handle, + event->receive.chan->psm, sdu, sdu_len); + } + + os_mbuf_free_chain(event->receive.sdu_rx); + free(sdu); + + return ots_l2cap_recv_ready(event->receive.chan); + } + + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: { + LOG_DBG("[N]L2capCocTxUnstalled[%u][%d]", + event->tx_unstalled.conn_handle, event->tx_unstalled.status); + + /* Report completion even on failure: ops->sent carries no status, and + * silence would leave the upper layer's TX pending forever. */ + if (event->tx_unstalled.status) { + LOG_ERR("[N]L2capCocTxFail[%d]", event->tx_unstalled.status); + } + + bt_le_l2cap_post_sent(event->tx_unstalled.conn_handle, + event->tx_unstalled.chan->psm); + return 0; + } + + default: + return 0; + } +} + +int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle) +{ + struct os_mbuf *sdu_rx; + struct ots_chan_slot *slot; + int rc; + + /* Reject if this connection already has an OTS channel. ots_chans is a + * cache under ble_hs_lock, so no host_lock here. */ + for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) { + if ((ots_chans[i].used || ots_chans[i].pending) && + ots_chans[i].conn_handle == conn_handle) { + LOG_WRN("[N]L2capOtsChanExist[%u]", conn_handle); + return -EALREADY; + } + } + slot = ots_chan_alloc_pending(conn_handle); + if (slot == NULL) { + LOG_ERR("[N]L2capOtsNoSlot[%u]", conn_handle); + return -ENOMEM; + } + + sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); + if (sdu_rx == NULL) { + ots_chan_free(slot); + LOG_ERR("[N]L2capNoBufForConnect"); + return -ENOMEM; + } + + rc = ble_l2cap_connect(conn_handle, L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, + sdu_rx, ots_l2cap_event_cb, NULL); + if (rc) { + /* NimBLE takes ownership of sdu_rx and frees it on every failure path */ + ots_chan_free(slot); + LOG_ERR("[N]L2capConnectFail[%d]", rc); + return nimble_err_to_errno(rc); + } + + return 0; +} + +int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) +{ + struct ots_chan_slot *slot; + struct ble_l2cap_chan *ble_chan = NULL; + uint16_t conn_handle_h; + struct ble_gap_conn_desc desc; + int rc; + + slot = ots_chan_find_used_by_conn(chan->conn->handle); + if (slot == NULL) { + LOG_WRN("[N]L2capNoOtsChan"); + return -ENOTCONN; + } + ble_chan = slot->chan; + conn_handle_h = slot->conn_handle; + + /* Best-effort liveness check: narrows the window where ble_chan could be + * freed between this lookup and ble_l2cap_disconnect acquiring ble_hs_lock. */ + if (ble_gap_conn_find(conn_handle_h, &desc) != 0) { + LOG_WRN("[N]L2capDiscConnGone[%u]", conn_handle_h); + return -ENOTCONN; + } + + rc = ble_l2cap_disconnect(ble_chan); + if (rc) { + LOG_ERR("[N]L2capDisconnectFail[%d]", rc); + return nimble_err_to_errno(rc); + } + + return 0; +} + +int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct ots_chan_slot *slot; + struct ble_l2cap_chan *ble_chan = NULL; + uint16_t conn_handle_h, psm_h; + struct os_mbuf *sdu_tx; + struct ble_gap_conn_desc desc; + int rc; + + slot = ots_chan_find_used_by_conn(chan->conn->handle); + if (slot == NULL) { + LOG_WRN("[N]L2capNoOtsChan"); + return -ENOTCONN; + } + ble_chan = slot->chan; + conn_handle_h = slot->conn_handle; + psm_h = slot->psm; + + /* Best-effort liveness check: ble_gap_conn_find takes ble_hs_lock, so the + * conn can't be torn down while it runs. It narrows but does not close the + * window before ble_l2cap_send — that reads chan->coc_tx.mtu before taking + * ble_hs_lock, so a concurrently freed chan is still dereferenced there. */ + if (ble_gap_conn_find(conn_handle_h, &desc) != 0) { + LOG_WRN("[N]L2capSendConnGone[%u]", conn_handle_h); + return -ENOTCONN; + } + + sdu_tx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); + if (sdu_tx == NULL) { + LOG_ERR("[N]L2capNoBufForSend"); + return -ENOMEM; + } + + rc = os_mbuf_append(sdu_tx, buf->data, buf->len); + if (rc) { + LOG_ERR("[N]L2capAppendBufFail[%d]", rc); + os_mbuf_free_chain(sdu_tx); + return -EIO; + } + + rc = ble_l2cap_send(ble_chan, sdu_tx); + if (rc && rc != BLE_HS_ESTALLED) { + if (rc == BLE_HS_EBADDATA || rc == BLE_HS_EBUSY) { + /* sdu was rejected before being queued; caller still owns it. */ + LOG_ERR("[N]L2capSendFail[%d]", rc); + os_mbuf_free_chain(sdu_tx); + } else { + /* NimBLE only consumes the mbuf on success; free it on any failure. */ + LOG_ERR("[N]L2capSendInternalFail[%d]", rc); + os_mbuf_free_chain(sdu_tx); + } + + return nimble_err_to_errno(rc); + } + + /* Payload now in sdu_tx; release caller's buf else ot_chan_tx_pool + * (1 entry) leaks. */ + net_buf_unref(buf); + + if (rc == BLE_HS_ESTALLED) { + /* Out of credits: queued, reported by COC_TX_UNSTALLED. If the conn + * drops while stalled, NimBLE fires only COC_DISCONNECTED; the OTS + * disconnected callback cleans up tx state, so the missing sent is + * harmless. */ + LOG_DBG("[N]L2capMoreCreditsForSend"); + return 0; + } + + /* Fully transmitted; NimBLE raises COC_TX_UNSTALLED only for a stalled SDU, + * so this is the sole completion signal. */ + bt_le_l2cap_post_sent(conn_handle_h, psm_h); + + return 0; +} + +int bt_le_nimble_l2cap_init(void) +{ + int rc; + + memset(ots_chans, 0, sizeof(ots_chans)); + + rc = os_mempool_init(&ots_mbuf_mempool, OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2, ots_mem, "ots_pool"); + if (rc) { + LOG_ERR("[N]L2capInitOtsMempoolFail[%d]", rc); + return rc; + } + + rc = os_mbuf_pool_init(&ots_mbuf_pool, &ots_mbuf_mempool, L2CAP_LE_OTS_MTU, OTS_L2CAP_BUF_COUNT); + if (rc) { + LOG_ERR("[N]L2capInitOtsMbufPoolFail[%d]", rc); + return rc; + } + +#if CONFIG_BT_OTS + rc = ble_l2cap_create_server(L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, ots_l2cap_event_cb, NULL); + /* NimBLE has no delete-server API, so the registration from a previous cycle + * survives deinit. It still points at ots_l2cap_event_cb, so treat EALREADY + * as success - failing here would make any re-init impossible. */ + if (rc == BLE_HS_EALREADY) { + LOG_WRN("[N]L2capL2capSrvExist"); + } else if (rc) { + LOG_ERR("[N]L2capCreateL2capSrvFail[%d]", rc); + return rc; + } +#endif /* CONFIG_BT_OTS */ + + return 0; +} + +void bt_le_nimble_l2cap_deinit(void) +{ + LOG_DBG("[N]L2capDeinit"); + + /* Nothing to release: ots_mem is static BSS and os_mempool_init re-registers + * an existing pool by name, so re-init needs no teardown here. The CoC server + * cannot be dropped either - NimBLE exposes no delete-server API - which is + * why init tolerates BLE_HS_EALREADY. */ + + memset(ots_chans, 0, sizeof(ots_chans)); +} diff --git a/components/bt/esp_ble_audio/host/services/ots/ots.c b/components/bt/esp_ble_audio/host/services/ots/ots.c index 05563c2de88..1c5c5e1311e 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -310,6 +311,16 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn, struct bt_gatt_ots_object *new_obj; struct bt_ots_obj_created_desc created_desc; + /* Directory listing Object Type is 16- or 128-bit only (same as OACP Create). */ + switch (param->type.uuid.type) { + case BT_UUID_TYPE_16: + case BT_UUID_TYPE_128: + break; + default: + LOG_ERR("OtsObjAddInvType[%u]", param->type.uuid.type); + return -EINVAL; + } + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list && !bt_ots_dir_list_is_idle(ots->dir_list)) { LOG_DBG("OtsDirListBusy"); @@ -330,6 +341,9 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn, if (err) { (void)bt_gatt_ots_obj_manager_obj_delete(new_obj); + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) { + bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager); + } return err; } @@ -359,11 +373,19 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn, LOG_ERR("OtsObjCreatedCbNotSet"); (void)bt_gatt_ots_obj_manager_obj_delete(new_obj); + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) { + bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager); + } return -EINVAL; } new_obj->metadata.type = param->type; - new_obj->metadata.name = created_desc.name; + /* Own the name: a client name write copies into metadata.name, and the + * application's buffer may be read-only or shorter than the max name. */ + strncpy(new_obj->metadata.name_c, created_desc.name, CONFIG_BT_OTS_OBJ_MAX_NAME_LEN); + new_obj->metadata.name_c[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN] = '\0'; + new_obj->metadata.name = new_obj->metadata.name_c; new_obj->metadata.size = created_desc.size; new_obj->metadata.props = created_desc.props; @@ -448,6 +470,11 @@ int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id) return err; } + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) { + /* Object removed from the list: drop stale anchor and refresh size. */ + bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager); + } + if (ots->cur_obj == obj) { ots->cur_obj = NULL; } @@ -464,18 +491,48 @@ void *bt_ots_svc_decl_get(struct bt_ots *ots) static void oacp_indicate_work_handler(struct k_work *work) { - struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work); + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_gatt_ots_indicate *ind = + CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work); struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, oacp_ind); + int err; - bt_gatt_indicate(NULL, &ots->oacp_ind.params); + if (!ind->conn) { + LOG_WRN("OtsOacpIndNoConn"); + ots->oacp_ind.ind_in_flight = false; + return; + } + + LOG_INF("OtsOacpInd[%04x]", ind->conn->handle); + + err = bt_gatt_indicate(ind->conn, &ots->oacp_ind.params); + if (err) { + LOG_ERR("OtsOacpIndFail[%04x][%d]", ind->conn->handle, err); + ots->oacp_ind.ind_in_flight = false; + } } static void olcp_indicate_work_handler(struct k_work *work) { - struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work); + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_gatt_ots_indicate *ind = + CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work); struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, olcp_ind); + int err; - bt_gatt_indicate(NULL, &ots->olcp_ind.params); + if (!ind->conn) { + LOG_WRN("OtsOlcpIndNoConn"); + ots->olcp_ind.ind_in_flight = false; + return; + } + + LOG_INF("OtsOlcpInd[%04x]", ind->conn->handle); + + err = bt_gatt_indicate(ind->conn, &ots->olcp_ind.params); + if (err) { + LOG_ERR("OtsOlcpIndFail[%04x][%d]", ind->conn->handle, err); + ots->olcp_ind.ind_in_flight = false; + } } int bt_ots_init(struct bt_ots *ots, @@ -531,7 +588,11 @@ int bt_ots_init(struct bt_ots *ots, err = bt_gatt_service_register(ots->service); if (err) { - bt_gatt_ots_l2cap_unregister(&ots->l2cap); + int unreg_err = bt_gatt_ots_l2cap_unregister(&ots->l2cap); + + if (unreg_err) { + LOG_ERR("OtsL2capUnregFail[%d]", unreg_err); + } return err; } @@ -540,8 +601,8 @@ int bt_ots_init(struct bt_ots *ots, bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager); } - k_work_init(&ots->oacp_ind.work, oacp_indicate_work_handler); - k_work_init(&ots->olcp_ind.work, olcp_indicate_work_handler); + k_work_init_delayable(&ots->oacp_ind.work, oacp_indicate_work_handler); + k_work_init_delayable(&ots->olcp_ind.work, olcp_indicate_work_handler); LOG_DBG("OtsInit"); @@ -616,6 +677,7 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co char id_str[BT_OTS_OBJ_ID_STR_LEN]; struct bt_gatt_ots_object *obj; struct bt_gatt_ots_object *next_obj; + bool deleted = false; int err; err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &next_obj); @@ -638,9 +700,34 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co if (bt_gatt_ots_obj_manager_obj_delete(obj)) { LOG_ERR("OtsObjMgrDelFail[%s]", id_str); + } else { + deleted = true; } } } + + /* Refresh once after the loop: while empty-name objects are being deleted + * the manager still holds not-yet-removed ones, and dir_list_update_size + * would assert (name_len > 0) on them. + */ + if (deleted && IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) { + bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager); + } +} + +static void ots_ind_on_disconnect(struct bt_gatt_ots_indicate *ind, struct bt_conn *conn) +{ + if (ind->conn != conn) { + LOG_INF("OtsIndSkipDisc[%04x][%04x]", + ind->conn ? ind->conn->handle : 0xFFFF, conn->handle); + return; + } + + LOG_INF("OtsIndClrOnDisc[%04x]", conn->handle); + + (void)k_work_cancel_delayable(&ind->work); + ind->ind_in_flight = false; + ind->conn = NULL; } static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason) @@ -654,6 +741,9 @@ static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason) LOG_DBG("OtsInstDisconnect[%u]", index); + ots_ind_on_disconnect(&instance->oacp_ind, conn); + ots_ind_on_disconnect(&instance->olcp_ind, conn); + if (instance->cur_obj != NULL) { __ASSERT(instance->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE, "The current object is expected to be in idle state as part " @@ -678,7 +768,7 @@ struct bt_ots *bt_ots_free_instance_get(void) return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++]; } -static int bt_gatt_ots_instances_prepare(void) +int bt_gatt_ots_instances_prepare(void) { uint32_t index; struct bt_ots *instance; @@ -709,10 +799,12 @@ static int bt_gatt_ots_instances_prepare(void) return 0; } -SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION, - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); - int bt_gatt_ots_conn_cb_register(void) { return bt_conn_cb_register_safe((void *)&bt_conn_cb_conn_callbacks); } + +void bt_gatt_ots_conn_cb_unregister(void) +{ + (void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_conn_callbacks); +} diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_client.c b/components/bt/esp_ble_audio/host/services/ots/ots_client.c index 1a2d696cc5e..0d7157bf07a 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_client.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_client.c @@ -2,6 +2,7 @@ * @brief Bluetooth Object Transfer Client * * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -131,6 +132,7 @@ static void read_next_metadata(struct bt_conn *conn, static int read_attr(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, uint16_t handle, bt_gatt_read_func_t cb); +static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst); /* L2CAP callbacks */ static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, @@ -177,6 +179,7 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, } const uint32_t offset = cur_inst->rcvd_size; + uint32_t len = buf->len; bool is_complete = false; const struct bt_ots_obj_metadata *cur_object = &cur_inst->otc_inst->cur_object; @@ -184,19 +187,24 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, LOG_DBG("OtsCliL2capRecv[%u][%u]", buf->len, offset); - cur_inst->rcvd_size += buf->len; - - if (cur_inst->rcvd_size >= cur_object->size.cur) { - is_complete = true; + if (offset >= cur_object->size.cur) { + LOG_WRN("OtsCliRecvPastEnd[%u][%u]", offset, cur_object->size.cur); + (void)bt_gatt_ots_l2cap_disconnect(l2cap_ctx); + cur_inst = NULL; + return -EMSGSIZE; } - if (cur_inst->rcvd_size > cur_object->size.cur) { - LOG_WRN("OtsCliRecvExceedMax[%u][%u]", cur_inst->rcvd_size, cur_object->size.cur); + if (offset + len > cur_object->size.cur) { + LOG_WRN("OtsCliRecvExceedMax[%u][%u]", offset + len, cur_object->size.cur); + len = cur_object->size.cur - offset; } + cur_inst->rcvd_size = offset + len; + is_complete = (cur_inst->rcvd_size >= cur_object->size.cur); + if (cur_inst->otc_inst->cb != NULL && cur_inst->otc_inst->cb->obj_data_read != NULL) { cb_ret = cur_inst->otc_inst->cb->obj_data_read(cur_inst->otc_inst, conn, offset, - buf->len, buf->data, + len, buf->data, is_complete); } else { LOG_ERR("OtsCliObjDataRdCbNull"); @@ -234,10 +242,15 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn) { + struct bt_otc_internal_instance_t *inst = + CONTAINER_OF(l2cap_ctx, struct bt_otc_internal_instance_t, l2cap_ctx); + + ARG_UNUSED(conn); + LOG_DBG("OtsCliL2capClosed"); - if (cur_inst) { - cur_inst = NULL; - } + + /* Only release if this channel belonged to the active transfer. */ + oacp_clr_cur_inst(inst); } /* End L2CAP callbacks */ @@ -318,7 +331,9 @@ static void olcp_ind_handler(struct bt_conn *conn, enum bt_gatt_ots_olcp_proc_type op_code; struct net_buf_simple net_buf; - if (length < sizeof(op_code)) { + /* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject + * every valid 3-byte response. */ + if (length < sizeof(uint8_t)) { LOG_WRN("OtsCliInvIndLen[%u]", length); return; } @@ -387,18 +402,29 @@ static void olcp_ind_handler(struct bt_conn *conn, } } +static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst) +{ + if (cur_inst == inst) { + cur_inst = NULL; + } +} + static void oacp_ind_handler(struct bt_conn *conn, - struct bt_ots_client *otc_inst, + struct bt_otc_internal_instance_t *inst, const void *data, uint16_t length) { + struct bt_ots_client *otc_inst = inst->otc_inst; enum bt_gatt_ots_oacp_proc_type op_code; enum bt_gatt_ots_oacp_proc_type req_opcode; enum bt_gatt_ots_oacp_res_code result_code; uint32_t checksum; struct net_buf_simple net_buf; - if (length < sizeof(op_code)) { + /* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject + * every valid 3-byte response. */ + if (length < sizeof(uint8_t)) { LOG_WRN("OtsCliInvIndLen[%u]", length); + oacp_clr_cur_inst(inst); return; } @@ -409,11 +435,12 @@ static void oacp_ind_handler(struct bt_conn *conn, LOG_DBG("OtsCliOacpInd"); if (op_code == BT_GATT_OTS_OACP_PROC_RESP) { - if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) { + if (net_buf.len >= (sizeof(uint8_t) + sizeof(uint8_t))) { req_opcode = net_buf_simple_pull_u8(&net_buf); result_code = net_buf_simple_pull_u8(&net_buf); } else { LOG_WRN("OtsCliInvIndDataLen[%u]", net_buf.len); + oacp_clr_cur_inst(inst); return; } @@ -427,13 +454,22 @@ static void oacp_ind_handler(struct bt_conn *conn, } } else { LOG_WRN("OtsCliInvChecksumLen[%u]", net_buf.len); - return; } + /* Checksum never uses L2CAP; always release cur_inst. */ + oacp_clr_cur_inst(inst); + } else if ((req_opcode == BT_GATT_OTS_OACP_PROC_READ || + req_opcode == BT_GATT_OTS_OACP_PROC_WRITE) && + result_code != BT_GATT_OTS_OACP_RES_SUCCESS) { + /* Read/Write SUCCESS still waits on L2CAP (rx/tx_done/closed). */ + LOG_DBG("OtsCliOacpFailClr[%02x][%02x]", req_opcode, result_code); + oacp_clr_cur_inst(inst); + (void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx); } print_oacp_response(req_opcode, result_code); } else { LOG_WRN("OtsCliInvIndOpcode[%u]", op_code); + oacp_clr_cur_inst(inst); } } @@ -465,7 +501,7 @@ uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn, if (handle == inst->otc_inst->olcp_handle) { olcp_ind_handler(conn, inst->otc_inst, data, length); } else if (handle == inst->otc_inst->oacp_handle) { - oacp_ind_handler(conn, inst->otc_inst, data, length); + oacp_ind_handler(conn, inst, data, length); } } return BT_GATT_ITER_CONTINUE; @@ -511,8 +547,50 @@ static uint8_t read_feature_cb(struct bt_conn *conn, uint8_t err, return BT_GATT_ITER_STOP; } +/* Disconnect fires no subscription callback, so the OLCP/OACP indication that + * releases busy never arrives — an in-flight procedure would strand -EBUSY. */ +static void ots_client_conn_disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(conn); + ARG_UNUSED(reason); + + for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) { + struct bt_otc_internal_instance_t *inst = &otc_insts[i]; + + if (inst->otc_inst == NULL || !inst->busy) { + continue; + } + + LOG_WRN("OtsCliDisconnBusy[%d]", i); + + inst->busy = false; + oacp_clr_cur_inst(inst); + } +} + +BT_CONN_CB_DEFINE(client_conn_callbacks) = { + .disconnected = ots_client_conn_disconnected, +}; + +int bt_gatt_ots_client_conn_cb_register(void) +{ + return bt_conn_cb_register_safe((void *)&bt_conn_cb_client_conn_callbacks); +} + +void bt_gatt_ots_client_conn_cb_unregister(void) +{ + (void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_client_conn_callbacks); +} + int bt_ots_client_register(struct bt_ots_client *otc_inst) { + /* A NULL inst would leave the slot marked free after registering its + * L2CAP node, so the next call re-appends the same node and self-links it. */ + if (otc_inst == NULL) { + LOG_ERR("OtsCliInvInst"); + return -EINVAL; + } + for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) { int err; @@ -534,18 +612,32 @@ int bt_ots_client_register(struct bt_ots_client *otc_inst) return -ENOMEM; } +__attribute__((unused)) int bt_ots_client_unregister(uint8_t index) { - if (index < ARRAY_SIZE(otc_insts)) { - bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx); - memset(&otc_insts[index], 0, sizeof(otc_insts[index])); - } else { + int err; + + if (index >= ARRAY_SIZE(otc_insts)) { return -EINVAL; } + err = bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx); + if (err) { + LOG_WRN("OtsCliL2capUnregFail[%d]", err); + return err; + } + + if (cur_inst == &otc_insts[index]) { + LOG_DBG("OtsCliUnregClrCurInst[%u]", index); + cur_inst = NULL; + } + + memset(&otc_insts[index], 0, sizeof(otc_insts[index])); + return 0; } +__attribute__((unused)) int bt_ots_client_read_feature(struct bt_ots_client *otc_inst, struct bt_conn *conn) { @@ -602,7 +694,15 @@ static void write_olcp_cb(struct bt_conn *conn, uint8_t err, return; } - inst->busy = false; + /* Keep busy until OLCP indication; only release on write failure. + * Notify the app — without an indication, obj_selected would never fire. + */ + if (err) { + LOG_WRN("OtsCliOlcpWrFail[%02x]", err); + inst->busy = false; + on_object_selected(conn, BT_GATT_OTS_OLCP_RES_OPERATION_FAILED, + inst->otc_inst); + } } static int write_olcp(struct bt_otc_internal_instance_t *inst, @@ -633,6 +733,7 @@ static int write_olcp(struct bt_otc_internal_instance_t *inst, return err; } +__attribute__((unused)) int bt_ots_client_select_id(struct bt_ots_client *otc_inst, struct bt_conn *conn, uint64_t obj_id) @@ -679,6 +780,7 @@ int bt_ots_client_select_id(struct bt_ots_client *otc_inst, return -EOPNOTSUPP; } +__attribute__((unused)) int bt_ots_client_select_first(struct bt_ots_client *otc_inst, struct bt_conn *conn) { @@ -713,6 +815,7 @@ int bt_ots_client_select_first(struct bt_ots_client *otc_inst, return -EOPNOTSUPP; } +__attribute__((unused)) int bt_ots_client_select_last(struct bt_ots_client *otc_inst, struct bt_conn *conn) { @@ -748,6 +851,7 @@ int bt_ots_client_select_last(struct bt_ots_client *otc_inst, return -EOPNOTSUPP; } +__attribute__((unused)) int bt_ots_client_select_next(struct bt_ots_client *otc_inst, struct bt_conn *conn) { @@ -782,6 +886,7 @@ int bt_ots_client_select_next(struct bt_ots_client *otc_inst, return -EOPNOTSUPP; } +__attribute__((unused)) int bt_ots_client_select_prev(struct bt_ots_client *otc_inst, struct bt_conn *conn) { @@ -1040,6 +1145,7 @@ static uint8_t read_obj_created_cb(struct bt_conn *conn, uint8_t err, date_time_decode( &net_buf, &inst->otc_inst->cur_object.first_created); + BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read); } else { LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; @@ -1079,6 +1185,7 @@ static uint8_t read_obj_modified_cb(struct bt_conn *conn, uint8_t err, if (length == BT_OTS_DATE_TIME_FIELD_SIZE) { date_time_decode(&net_buf, &inst->otc_inst->cur_object.modified); + BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read); } else { LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; @@ -1102,8 +1209,9 @@ static int read_attr(struct bt_conn *conn, uint16_t handle, bt_gatt_read_func_t cb) { if (!handle) { + /* Characteristic not discovered — skip without poisoning metadata_err. */ LOG_DBG("OtsCliHdlNotSet"); - return -EINVAL; + return -ENOENT; } else if (cb == NULL) { LOG_ERR("OtsCliCbNull"); return -EINVAL; @@ -1138,22 +1246,24 @@ static uint8_t read_obj_properties_cb(struct bt_conn *conn, uint8_t err, if (err) { LOG_WRN("OtsCliMetaRdErr[%02x]", err); - } else if (data && length == OTS_PROPERTIES_LEN) { - struct bt_ots_obj_metadata *cur_object = - &inst->otc_inst->cur_object; + } else if (data) { + if (length != OTS_PROPERTIES_LEN) { + LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } else { + struct bt_ots_obj_metadata *cur_object = + &inst->otc_inst->cur_object; - cur_object->props = net_buf_simple_pull_le32(&net_buf); + cur_object->props = net_buf_simple_pull_le32(&net_buf); - LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props); + LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props); - if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) { - LOG_WRN("OtsCliObjRdNotSupp"); + if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) { + LOG_WRN("OtsCliObjRdNotSupp"); + } + + BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read); } - - BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read); - } else { - LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN); - cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } if (cb_err) { @@ -1181,7 +1291,14 @@ static void write_oacp_cp_cb(struct bt_conn *conn, uint8_t err, return; } - inst->busy = false; + /* Keep busy until OACP indication; only release on write failure. */ + if (err) { + LOG_WRN("OtsCliOacpWrFail[%02x]", err); + inst->busy = false; + oacp_clr_cur_inst(inst); + /* oacp_read may already have connected L2CAP before the GATT write. */ + (void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx); + } } static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err, @@ -1201,6 +1318,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err, LOG_WRN("OtsCliOacpWrReqFail[%02x]", err); inst->busy = false; cur_inst = NULL; + (void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx); return; } @@ -1210,6 +1328,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err, if (err) { LOG_WRN("OtsCliL2capSendErr[%d]", err); cur_inst = NULL; + (void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx); } inst->busy = false; @@ -1236,6 +1355,7 @@ static int oacp_read(struct bt_conn *conn, * transfer? */ + l2cap = &inst->l2cap_ctx; err = bt_gatt_ots_l2cap_connect(conn, &l2cap); if (err) { LOG_WRN("OtsCliL2capConnectFail[%d]", err); @@ -1269,6 +1389,8 @@ static int oacp_read(struct bt_conn *conn, if (!err) { inst->busy = true; cur_inst = inst; + } else { + (void)bt_gatt_ots_l2cap_disconnect(l2cap); } inst->rcvd_size = 0; @@ -1292,6 +1414,7 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i return -EBUSY; } + l2cap = &inst->l2cap_ctx; err = bt_gatt_ots_l2cap_connect(conn, &l2cap); if (err) { LOG_WRN("OtsCliL2capConnectFail[%d]", err); @@ -1328,6 +1451,8 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i if (!err) { inst->busy = true; cur_inst = inst; + } else { + (void)bt_gatt_ots_l2cap_disconnect(l2cap); } inst->rcvd_size = 0; @@ -1405,6 +1530,7 @@ int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst, return oacp_read(conn, inst); } +__attribute__((unused)) int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, struct bt_conn *conn, const void *buf, size_t len, off_t offset, enum bt_ots_oacp_write_op_mode mode) @@ -1472,6 +1598,7 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode); } +__attribute__((unused)) int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn, off_t offset, size_t len) { @@ -1569,6 +1696,10 @@ static void read_next_metadata(struct bt_conn *conn, if (err) { LOG_INF("OtsCliMetaRdFailTryNext[%d]", err); + /* -ENOENT: handle not discovered, skip. Other errors: keep first. */ + if (err != -ENOENT && !inst->metadata_err) { + inst->metadata_err = err; + } read_next_metadata(conn, inst); } } @@ -1756,6 +1887,7 @@ static int decode_record(struct net_buf_simple *buf, return rec->len; } +__attribute__((unused)) int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length, bt_ots_client_dirlisting_cb cb) { @@ -1772,6 +1904,9 @@ int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length, while (net_buf.len) { int ret; + /* Optional fields written only when flagged; clear between records. */ + memset(&record, 0, sizeof(record)); + count++; if (net_buf.len < sizeof(uint16_t)) { diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_client_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_client_internal.h index 8109f407cf9..6bd4473d200 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_client_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_client_internal.h @@ -4,6 +4,7 @@ * For use with the Object Transfer Service Client (OTC) * * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_dir_list.c b/components/bt/esp_ble_audio/host/services/ots/ots_dir_list.c index 7e38b3d29a6..fa165d984d0 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_dir_list.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_dir_list.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2021 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -263,6 +264,12 @@ static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_man dir_list->dir_list_obj->metadata.size.cur = len; } +void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager) +{ + bt_ots_dir_list_reset_anchor(dir_list, obj_manager); + dir_list_update_size(dir_list, obj_manager); +} + void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager, struct bt_gatt_ots_object *cur_obj) { @@ -273,8 +280,7 @@ void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manage return; } - bt_ots_dir_list_reset_anchor(dir_list, obj_manager); - dir_list_update_size(dir_list, obj_manager); + bt_ots_dir_list_content_changed(dir_list, obj_manager); } void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager) diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_dir_list_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_dir_list_internal.h index ea30cc4bf27..9615438a2e3 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_dir_list_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_dir_list_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2021 - 2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -158,6 +159,7 @@ enum { void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager, struct bt_gatt_ots_object *cur_obj); +void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager); void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager); ssize_t bt_ots_dir_list_content_get(struct bt_ots_dir_list *dir_list, void *obj_manager, void **data, size_t len, off_t offset); diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_internal.h index a692d4c3715..0810b495a1c 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +13,7 @@ extern "C" { #endif #include +#include <../host/conn_internal.h> #include "ots_l2cap_internal.h" #include "ots_oacp_internal.h" #include "ots_olcp_internal.h" @@ -122,7 +124,11 @@ struct bt_gatt_ots_indicate { struct bt_gatt_attr attr; struct bt_gatt_ccc_managed_user_data ccc; bool is_enabled; - struct k_work work; + /* True from schedule until indication confirm (or indicate submit fail). */ + bool ind_in_flight; + /* Peer that wrote the control point; indication must target this conn only. */ + struct bt_conn *conn; + struct k_work_delayable work; uint8_t res[OACP_OLCP_RES_MAX_SIZE]; }; diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c index da6d0be721b..49179b43ce5 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -46,8 +47,9 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx) len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU); len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent); - /* Prepare buffer for sending. */ - buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER); + /* Single-buffer pool, and iso_task is both the only allocator and the only + * path that releases it, so waiting here would self-deadlock. */ + buf = net_buf_alloc(&ot_chan_tx_pool, K_NO_WAIT); if (buf == NULL) { LOG_ERR("OtsL2capTxBufAllocFail"); return -ENOMEM; @@ -77,7 +79,7 @@ static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan) { LOG_DBG("OtsL2capAllocBuf"); - return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER); + return net_buf_alloc(&ot_chan_rx_pool, K_NO_WAIT); } #endif @@ -93,12 +95,11 @@ static void l2cap_sent(struct bt_l2cap_chan *chan) /* Ongoing TX - sending next chunk. */ if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) { if (ots_l2cap_send(l2cap_ctx)) { - /* Send failed - clean up TX state to unblock channel. */ + /* Do not call tx_done: it means success to upper layers + * (oacp_read would skip unread bytes; write_obj_tx_done + * would report a full write). Abort via disconnect → closed. */ LOG_WRN("OtsL2capTxAbort"); - memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx)); - if (l2cap_ctx->tx_done) { - l2cap_ctx->tx_done(l2cap_ctx, chan->conn); - } + (void)bt_l2cap_chan_disconnect(chan); } return; @@ -155,6 +156,12 @@ static void l2cap_disconnected(struct bt_l2cap_chan *chan) if (l2cap_ctx->closed) { l2cap_ctx->closed(l2cap_ctx, chan->conn); } + + /* Contexts are reused via find_free_l2cap_ctx; drop procedure hooks + * so a later accept/connect cannot invoke stale callbacks. */ + l2cap_ctx->closed = NULL; + l2cap_ctx->rx_done = NULL; + l2cap_ctx->tx_done = NULL; } static const struct bt_l2cap_chan_ops l2cap_ops = { @@ -216,7 +223,7 @@ static struct bt_l2cap_server l2cap_server = { .accept = l2cap_accept, }; -static int bt_gatt_ots_l2cap_init(void) +int bt_gatt_ots_l2cap_init(void) { int err; @@ -268,6 +275,10 @@ int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx, int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx) { + if (sys_slist_find(&channels, &l2cap_ctx->node, NULL)) { + return -EALREADY; + } + sys_slist_append(&channels, &l2cap_ctx->node); return 0; @@ -275,6 +286,11 @@ int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx) int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx) { + if (l2cap_ctx->ot_chan.chan.conn) { + LOG_WRN("OtsL2capUnregBusy"); + return -EBUSY; + } + sys_slist_find_and_remove(&channels, &l2cap_ctx->node); return 0; @@ -297,11 +313,21 @@ int bt_gatt_ots_l2cap_connect(struct bt_conn *conn, return -EINVAL; } + /* Callers owning a context pass it in, so the context they later use in + * their callbacks is the one actually connected. The global free-list + * lookup would otherwise hand out another instance's (or the server's) + * context whenever more than one is registered. */ + ctx = *l2cap_ctx; *l2cap_ctx = NULL; - ctx = find_free_l2cap_ctx(); if (!ctx) { - return -ENOMEM; + ctx = find_free_l2cap_ctx(); + if (!ctx) { + return -ENOMEM; + } + } else if (ctx->ot_chan.chan.conn) { + LOG_WRN("OtsL2capCtxAlreadyConnected"); + return -EBUSY; } l2cap_chan_init(&ctx->ot_chan); @@ -323,6 +349,3 @@ int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx) { return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan); } - -SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION, - CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap_internal.h index 765c0f88530..24ab91d6d50 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,11 +50,12 @@ int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx); /** @brief Connect OTS L2CAP channel * * This function is for the OTS client to make an L2CAP connection to - * the OTS server. One of the available registered L2CAP contexts - * will be used for the connection. + * the OTS server. * - * @param[in] conn Connection pointer - * @param[out] l2cap_ctx The context that was connected + * @param[in] conn Connection pointer + * @param[in,out] l2cap_ctx On entry, the caller's context to connect, or NULL + * to pick any free registered one. On success, set + * to the connected context; NULL on failure. * * @return 0 in case of success or negative value in case of error */ diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_oacp.c b/components/bt/esp_ble_audio/host/services/ots/ots_oacp.c index 4d4e00a6aba..f56d44d83c6 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_oacp.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_oacp.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -37,13 +38,16 @@ static void oacp_l2cap_closed(struct bt_gatt_ots_l2cap *l2cap_ctx, ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); + /* Always drop procedure sinks — cur_obj may already be cleared (e.g. + * deleted while idle after a failed send that left callbacks set). */ + l2cap_ctx->rx_done = NULL; + l2cap_ctx->tx_done = NULL; + if (!ots->cur_obj) { return; } ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; - l2cap_ctx->rx_done = NULL; - l2cap_ctx->tx_done = NULL; } #if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) @@ -68,6 +72,12 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate( return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; } + if (ots->cur_obj && + ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) { + LOG_DBG("OtsOacpCreateObjLocked"); + return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; + } + err = bt_ots_obj_add_internal(ots, conn, ¶m, &obj); if (err) { goto exit; @@ -216,6 +226,11 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate( LOG_DBG("OtsOacpValRd[%08x][%08x]", params->offset, params->len); + if (!BT_OTS_OACP_GET_FEAT_READ(ots->features.oacp)) { + LOG_DBG("OtsOacpRdNotSupp"); + return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; + } + if (!ots->cur_obj) { return BT_GATT_OTS_OACP_RES_INV_OBJ; } @@ -297,8 +312,9 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate( return BT_GATT_OTS_OACP_RES_INV_PARAM; } - /* append is not supported */ - if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.cur) { + /* Growing the object is allowed (see the size.cur update in + * oacp_write_proc_cb), but never past what the application allocated. */ + if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) { return BT_GATT_OTS_OACP_RES_INV_PARAM; } @@ -511,11 +527,31 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, return; } + /* Early EOF while bytes remain: do not L2CAP-send len 0 (tx_done would + * re-enter with sent_len unchanged → busy loop). + */ + if (len == 0) { + LOG_WRN("OtsOacpRdEofEarly[%u][%u]", read_op->sent_len, + read_op->oacp_params.len); + + bt_gatt_ots_l2cap_disconnect(&ots->l2cap); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) { + return; + } + + ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0, offset); + return; + } + ots->l2cap.tx_done = oacp_read_proc_cb; ots->l2cap.closed = oacp_l2cap_closed; err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len); if (err) { LOG_WRN("OtsOacpL2capErr[%d]", err); + bt_gatt_ots_l2cap_disconnect(&ots->l2cap); ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; } else { read_op->sent_len += len; @@ -557,9 +593,17 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, return -ENODEV; } + /* Reading state.write_op while another procedure owns the union would + * confuse recv_len with the read op's fields and underflow len below. */ + if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_WRITE_OP_STATE) { + LOG_ERR("OtsOacpWrInvState[%d]", ots->cur_obj->state.type); + return -EINVAL; + } + if (!ots->cb->obj_write) { LOG_ERR("OtsOacpWrNoCb"); ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + l2cap_ctx->rx_done = NULL; return -ENODEV; } @@ -608,6 +652,13 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, ots->cur_obj->metadata.size.cur = offset + len; } + /* Back to idle means this write is over (completed or failed above). The + * CoC stays open, so drop the sink or a later procedure's inbound data + * would still land here. */ + if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE) { + l2cap_ctx->rx_done = NULL; + } + return rc; } #endif @@ -620,11 +671,28 @@ static void oacp_ind_cb(struct bt_conn *conn, LOG_DBG("OtsOacpRecvIndAck[%04x]", err); + ots->oacp_ind.ind_in_flight = false; + ots->oacp_ind.conn = NULL; + if (!ots->cur_obj) { LOG_DBG("OtsOacpNoObjForAck"); return; } + if (err) { + /* Client did not ACK the OACP response — do not start L2CAP I/O. + * Leave READ/WRITE would permanently OBJ_LOCKED. */ + LOG_WRN("OtsOacpIndFail[%02x][%d]", err, ots->cur_obj->state.type); + if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_READ_OP_STATE || + ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_WRITE_OP_STATE) { + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + ots->l2cap.rx_done = NULL; + ots->l2cap.tx_done = NULL; + (void)bt_gatt_ots_l2cap_disconnect(&ots->l2cap); + } + return; + } + switch (ots->cur_obj->state.type) { case BT_GATT_OTS_OBJECT_READ_OP_STATE: oacp_read_proc_execute(ots, conn); @@ -641,7 +709,8 @@ static void oacp_ind_cb(struct bt_conn *conn, } } -static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr, +static void oacp_ind_send(struct bt_conn *conn, + const struct bt_gatt_attr *oacp_attr, struct bt_gatt_ots_oacp_proc oacp_proc, enum bt_gatt_ots_oacp_res_code oacp_status, struct net_buf_simple *resp_param) @@ -649,6 +718,7 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr, struct bt_ots *ots = (struct bt_ots *) oacp_attr->user_data; uint8_t *oacp_res = ots->oacp_ind.res; uint16_t oacp_res_len = 0; + int err; /* Encode OACP Response */ oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP; @@ -669,9 +739,16 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr, ots->oacp_ind.params.data = oacp_res; ots->oacp_ind.params.len = oacp_res_len; - LOG_DBG("OtsOacpSendInd"); + LOG_DBG("OtsOacpSendInd[%u]", conn->handle); - k_work_submit(&ots->oacp_ind.work); + ots->oacp_ind.conn = conn; + ots->oacp_ind.ind_in_flight = true; + err = k_work_schedule(&ots->oacp_ind.work, K_NO_WAIT_ASYNC); + if (err < 0) { + LOG_ERR("OtsOacpSchIndFail[%u][%d]", conn->handle, err); + ots->oacp_ind.ind_in_flight = false; + ots->oacp_ind.conn = NULL; + } } ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, @@ -697,7 +774,8 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } - if (k_work_is_pending(&ots->oacp_ind.work)) { + if (ots->oacp_ind.ind_in_flight || + k_work_is_pending(&ots->oacp_ind.work.work)) { LOG_WRN("OtsOacpWrBeforeIndSent"); return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); } @@ -725,7 +803,7 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); } - oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param); + oacp_ind_send(conn, attr, oacp_proc, oacp_status, &resp_param); return len; } @@ -741,5 +819,10 @@ void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr, oacp_ind->is_enabled = false; if (value == BT_GATT_CCC_INDICATE) { oacp_ind->is_enabled = true; + } else { + LOG_DBG("OtsOacpIndClrOnCcc"); + (void)k_work_cancel_delayable(&oacp_ind->work); + oacp_ind->ind_in_flight = false; + oacp_ind->conn = NULL; } } diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_oacp_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_oacp_internal.h index a72f3e61f54..caa7ad8adfd 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_oacp_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_oacp_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager.c b/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager.c index 4ebc3c5148e..294e28a5730 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -185,7 +186,7 @@ int bt_gatt_ots_obj_manager_obj_add( if (!cur_obj->is_allocated) { cur_obj->is_allocated = true; - /* TODO: do we need to reset cur_obj->val to 0 here? */ + (void)memset(&cur_obj->val, 0, sizeof(cur_obj->val)); cur_obj->val.id = obj_index_to_id(i); sys_dlist_append(&obj_manager->list, &cur_obj->dnode); diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager_internal.h index 4bc2f5cba4c..019fdef11b9 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_obj_manager_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_olcp.c b/components/bt/esp_ble_audio/host/services/ots/ots_olcp.c index c1982bde8bc..6f30cd94f99 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_olcp.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_olcp.c @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -200,16 +201,23 @@ static void olcp_ind_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { + struct bt_ots *ots = (struct bt_ots *) params->attr->user_data; + LOG_DBG("OtsOlcpRecvIndAck[%04x]", err); + + ots->olcp_ind.ind_in_flight = false; + ots->olcp_ind.conn = NULL; } -static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr, +static void olcp_ind_send(struct bt_conn *conn, + const struct bt_gatt_attr *olcp_attr, enum bt_gatt_ots_olcp_proc_type req_op_code, enum bt_gatt_ots_olcp_res_code olcp_status) { struct bt_ots *ots = (struct bt_ots *) olcp_attr->user_data; uint8_t *olcp_res = ots->olcp_ind.res; uint16_t olcp_res_len = 0; + int err; /* Encode OLCP Response */ olcp_res[olcp_res_len++] = BT_GATT_OTS_OLCP_PROC_RESP; @@ -219,7 +227,7 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr, /* Prepare indication parameters */ memset(&ots->olcp_ind.params, 0, sizeof(ots->olcp_ind.params)); memcpy(&ots->olcp_ind.attr, olcp_attr, sizeof(ots->olcp_ind.attr)); - ots->olcp_ind.params.attr = olcp_attr; + ots->olcp_ind.params.attr = &ots->olcp_ind.attr; ots->olcp_ind.params.func = olcp_ind_cb; ots->olcp_ind.params.data = olcp_res; ots->olcp_ind.params.len = olcp_res_len; @@ -227,9 +235,16 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr, ots->olcp_ind.params.chan_opt = BT_ATT_CHAN_OPT_NONE; #endif /* CONFIG_BT_EATT */ - LOG_DBG("OtsOlcpSendInd"); + LOG_DBG("OtsOlcpSendInd[%u]", conn->handle); - k_work_submit(&ots->olcp_ind.work); + ots->olcp_ind.conn = conn; + ots->olcp_ind.ind_in_flight = true; + err = k_work_schedule(&ots->olcp_ind.work, K_NO_WAIT_ASYNC); + if (err < 0) { + LOG_ERR("OtsOlcpSchIndFail[%u][%d]", conn->handle, err); + ots->olcp_ind.ind_in_flight = false; + ots->olcp_ind.conn = NULL; + } } ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn, @@ -255,11 +270,18 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } - if (k_work_is_pending(&ots->olcp_ind.work)) { + if (ots->olcp_ind.ind_in_flight || + k_work_is_pending(&ots->olcp_ind.work.work)) { LOG_WRN("OtsOlcpWrBeforeIndSent"); return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); } + if (ots->cur_obj && + ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) { + LOG_WRN("OtsOlcpWrObjBusy[%d]", ots->cur_obj->state.type); + return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); + } + old_obj = ots->cur_obj; decode_status = olcp_command_decode(buf, len, &olcp_proc); @@ -300,7 +322,7 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); } - olcp_ind_send(attr, olcp_proc.type, olcp_status); + olcp_ind_send(conn, attr, olcp_proc.type, olcp_status); return len; } @@ -316,5 +338,10 @@ void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr, olcp_ind->is_enabled = false; if (value == BT_GATT_CCC_INDICATE) { olcp_ind->is_enabled = true; + } else { + LOG_DBG("OtsOlcpIndClrOnCcc"); + (void)k_work_cancel_delayable(&olcp_ind->work); + olcp_ind->ind_in_flight = false; + olcp_ind->conn = NULL; } } diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_olcp_internal.h b/components/bt/esp_ble_audio/host/services/ots/ots_olcp_internal.h index d0fa74fb6f9..7d00d39e812 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_olcp_internal.h +++ b/components/bt/esp_ble_audio/host/services/ots/ots_olcp_internal.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA + * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h index b78e45b180e..bc8259a1943 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h @@ -1505,6 +1505,7 @@ struct bt_bap_unicast_server_cb { * * @return 0 in case of success, negative error code otherwise. */ +int bt_bap_unicast_server_register(const struct bt_bap_unicast_server_register_param *param); int bt_bap_unicast_server_register_safe(const struct bt_bap_unicast_server_register_param *param); /** @@ -1521,6 +1522,7 @@ int bt_bap_unicast_server_register_safe(const struct bt_bap_unicast_server_regis * * @return 0 in case of success, negative error code otherwise. */ +int bt_bap_unicast_server_unregister(void); int bt_bap_unicast_server_unregister_safe(void); /** @@ -2695,6 +2697,7 @@ int bt_bap_broadcast_sink_delete_safe(struct bt_bap_broadcast_sink *sink); * * @return 0 in case of success or negative value in case of error. */ +int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb); int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb); /** @@ -2705,6 +2708,7 @@ int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb); * * @return 0 in case of success or negative value in case of error. */ +int bt_bap_scan_delegator_unregister(void); int bt_bap_scan_delegator_unregister_safe(void); /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/gmap.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/gmap.h index a9a65cfa04c..4940c5f2958 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/gmap.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/gmap.h @@ -66,19 +66,19 @@ enum bt_gmap_ugg_feat { /** * @brief Support transmitting multiple LC3 codec frames per block in an SDU * - * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0 + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 0 */ BT_GMAP_UGG_FEAT_MULTIPLEX = BIT(0), /** * @brief 96 kbps source support * - * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0 + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 0 */ BT_GMAP_UGG_FEAT_96KBPS_SOURCE = BIT(1), /** * @brief Support for receiving at least two channels of audio, each in a separate CIS * - * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 1 and + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 1 and * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT} > 1 */ BT_GMAP_UGG_FEAT_MULTISINK = BIT(2), diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h index 546ae5eebd4..95c6e5ab6d1 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h @@ -392,6 +392,7 @@ struct bt_has_preset_register_param { * * @return 0 if success, errno on failure. */ +int bt_has_register(const struct bt_has_features_param *features); int bt_has_register_safe(const struct bt_has_features_param *features); /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h index 0afa3453af5..f3ff59566d5 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h @@ -1601,6 +1601,7 @@ int bt_media_proxy_pl_register_safe(struct media_proxy_pl_calls *pl_calls); * * TODO: Move to player header file */ +int bt_media_proxy_pl_init(void); int bt_media_proxy_pl_init_safe(void); /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/micp.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/micp.h index 353eaccda78..615e3c4b9ef 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/micp.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/micp.h @@ -94,6 +94,7 @@ struct bt_micp_included { * * @return 0 if success, errno on failure. */ +int bt_micp_mic_dev_register(struct bt_micp_mic_dev_register_param *param); int bt_micp_mic_dev_register_safe(struct bt_micp_mic_dev_register_param *param); /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h index b0ffc14449c..8d47ea2838e 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h @@ -591,6 +591,7 @@ struct bt_tbs_register_param { * @kconfig{CONFIG_BT_TBS_BEARER_COUNT}) * @retval -ENOEXEC The service failed to be registered */ +int bt_tbs_register_bearer(const struct bt_tbs_register_param *param); int bt_tbs_register_bearer_safe(const struct bt_tbs_register_param *param); /** @@ -611,6 +612,7 @@ int bt_tbs_register_bearer_safe(const struct bt_tbs_register_param *param); * registered. * @retval -ENOEXEC The service failed to be unregistered */ +int bt_tbs_unregister_bearer(uint8_t bearer_index); int bt_tbs_unregister_bearer_safe(uint8_t bearer_index); /** @brief Prints all calls of all services to the debug log */ diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tmap.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tmap.h index 95c2ac5c522..91b4e2e1f28 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tmap.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tmap.h @@ -101,6 +101,7 @@ struct bt_tmap_cb { * * @return 0 on success or negative error value on failure. */ +int bt_tmap_register(enum bt_tmap_role role); int bt_tmap_register_safe(enum bt_tmap_role role); /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h index db605ea47df..e6d1c4b0718 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h @@ -131,6 +131,7 @@ int bt_vcp_vol_rend_included_get_safe(struct bt_vcp_included *included); * * @return 0 if success, errno on failure. */ +int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param); int bt_vcp_vol_rend_register_safe(struct bt_vcp_vol_rend_register_param *param); /** diff --git a/components/bt/esp_ble_audio/lib/lib b/components/bt/esp_ble_audio/lib/lib index db6ea94a1ec..11b8159e741 160000 --- a/components/bt/esp_ble_audio/lib/lib +++ b/components/bt/esp_ble_audio/lib/lib @@ -1 +1 @@ -Subproject commit db6ea94a1ecfac283e84858c8e07f5be18536455 +Subproject commit 11b8159e741a0657b63b8275f50db5101c851547 diff --git a/components/bt/esp_ble_iso/CMakeLists.txt b/components/bt/esp_ble_iso/CMakeLists.txt index 4db9e815cba..32e5d80ce4d 100644 --- a/components/bt/esp_ble_iso/CMakeLists.txt +++ b/components/bt/esp_ble_iso/CMakeLists.txt @@ -44,6 +44,7 @@ list(APPEND ble_iso_srcs "${CMAKE_CURRENT_LIST_DIR}/host/common/app/gap.c" "${CMAKE_CURRENT_LIST_DIR}/host/common/app/gatt.c" "${CMAKE_CURRENT_LIST_DIR}/host/utils/crc/crc32_sw.c" + "${CMAKE_CURRENT_LIST_DIR}/host/utils/assert.c" "${CMAKE_CURRENT_LIST_DIR}/host/utils/addr.c" "${CMAKE_CURRENT_LIST_DIR}/host/utils/bt_str.c" "${CMAKE_CURRENT_LIST_DIR}/host/utils/buf.c" @@ -57,21 +58,6 @@ list(APPEND ble_iso_srcs "${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_iso_common_api.c" ) -# L2CAP host shim wiring is currently only consumed by the OTS service -# (which lives in esp_ble_audio). common/l2cap.c has both host branches -# (Bluedroid returns -ENOTSUP), so compile it under either host whenever -# OTS is enabled; the NimBLE-only adapter glue stays NimBLE-gated. -if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT) - list(APPEND ble_iso_srcs - "${CMAKE_CURRENT_LIST_DIR}/host/common/l2cap.c" - ) - if(CONFIG_BT_NIMBLE_ENABLED) - list(APPEND ble_iso_srcs - "${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/l2cap.c" - ) - endif() -endif() - list(APPEND ble_iso_include_dirs "${CMAKE_CURRENT_LIST_DIR}/api/include" "${CMAKE_CURRENT_LIST_DIR}/host/common/include" diff --git a/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c b/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c index 0f9f6deb998..bb83fb5ac2f 100644 --- a/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c +++ b/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c @@ -35,15 +35,9 @@ esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size, type = ltv[i + 1]; data_len = len - sizeof(uint8_t); - /* Skip empty value entries in strict parsing mode. */ - if (data_len == 0) { - i += (size_t)len + 1; - continue; - } - - /* A callback returning false aborts parsing; report it as an error so - * callers detect it (bt_audio_data_parse() returns -ECANCELED here). */ - if (func(type, <v[i + 2], data_len, user_data) == false) { + /* Zero-length values are valid (e.g. BROADCAST_IMMEDIATE flag-only LTV). + * Match Zephyr bt_audio_data_parse: invoke callback with data=NULL. */ + if (func(type, data_len > 0 ? <v[i + 2] : NULL, data_len, user_data) == false) { return ESP_FAIL; } @@ -292,20 +286,26 @@ esp_err_t esp_ble_iso_big_sync(uint16_t sync_handle, esp_ble_iso_big_sync_param_t *param, esp_ble_iso_big_t **out_big) { + esp_err_t ret = ESP_OK; void *per_adv_sync; int err; - per_adv_sync = bt_le_per_adv_sync_find_safe(sync_handle); + bt_le_host_lock(); + + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync == NULL) { - return ESP_ERR_NOT_FOUND; + ret = ESP_ERR_NOT_FOUND; + goto unlock; } - err = bt_iso_big_sync_safe(per_adv_sync, param, out_big); + err = bt_iso_big_sync(per_adv_sync, param, out_big); if (err) { - return ESP_FAIL; + ret = ESP_FAIL; } - return ESP_OK; +unlock: + bt_le_host_unlock(); + return ret; } #endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ @@ -399,7 +399,7 @@ esp_err_t esp_ble_iso_chan_send_ts(esp_ble_iso_chan_t *chan, } #endif /* CONFIG_BT_ISO_TX */ -void esp_ble_iso_gap_app_post_event(uint8_t type, void *param) +void esp_ble_iso_gap_app_post_event(uint16_t type, void *param) { bt_le_gap_app_post_event(type, param); } diff --git a/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h b/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h index a9560718f74..8f06e42566e 100644 --- a/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h +++ b/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h @@ -583,7 +583,7 @@ typedef struct { * @param type Event type. * @param param Event parameters. */ -void esp_ble_iso_gap_app_post_event(uint8_t type, void *param); +void esp_ble_iso_gap_app_post_event(uint16_t type, void *param); /** * @brief Initialize ISO common functionality. diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c index 0d9e98a3972..dda8a19ed38 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c @@ -154,7 +154,7 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); /* Only AUTH_CMPL reaches here from the application's * esp_ble_gap_register_callback path. EXT_ADV_REPORT / PA_SYNC_ESTAB / @@ -164,26 +164,17 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param) switch (event) { case ESP_GAP_BLE_AUTH_CMPL_EVT: { const esp_ble_auth_cmpl_t *a = &p->ble_security.auth_cmpl; - struct gatt_conn *gatt_conn; uint8_t sec_level; - gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(a->addr_type, a->bd_addr, false); - if (gatt_conn == NULL) { - LOG_ERR("[B]UnknownDevForEnc"); - free(qev); - return; - } - qev->type = BT_LE_GAP_APP_PARAM_SECURITY_CHANGE; qev->security_change.status = (a->success ? 0x00 : 0xFF); - /* Populate connection identity unconditionally so failure events - * carry valid conn_handle / role / dst to the application. */ - qev->security_change.conn_handle = gatt_conn->conn_handle; - qev->security_change.role = gatt_conn->role; - qev->security_change.dst.type = gatt_conn->peer.type; - memcpy(qev->security_change.dst.val, gatt_conn->peer.val, BT_ADDR_SIZE); + /* dst only: conn_handle/role are resolved from it by the iso_task handler. + * Looking them up here would mean reading gatt_conns[] from BTC, which + * iso_task mutates under host_lock - and BTC must not take that lock. */ + qev->security_change.dst.type = a->addr_type; + memcpy(qev->security_change.dst.val, a->bd_addr, BT_ADDR_SIZE); if (qev->security_change.status == 0) { /* Derive level from auth_mode bits (mirrors NimBLE's sec_state @@ -204,9 +195,9 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param) /* Attach the bonded LTK (CSIS SIRK-encryption key K). */ if (bd_read_bonded_ltk(a->bd_addr, qev->security_change.ltk)) { qev->security_change.ltk_present = 1; - LOG_INF("[B]LtkFromStore[%u]", qev->security_change.conn_handle); + LOG_INF("[B]LtkFromStore"); } else { - LOG_WRN("[B]NoLtkForEnc[%u]", qev->security_change.conn_handle); + LOG_WRN("[B]NoLtkForEnc"); } } @@ -234,7 +225,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event, int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); switch (event) { #if (BLE_50_EXTEND_SCAN_EN == TRUE) @@ -258,7 +249,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event, if (qev->ext_scan_recv.data_len) { qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len); - assert(qev->ext_scan_recv.data); + BT_LE_ASSERT(qev->ext_scan_recv.data); memcpy(qev->ext_scan_recv.data, r->adv_data, qev->ext_scan_recv.data_len); } break; @@ -347,7 +338,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event, if (qev->pa_sync_recv.data_len) { qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len); - assert(qev->pa_sync_recv.data); + BT_LE_ASSERT(qev->pa_sync_recv.data); memcpy(qev->pa_sync_recv.data, r->data, qev->pa_sync_recv.data_len); } break; @@ -357,16 +348,6 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event, #if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE) case BTM_BLE_GAP_PERIODIC_ADV_SYNC_TRANS_RECV_EVT: { const tBTM_BLE_PERIOD_ADV_SYNC_TRANS_RECV *r = ¶ms->past_recv; - struct gatt_conn *gatt_conn; - - /* Look up the PAST-delivering ACL handle by peer BDA — past_recv - * doesn't carry addr type. */ - gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, r->addr, true); - if (gatt_conn == NULL) { - LOG_ERR("[B]UnknownPastSrc"); - free(qev); - return; - } qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_PAST; @@ -378,7 +359,13 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event, qev->pa_sync_past.adv_phy = r->adv_phy; qev->pa_sync_past.per_adv_itvl = r->adv_interval; qev->pa_sync_past.adv_ca = r->adv_clk_accuracy; - qev->pa_sync_past.conn_handle = gatt_conn->conn_handle; + + /* Sender identity: HCI reports it as a Connection_Handle and carries no + * address for it (adv_addr_type/adv_addr above are the advertiser's). + * btu_hcif copies the BDA out of the LCB but drops both the handle and + * the address type, so .type stays unset and the handler matches on the + * value alone. */ + memcpy(qev->pa_sync_past.src_addr.val, r->addr, BT_ADDR_SIZE); #if (BLE_50_EXTEND_SYNC_EN == TRUE) /* PAST-established syncs need tracker too: a later app-initiated diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c index d1ede2c903f..af55064cd75 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c @@ -21,6 +21,7 @@ #include "bta/bta_gatt_common.h" #include "bta_gattc_int.h" #include "btc_gatt_util.h" +#include "osi/allocator.h" #include "stack/btm_ble_api.h" #include "common/host.h" @@ -148,7 +149,7 @@ static int gatts_indicate_resolve(const struct bt_gatt_attr *attr_in, handle = data.handle; if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) { - assert(data.attr->user_data); + BT_LE_ASSERT(data.attr->user_data); chrc = data.attr->user_data; if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) { @@ -181,7 +182,7 @@ static struct gatts_list_node *gatts_list_node_alloc(struct bt_gatt_indicate_par n->data_copy = NULL; if (ip->len > 0) { - assert(ip->data); + BT_LE_ASSERT(ip->data); n->data_copy = bt_le_ext_malloc(ip->len); if (n->data_copy == NULL) { @@ -226,6 +227,27 @@ static void gatts_notify_list_drain(struct gatt_conn *gatt_conn) } } +/* Remove the first notify marker whose value_handle matches. Used by + * handle_gatts_notify_tx_event so an indication CONF cannot be mistaken for + * a notify CONF when a notify marker is already queued (BTU/ISO race). */ +static struct gatts_notify_node * +gatts_notify_take_by_handle(struct gatt_conn *gatt_conn, uint16_t handle) +{ + struct gatts_notify_node *notify; + struct gatts_notify_node *tmp; + sys_snode_t *prev = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&gatt_conn->gatts_notify_list, notify, tmp, node) { + if (notify->value_handle == handle) { + sys_slist_remove(&gatt_conn->gatts_notify_list, prev, ¬ify->node); + return notify; + } + prev = ¬ify->node; + } + + return NULL; +} + /* Rewrite a slot's conn_id to the GATTS/GATTC gatt_if (low byte); the BTA index * (high byte) is shared on one ACL. Lets a dual-role link op on the right conn_id. */ static inline uint16_t to_gatts_conn_id(uint16_t conn_id) @@ -280,6 +302,13 @@ uint8_t bt_le_bluedroid_gattc_get_if(void) uint8_t bt_le_bluedroid_gatts_get_if(void) { + /* Callers pass this straight to BTA without checking, so the value matters + * less than the log: a silent 0 surfaces only as an unexplained sem timeout. */ + if (gatts_if == 0) { + LOG_WRN("[B]GetGattsIfBeforeInit"); + return 0xFF; /* ESP_GATT_IF_NONE */ + } + return gatts_if; } @@ -435,7 +464,7 @@ static void gattc_connect_event_handler(tBTA_GATTC_CONNECT *connect) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_CONNECT_EVENT; @@ -458,7 +487,7 @@ static void gattc_disconnect_event_handler(tBTA_GATTC_DISCONNECT *disconnect) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_DISCONNECT_EVENT; @@ -478,7 +507,7 @@ static void gattc_open_event_handler(tBTA_GATTC_OPEN *open) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_OPEN_EVENT; @@ -498,7 +527,7 @@ static void gattc_mtu_event_handler(tBTA_GATTC_CFG_MTU *cfg_mtu) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_MTU_EVENT; @@ -519,7 +548,7 @@ static void gattc_disc_cmpl_event_handler(tBTA_GATTC_DIS_CMPL *disc_cmpl) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_DISC_CMPL_EVENT; @@ -539,7 +568,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_READ_CHRC_EVENT; @@ -553,7 +582,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read) qev->gattc_read_chrc.len = read->p_value->len; qev->gattc_read_chrc.value = bt_le_ext_calloc(1, read->p_value->len); - assert(qev->gattc_read_chrc.value); + BT_LE_ASSERT(qev->gattc_read_chrc.value); memcpy(qev->gattc_read_chrc.value, read->p_value->p_value, read->p_value->len); } @@ -574,7 +603,7 @@ static void gattc_write_chrc_event_handler(tBTA_GATTC_WRITE *write) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_WRITE_CHRC_EVENT; @@ -596,15 +625,15 @@ static void gatts_notify_tx_event_handler(tBTA_GATTS_REQ *req) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_NOTIFY_TX_EVENT; /* BTA fires CONF_EVT for both indication acks and immediate notify * completions; tBTA_GATTS_REQ carries no flag to tell them apart. The - * bluedroid handler disambiguates by popping a parallel notify marker - * list first (see gatts_notify_enqueue / handle_gatts_notify_tx_event), - * so is_notify here is left unused on this path. */ + * bluedroid handler matches by attr_handle against gatts_list head / + * gatts_notify_list markers (see gatts_notify_take_by_handle), so + * is_notify here is left unused on this path. */ qev->gatts_notify_tx.is_notify = false; qev->gatts_notify_tx.conn_id = req->conn_id; qev->gatts_notify_tx.attr_handle = req->handle; @@ -623,7 +652,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_NOTIFY_RX_EVENT; @@ -635,7 +664,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify) qev->gattc_notify_rx.len = notify->len; qev->gattc_notify_rx.value = bt_le_ext_calloc(1, notify->len); - assert(qev->gattc_notify_rx.value); + BT_LE_ASSERT(qev->gattc_notify_rx.value); memcpy(qev->gattc_notify_rx.value, notify->value, notify->len); } @@ -656,7 +685,7 @@ static void gatts_connect_event_handler(tBTA_GATTS_CONN *connect) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_CONNECT_EVENT; @@ -679,7 +708,7 @@ static void gatts_disconnect_event_handler(tBTA_GATTS_CONN *disconnect) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_DISCONNECT_EVENT; @@ -700,7 +729,7 @@ static void gatts_mtu_event_handler(tBTA_GATTS_REQ *req) /* req->p_data is non-NULL here: BTA always passes a stack object. */ qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_MTU_EVENT; @@ -720,7 +749,7 @@ static void gatts_read_req_handler(tBTA_GATTS_REQ *req) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_READ_EVENT; @@ -746,7 +775,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req) /* req->p_data is non-NULL here: BTA always passes a stack object. */ qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_WRITE_EVENT; @@ -762,7 +791,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req) qev->gatts_write.len = req->p_data->write_req.len; qev->gatts_write.value = bt_le_ext_calloc(1, req->p_data->write_req.len); - assert(qev->gatts_write.value); + BT_LE_ASSERT(qev->gatts_write.value); memcpy(qev->gatts_write.value, req->p_data->write_req.value, req->p_data->write_req.len); } @@ -783,7 +812,7 @@ static void gatts_exec_write_req_handler(tBTA_GATTS_REQ *req) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTS_EXEC_WRITE_EVENT; qev->gatts_exec_write.conn_id = req->conn_id; @@ -1369,7 +1398,7 @@ static void post_acl_connect_app_event(struct gatt_conn *gatt_conn) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GAP_APP_PARAM_ACL_CONNECT; @@ -1397,7 +1426,7 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GAP_APP_PARAM_ACL_DISCONNECT; @@ -1411,6 +1440,24 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason) } } +/* Fire func(err) for every pending GATTS indication and free the nodes, so lib + * state machines waiting on a confirm unblock. conn may be NULL (already gone) + * — func(NULL, ...) still releases the waiter. */ +static void gatts_list_fire_and_drain(struct gatt_conn *gatt_conn, + struct bt_conn *conn, uint8_t err) +{ + struct gatts_list_node *n; + sys_snode_t *snode; + + while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) { + n = CONTAINER_OF(snode, struct gatts_list_node, node); + if (n->params_copy.func) { + n->params_copy.func(conn, &n->params_copy, err); + } + gatts_list_node_free(n); + } +} + static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event) { struct gatt_conn *gatt_conn; @@ -1423,8 +1470,22 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event) gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false); if (gatt_conn) { - LOG_ERR("[B]DevAlreadyExists"); - return; + if (gatt_conn->conn_handle == event->conn_handle) { + LOG_ERR("[B]DevAlreadyExists"); + return; + } + + /* Same peer, different handle: prior disconnect was missed/delayed. + * Drop the stale slot so reconnect can proceed. */ + LOG_WRN("[B]StaleGattcConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle); + + post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED); + + /* Stale slot never gets a disconnect event: fire pending indication + * funcs so lib state machines unblock before the drain in reset. */ + gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY); + + reset_gatt_conn(gatt_conn); } /* App initiated via esp_ble_gattc_aux_open(engine_gattc_if, ...). BTA @@ -1450,9 +1511,7 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event) static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *event) { struct gatt_conn *gatt_conn; - struct gatts_list_node *n; struct bt_conn *conn; - sys_snode_t *snode; gatt_conn = find_gatt_conn_with_conn_id(event->conn_id); if (gatt_conn == NULL) { @@ -1463,22 +1522,15 @@ static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *e event->conn_handle = gatt_conn->conn_handle; if (gatt_conn->role == BTM_ROLE_MASTER) { - /* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS). - * Mirror handle_gatts_disconnect_event: fire func(err) for every - * pending indication so lib state machines don't stall. */ conn = bt_le_acl_conn_find(event->conn_handle); - while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) { - n = CONTAINER_OF(snode, struct gatts_list_node, node); - if (conn && n->params_copy.func) { - n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY); - } - gatts_list_node_free(n); - } + /* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS): + * fire func(err) for pending indications so lib state machines don't + * stall. conn may be gone — func(NULL, ...) still unblocks waiters. */ + gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY); post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason); - /* Reset the corresponding gatt_conn */ reset_gatt_conn(gatt_conn); } } @@ -1502,13 +1554,11 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event) post_acl_connect_app_event(gatt_conn); if (gatt_conn->status) { - /* If failed to create connection, reset the corresponding gatt_conn */ reset_gatt_conn(gatt_conn); return; } } else { if (gatt_conn->gattc_open && gatt_conn->status != BTA_GATT_OK) { - /* Failed to open gatt client, post an event to notify the app layer */ bt_le_gattc_app_open_event(event, gattc_if); return; } @@ -1526,15 +1576,13 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event) gatt_conn->cfg_mtu = 1; } - /* Post an event to the app layer, we need this event to discover - * services and characteristics, etc. - */ bt_le_gattc_app_open_event(event, gattc_if); } static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event) { struct gatt_conn *gatt_conn; + uint16_t prev_mtu; gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id); if (gatt_conn == NULL) { @@ -1548,6 +1596,8 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event) * related to the Link Layer role, hence we don't check the Link layer * role here and only check if the MTU has already been exchanged here. */ + prev_mtu = gatt_conn->mtu; + if (gatt_conn->mtu != 0) { LOG_INF("[B]GattcMtuExchanged[%u][%u][%u]", gatt_conn->conn_handle, gatt_conn->mtu, event->mtu); @@ -1558,13 +1608,17 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event) } if (gatt_conn->cfg_mtu && gatt_conn->mtu_posted == 0) { - /* Post an event to the app layer, we need this event to discover - * services and characteristics, etc. Report the clamped value so - * the app stays consistent with gatt_conn state (mirrors NimBLE's - * BLE_GAP_EVENT_MTU which already carries the negotiated value). */ + /* Report the clamped value so the app stays consistent with + * gatt_conn state (mirrors NimBLE's BLE_GAP_EVENT_MTU). */ bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu); - gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */ + gatt_conn->mtu_posted = 1; + } else if (gatt_conn->mtu_posted && gatt_conn->mtu < prev_mtu) { + /* Dual-role: peer/side may clamp after the first post; app must + * learn the smaller value or it will oversize ATT PDUs. */ + LOG_INF("[B]GattcMtuClamped[%u][%u][%u]", + event->conn_handle, prev_mtu, gatt_conn->mtu); + bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu); } } @@ -1617,7 +1671,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve const uint8_t *val; sys_snode_t *snode; uint16_t vlen; - uint16_t off; uint8_t ret; gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id); @@ -1660,11 +1713,14 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve goto end; } - if ((read_copy.handle_count == 0 && - (event->attr_handle < read_copy.by_uuid.start_handle || - event->attr_handle > read_copy.by_uuid.end_handle)) || - (read_copy.handle_count == 1 && - event->attr_handle != read_copy.single.handle)) { + /* On error BTA often returns attr_handle 0; only validate the handle when + * the read succeeded, otherwise pass the real ATT status to the caller. */ + if (event->status == 0 && + ((read_copy.handle_count == 0 && + (event->attr_handle < read_copy.by_uuid.start_handle || + event->attr_handle > read_copy.by_uuid.end_handle)) || + (read_copy.handle_count == 1 && + event->attr_handle != read_copy.single.handle))) { LOG_ERR("[B]GattcRdCharInvRsp[%u][%u][%u][%u][%u]", read_copy.handle_count, event->attr_handle, read_copy.by_uuid.start_handle, @@ -1676,13 +1732,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve val = event->value; vlen = event->len; - if (read_copy.handle_count == 1 && read_copy.single.offset != 0 && - event->status == 0) { - off = read_copy.single.offset; - val = (off < event->len) ? event->value + off : NULL; - vlen = (off < event->len) ? (event->len - off) : 0; - } - /* By-UUID read: report the matched handle in params->by_uuid.start_handle (like * Zephyr); callers (has_client active-index read) use it as the value handle. */ if (read_copy.handle_count == 0 && event->status == 0) { @@ -1745,7 +1794,7 @@ static void handle_gattc_write_chrc_event(struct bt_le_gattc_write_chrc_event *e params = op->write_params; free(op); - assert(params && params->func); + BT_LE_ASSERT(params && params->func); if (event->attr_handle != params->handle) { LOG_ERR("[B]GattcWrCharInvRsp[%u][%u]", event->attr_handle, params->handle); @@ -1826,8 +1875,22 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event) gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false); if (gatt_conn) { - LOG_ERR("[B]DevAlreadyExists"); - return; + if (gatt_conn->conn_handle == event->conn_handle) { + LOG_ERR("[B]DevAlreadyExists"); + return; + } + + /* Same peer, different handle: prior disconnect was missed/delayed. + * Drop the stale slot so reconnect can proceed. */ + LOG_WRN("[B]StaleGattsConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle); + + post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED); + + /* Stale slot never gets a disconnect event: fire pending indication + * funcs so lib state machines unblock before the drain in reset. */ + gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY); + + reset_gatt_conn(gatt_conn); } gatt_conn = bt_le_bluedroid_find_free_gatt_conn(); @@ -1860,10 +1923,8 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event) static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *event) { - struct gatts_list_node *n; struct gatt_conn *gatt_conn; struct bt_conn *conn; - sys_snode_t *snode; gatt_conn = find_gatt_conn_with_conn_id(event->conn_id); if (gatt_conn == NULL) { @@ -1879,32 +1940,22 @@ static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *e return; } - /* conn may already have been torn down by the ACL layer before this event - * arrives — best-effort lookup, NULL is tolerated and the drain still runs - * (params->func is just skipped for nodes we can't address). */ conn = bt_le_acl_conn_find(event->conn_handle); - /* Fire func with err for every pending indication. Mirrors NimBLE stack's - * ble_gatts_indicate_fail_notconn on disconn — BTA never delivers CONF_EVT - * for in-flight indications when the conn drops, so the adapter has to - * simulate that fail path to keep lib state machines from stalling. */ - while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) { - n = CONTAINER_OF(snode, struct gatts_list_node, node); - if (conn && n->params_copy.func) { - n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY); - } - gatts_list_node_free(n); - } + /* conn may be gone — fire func(NULL, err) for pending indications so upper + * layers don't stall waiting for a CONF_EVT BTA never delivers after the + * link drops. */ + gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY); post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason); - /* Reset the corresponding gatt_conn */ reset_gatt_conn(gatt_conn); } static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event) { struct gatt_conn *gatt_conn; + uint16_t prev_mtu; gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id); if (gatt_conn == NULL) { @@ -1918,6 +1969,8 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event) * related to the Link Layer role, hence we don't check the Link layer * role here and only check if the MTU has already been exchanged here. */ + prev_mtu = gatt_conn->mtu; + if (gatt_conn->mtu != 0) { LOG_INF("[B]GattsMtuExchanged[%u][%u][%u]", gatt_conn->conn_handle, gatt_conn->mtu, event->mtu); @@ -1931,7 +1984,12 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event) /* Report clamped value — see handle_gattc_mtu_event. */ bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu); - gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */ + gatt_conn->mtu_posted = 1; + } else if (gatt_conn->mtu < prev_mtu) { + /* Dual-role clamp after first post — see handle_gattc_mtu_event. */ + LOG_INF("[B]GattsMtuClamped[%u][%u][%u]", + event->conn_handle, prev_mtu, gatt_conn->mtu); + bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu); } } @@ -1969,11 +2027,11 @@ static void handle_gatts_read_event(struct bt_le_gatts_read_event *event) goto end; } - /* The tBTA_GATT_STATUS structure is too large, hence use + /* The tBTA_GATTS_RSP structure is too large, hence use * dynamic memory here to avoid stack overflow. */ rsp = bt_le_ext_calloc(1, sizeof(*(rsp))); - assert(rsp); + BT_LE_ASSERT(rsp); rsp->attr_value.handle = event->attr_handle; @@ -2069,7 +2127,7 @@ static void handle_gatts_prepare_write(struct bt_le_gatts_write_event *event) if (status == BTA_GATT_OK) { /* The prepare-write response must echo handle/offset/value. */ rsp = bt_le_ext_calloc(1, sizeof(*rsp)); - assert(rsp); + BT_LE_ASSERT(rsp); rsp->attr_value.handle = event->attr_handle; rsp->attr_value.offset = event->offset; @@ -2133,7 +2191,6 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event) goto end; } - /* Check if the attribute UUID is CCCD */ if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) == 0) { if (event->len != 2) { LOG_ERR("[B]GattsWrInvPdu[%u]", event->len); @@ -2256,10 +2313,12 @@ static void handle_gatts_exec_write_event(struct bt_le_gatts_exec_write_event *e static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *event) { - struct gatts_list_node *n; + struct gatts_notify_node *notify; struct gatt_conn *gatt_conn; + struct gatts_list_node *n; struct bt_conn *conn; sys_snode_t *snode; + bool ind_match; /* Find conn once up front. If the acl_conn was already torn down (race * with a queued CLOSE_EVT behind this CONF_EVT), leave the head in the @@ -2278,28 +2337,51 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve return; } - /* Notify CONF_EVT first: BTA fires CONF_EVT immediately after send for - * notifications; pop the matching marker so we don't mistake it for an - * indication ack and corrupt the indication queue. */ - snode = sys_slist_get(&gatt_conn->gatts_notify_list); + /* BTA fires CONF_EVT for both notify completions and indication acks, + * with no type flag. Match by attr_handle — do NOT assume "any notify + * marker means this CONF is a notify": a notify may be enqueued on the + * ISO path while an earlier indication's peer ACK is already queued, + * which would swallow the indication CONF and stall gatts_list. When + * both heads share the same handle, prefer indication (peer ACK); the + * notify CONF arrives shortly and claims the leftover marker. */ + snode = sys_slist_peek_head(&gatt_conn->gatts_list); + ind_match = false; + n = NULL; if (snode != NULL) { - struct gatts_notify_node *nn = CONTAINER_OF(snode, struct gatts_notify_node, node); + n = CONTAINER_OF(snode, struct gatts_list_node, node); + ind_match = (n->value_handle == event->attr_handle); + } - if (nn->value_handle != event->attr_handle) { - LOG_DBG("[B]NotifyTxHdlSkew[%u][%u]", event->attr_handle, nn->value_handle); + if (!ind_match) { + notify = gatts_notify_take_by_handle(gatt_conn, event->attr_handle); + if (notify != NULL) { + free(notify); + return; } - free(nn); + + if (n == NULL) { + LOG_WRN("[B]NotifyTxNoPending[%u][%u]", + event->conn_handle, event->attr_handle); + return; + } + + /* Indication head exists but handle disagrees, and no notify marker + * matched either — list is out of sync. Drop the head to keep + * advancing (alternative is freezing the queue). */ + LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle); + + (void)sys_slist_get(&gatt_conn->gatts_list); + + if (n->params_copy.func) { + n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY); + } + + gatts_list_node_free(n); return; } - /* No notify pending — this CONF_EVT is an indication ack. */ - snode = sys_slist_get(&gatt_conn->gatts_list); - if (snode == NULL) { - LOG_WRN("[B]NotifyTxNoPending[%u]", event->conn_handle); - return; - } - - n = CONTAINER_OF(snode, struct gatts_list_node, node); + /* Indication ack for the current head. */ + (void)sys_slist_get(&gatt_conn->gatts_list); /* Submit next pending BEFORE firing the completion cb. host_mutex is * recursive, so a cb that re-enters bt_gatt_indicate isn't blocked; @@ -2319,20 +2401,6 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve } } - if (n->value_handle != event->attr_handle) { - LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle); - /* Mismatch is fatal here: BTA delivers CONF_EVT in FIFO order, so a - * head-vs-event handle disagreement means our list is out of sync. - * Drop the head to keep advancing (alternative is freezing the queue). - * Notify lib with err so its state machine doesn't stall waiting - * for a callback that will never match. */ - if (n->params_copy.func) { - n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY); - } - gatts_list_node_free(n); - return; - } - if (n->params_copy.func) { n->params_copy.func(conn, &n->params_copy, event->status); } @@ -2410,7 +2478,7 @@ void bt_le_bluedroid_gatt_handle_event(uint8_t *data, size_t data_len) break; default: - assert(0); + BT_LE_ASSERT(0); break; } @@ -2428,7 +2496,7 @@ void bt_le_bluedroid_gatts_svc_cb_register(struct gatts_svc_cb *cb) void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid_out) { - assert(uuid_out && uuid_in); + BT_LE_ASSERT(uuid_out && uuid_in); if (uuid_in->type == BT_UUID_TYPE_16) { ((tBT_UUID *)uuid_out)->len = LEN_UUID_16; @@ -2440,7 +2508,7 @@ void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid ((tBT_UUID *)uuid_out)->len = LEN_UUID_128; memcpy(((tBT_UUID *)uuid_out)->uu.uuid128, BT_UUID_128(uuid_in)->val, LEN_UUID_128); } else { - assert(0); + BT_LE_ASSERT(0); } } @@ -2526,8 +2594,8 @@ ssize_t bt_le_bluedroid_gatts_attr_read(struct bt_conn *conn, const struct bt_ga return len; } -/* Append a notify marker so handle_gatts_notify_tx_event can pop it off the - * notify list ahead of any pending indication head. Returns -ENOTCONN when +/* Append a notify marker so handle_gatts_notify_tx_event can match CONF_EVT + * by attr_handle against pending notifications. Returns -ENOTCONN when * the gatt_conn is already torn down (caller should skip the BTA send to * avoid a "Unknown connection ID" log from BTA), -ENOMEM on alloc fail. */ static int gatts_notify_enqueue(struct bt_conn *conn, uint16_t value_handle) @@ -2582,7 +2650,6 @@ static int gatts_notify(struct bt_conn *conn, data.attr = *attr; data.handle = bt_gatt_attr_get_handle(data.attr); - /* Lookup UUID if it was given */ if (uuid) { if (bt_gatts_find_attr_by_uuid(&data, uuid) == false) { return -ENOENT; @@ -2595,12 +2662,11 @@ static int gatts_notify(struct bt_conn *conn, } } - /* Check if attribute is a characteristic then adjust the handle */ if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) { struct bt_gatt_chrc *chrc; uint16_t required; - assert(data.attr->user_data); + BT_LE_ASSERT(data.attr->user_data); chrc = data.attr->user_data; required = need_cfm ? BT_GATT_CHRC_INDICATE : BT_GATT_CHRC_NOTIFY; @@ -2775,7 +2841,6 @@ int bt_le_bluedroid_gattc_disc_start(uint16_t conn_handle) true, BTA_GATT_TRANSPORT_LE, false, 0, false, 0xff, 0xff, 0, NULL, NULL, NULL); - /* Mark the gattc open as in progress */ gatt_conn->gattc_open = 1; return 0; @@ -2859,11 +2924,32 @@ static int gattc_disc_primary_svc(struct bt_conn *conn, uint16_t conn_id, end: if (db) { - free(db); + osi_free(db); } return 0; } +/* BTA expands every DB UUID to 128-bit. Return true and write the 16-bit + * value only when it matches the Bluetooth Base UUID; otherwise unsupported. */ +static bool bluedroid_db_uuid_as_16(const bt_uuid_t *src, uint16_t *out_val) +{ + static const uint8_t base[16] = { + BT_UUID_128_ENCODE(0x00000000, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB) + }; + + for (int i = 0; i < 16; i++) { + if (i == 12 || i == 13) { + continue; + } + if (src->uu[i] != base[i]) { + return false; + } + } + + *out_val = sys_get_le16(&src->uu[12]); + return true; +} + static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id, struct bt_gatt_discover_params *params) { @@ -2907,11 +2993,14 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id, i, db[i].type, db[i].start_handle, db[i].end_handle, db[i].attribute_handle, db[i].properties); + /* Only 16-bit Bluetooth Base UUIDs are supported; skip 32/128-bit + * rather than silently truncating them to a bogus 16-bit value. */ + if (!bluedroid_db_uuid_as_16(&db[i].uuid, &svc_uuid.val)) { + LOG_WRN("[B]InclSvcNon16BitUuidSkip[%u]", db[i].attribute_handle); + continue; + } + svc_uuid.uuid.type = BT_UUID_TYPE_16; - /* The LSB 12-octets is Bluetooth_Base_UUID, and the remaining - * 2-octets or 4-octets is used by 16-bit or 32-bit UUID. - */ - svc_uuid.val = sys_get_le16(db[i].uuid.uu + 12); inc_svc.uuid = &svc_uuid.uuid; inc_svc.start_handle = db[i].start_handle; @@ -2946,7 +3035,7 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id, end: if (db) { - free(db); + osi_free(db); } return 0; } @@ -3041,7 +3130,7 @@ static int gattc_disc_chrc(struct bt_conn *conn, uint16_t conn_id, end: if (db) { - free(db); + osi_free(db); } return 0; } @@ -3058,10 +3147,10 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id, uint16_t count = 0; int err = 0; - assert(params); - assert(params->uuid); - assert(params->func); - assert(params->sub_params); + BT_LE_ASSERT(params); + BT_LE_ASSERT(params->uuid); + BT_LE_ASSERT(params->func); + BT_LE_ASSERT(params->sub_params); /* Only descriptors can be filtered */ if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 || @@ -3100,6 +3189,9 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id, db[0].type, db[0].start_handle, db[0].end_handle, db[0].attribute_handle, db[0].properties); + /* Zephyr bt_gatt_attr contract: attr->uuid must be non-NULL. For + * filtered descriptor discovery this is the requested descriptor UUID. */ + attr.uuid = params->uuid; attr.handle = db[0].attribute_handle; status = BTA_GATTC_RegisterForNotifications(gattc_if, conn->le.dst.a.val, chrc_handle); @@ -3124,7 +3216,7 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id, end: if (db) { - free(db); + osi_free(db); } return err; } @@ -3186,6 +3278,17 @@ int bt_le_bluedroid_gattc_read(struct bt_conn *conn, struct bt_gatt_read_params return -ENODEV; } + /* BTA cannot emit ATT Read Blob: bta_gattc_read hardcodes GATT_READ_BY_HANDLE + * and tBTA_GATTC_API_READ has no offset. Rejecting beats a Read Request from + * byte 0, which makes a continuation read stall silently. Do not hoist this + * into bt_gatt_read() - NimBLE has ble_gattc_read_long. + * TODO: drop once Bluedroid exposes a read-with-offset API. */ + if (params->handle_count == 1 && params->single.offset != 0) { + LOG_ERR("[B]GattcRdNoBlob[%u][%u]", + params->single.handle, params->single.offset); + return -ENOTSUP; + } + /* BTA serializes via p_cmd_list (FIFO); we mirror with gattc_list so the * EVT can recover the caller's params. Append before submit so the head * is set when BTA delivers the cmpl synchronously on error paths. */ @@ -3230,6 +3333,13 @@ int bt_le_bluedroid_gattc_write(struct bt_conn *conn, struct bt_gatt_write_param LOG_DBG("[B]GattcWr[%u]", conn->handle); + /* bta_gattc_write memcpy's into a stack tGATT_CL_COMPLETE sized + * GATT_MAX_ATTR_LEN, with no length check of its own */ + if (params->length > BTA_GATT_MAX_ATTR_LEN) { + LOG_ERR("[B]GattcWrValTooBig[%u]", params->length); + return -EMSGSIZE; + } + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn->handle); if (gatt_conn == NULL) { LOG_ERR("[B]NoConnInfo[%u]", conn->handle); @@ -3261,6 +3371,12 @@ int bt_le_bluedroid_gattc_write_without_rsp(struct bt_conn *conn, uint16_t handl LOG_DBG("[B]GattcWrCmd[%u][%u][%u]", conn->handle, handle, length); + /* Same BTU stack-overflow hazard as gattc_write */ + if (length > BTA_GATT_MAX_ATTR_LEN) { + LOG_ERR("[B]GattcWrCmdValTooBig[%u]", length); + return -EMSGSIZE; + } + /* Mirror sibling GATT ops: refuse to forward doomed writes after * disconnect cleared the gatt_conn slot (bt_conn->state may briefly * still look CONNECTED during the teardown race). */ diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/l2cap.h b/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/l2cap.h new file mode 100644 index 00000000000..35b045e0a9b --- /dev/null +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/l2cap.h @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct bt_l2cap_chan; +struct net_buf; + +int bt_le_bluedroid_l2cap_chan_connect(uint16_t conn_handle); + +int bt_le_bluedroid_l2cap_chan_disconnect(struct bt_l2cap_chan *chan); + +int bt_le_bluedroid_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf); + +void bt_le_bluedroid_l2cap_accept_result(uint16_t conn_handle, uint8_t l2cap_id, + uint16_t chan_handle, uint16_t result); + +int bt_le_bluedroid_l2cap_init(void); + +void bt_le_bluedroid_l2cap_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c index baf82de4f00..9691741b07d 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c @@ -69,7 +69,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp) uint16_t conn_handle; tBTM_STATUS status; - assert(rsp); + BT_LE_ASSERT(rsp); conn_handle = sys_get_le16(buf->data + 3); @@ -89,7 +89,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp) tx_sync.packet_seq_num = sys_get_le16(rsp_buf + 2); tx_sync.tx_time_stamp = sys_get_le32(rsp_buf + 4); tx_sync.time_offset = sys_get_le24(rsp_buf + 8) & 0xFFFFFF; - assert(tx_sync.conn_handle == conn_handle); + BT_LE_ASSERT(tx_sync.conn_handle == conn_handle); } #else /* USE_DIRECT_HCI */ bt_le_host_lock(); @@ -100,7 +100,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp) LOG_ERR("[B]RdIsoTxSyncRspTimeout[0x%03x]", conn_handle); status = BTM_ERR_PROCESSING; } else { - assert(tx_sync.conn_handle == conn_handle); + BT_LE_ASSERT(tx_sync.conn_handle == conn_handle); status = tx_sync.status; } bt_le_host_unlock(); @@ -134,7 +134,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) uint8_t cis_count; uint8_t cig_id; - assert(rsp); + BT_LE_ASSERT(rsp); cig_id = buf->data[3]; cis_count = buf->data[17]; @@ -162,8 +162,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) for (uint8_t i = 0; i < rsp_cis_count; i++) { set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2); } - assert(set_cig_params.cig_id == cig_id); - assert(set_cig_params.cis_count == cis_count); + BT_LE_ASSERT(set_cig_params.cig_id == cig_id); + BT_LE_ASSERT(set_cig_params.cis_count == cis_count); } #else /* USE_DIRECT_HCI */ struct ble_hci_le_cis_params *cis_params; @@ -184,7 +184,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) mtl_p_to_c = sys_get_le16(buf->data + 15); cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params)); - assert(cis_params); + BT_LE_ASSERT(cis_params); for (size_t i = 0; i < cis_count; i++) { cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)]; @@ -213,8 +213,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) LOG_ERR("[B]SetCigParamsRspTimeout[%u]", cig_id); status = BTM_ERR_PROCESSING; } else { - assert(set_cig_params.cig_id == cig_id); - assert(set_cig_params.cis_count == cis_count); + BT_LE_ASSERT(set_cig_params.cig_id == cig_id); + BT_LE_ASSERT(set_cig_params.cis_count == cis_count); status = set_cig_params.status; if (status) { LOG_ERR("[B]SetCigParamsCtrlFail[%u][%02x]", cig_id, status); @@ -250,7 +250,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp uint8_t cis_count; uint8_t cig_id; - assert(rsp); + BT_LE_ASSERT(rsp); cig_id = buf->data[3]; cis_count = buf->data[17]; @@ -278,8 +278,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp for (uint8_t i = 0; i < rsp_cis_count; i++) { set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2); } - assert(set_cig_params.cig_id == cig_id); - assert(set_cig_params.cis_count == cis_count); + BT_LE_ASSERT(set_cig_params.cig_id == cig_id); + BT_LE_ASSERT(set_cig_params.cis_count == cis_count); } #else /* USE_DIRECT_HCI */ struct ble_hci_le_cis_params_test *cis_params; @@ -302,7 +302,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp framing = buf->data[16]; cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test)); - assert(cis_params); + BT_LE_ASSERT(cis_params); for (size_t i = 0; i < cis_count; i++) { cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)]; @@ -335,8 +335,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp LOG_ERR("[B]SetCigParamsTestRspTimeout[%u]", cig_id); status = BTM_ERR_PROCESSING; } else { - assert(set_cig_params.cig_id == cig_id); - assert(set_cig_params.cis_count == cis_count); + BT_LE_ASSERT(set_cig_params.cig_id == cig_id); + BT_LE_ASSERT(set_cig_params.cis_count == cis_count); status = set_cig_params.status; if (status) { LOG_ERR("[B]SetCigParamsTestCtrlFail[%u][%02x]", cig_id, status); @@ -377,7 +377,7 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp) cis_count = buf->data[3]; cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_cis_hdls)); - assert(cis_params); + 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)); @@ -629,7 +629,7 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp) uint8_t big_handle; tBTM_STATUS status; - assert(rsp); + BT_LE_ASSERT(rsp); big_handle = buf->data[3]; @@ -661,7 +661,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp uint16_t conn_handle; tBTM_STATUS status; - assert(rsp); + BT_LE_ASSERT(rsp); conn_handle = sys_get_le16(buf->data + 3); @@ -721,7 +721,7 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs uint16_t conn_handle; tBTM_STATUS status; - assert(rsp); + BT_LE_ASSERT(rsp); conn_handle = sys_get_le16(buf->data + 3); @@ -859,7 +859,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.status = 0x00; ev.handle = params->btm_cis_disconnectd_evt.cis_handle; @@ -876,7 +876,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.status = iso_hci_status(params->btm_cis_established_evt.status); ev.conn_handle = params->btm_cis_established_evt.conn_handle; @@ -907,7 +907,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.acl_handle = params->btm_cis_request_evt.acl_handle; ev.cis_handle = params->btm_cis_request_evt.cis_handle; @@ -926,7 +926,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_cmpl.num_bis * 2; qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.status = iso_hci_status(params->btm_big_cmpl.status); ev.big_handle = params->btm_big_cmpl.big_handle; @@ -957,7 +957,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.big_handle = params->btm_big_term.big_handle; ev.reason = params->btm_big_term.reason; @@ -974,7 +974,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_sync_estab.num_bis * 2; qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.status = iso_hci_status(params->btm_big_sync_estab.status); ev.big_handle = params->btm_big_sync_estab.big_handle; @@ -1003,7 +1003,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.big_handle = params->btm_big_sync_lost.big_handle; ev.reason = params->btm_big_sync_lost.reason; @@ -1020,7 +1020,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa qdata_len = 2 + 1 + sizeof(ev); qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); ev.sync_handle = params->btm_biginfo_report.sync_handle; ev.num_bis = params->btm_biginfo_report.num_bis; @@ -1275,7 +1275,7 @@ int bt_le_bluedroid_iso_init(void) err = iso_enable_cis(); if (err) { /* Roll back local init so a retry doesn't trip k_sem_create's - * assert(handle == NULL). Reverse order of init, skipping the + * BT_LE_ASSERT(handle == NULL). Reverse order of init, skipping the * disable_cis step since enable never took effect. */ #if CONFIG_BT_ISO_RX ble_host_register_rx_iso_data_cb(NULL); diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gap.c b/components/bt/esp_ble_iso/host/adapter/nimble/gap.c index 295d0c31127..f7cd8c42c8d 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gap.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gap.c @@ -83,7 +83,7 @@ void bt_le_nimble_gap_post_event(void *param) int err; qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); memset(&desc, 0, sizeof(desc)); ev = param; @@ -117,7 +117,7 @@ void bt_le_nimble_gap_post_event(void *param) if (qev->ext_scan_recv.data_len) { qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len); - assert(qev->ext_scan_recv.data); + BT_LE_ASSERT(qev->ext_scan_recv.data); memcpy(qev->ext_scan_recv.data, ev->ext_disc.data, qev->ext_scan_recv.data_len); } @@ -172,7 +172,7 @@ void bt_le_nimble_gap_post_event(void *param) if (qev->pa_sync_recv.data_len) { qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len); - assert(qev->pa_sync_recv.data); + BT_LE_ASSERT(qev->pa_sync_recv.data); memcpy(qev->pa_sync_recv.data, ev->periodic_report.data, qev->pa_sync_recv.data_len); } @@ -184,7 +184,7 @@ void bt_le_nimble_gap_post_event(void *param) qev->acl_connect.status = ev->connect.status; if (qev->acl_connect.status == 0) { err = ble_gap_conn_find(ev->connect.conn_handle, &desc); - assert(err == 0); + BT_LE_ASSERT(err == 0); qev->acl_connect.conn_handle = desc.conn_handle; qev->acl_connect.role = desc.role; @@ -206,7 +206,7 @@ void bt_le_nimble_gap_post_event(void *param) qev->security_change.status = ev->enc_change.status; if (qev->security_change.status == 0) { err = ble_gap_conn_find(ev->enc_change.conn_handle, &desc); - assert(err == 0); + BT_LE_ASSERT(err == 0); qev->security_change.conn_handle = ev->enc_change.conn_handle; qev->security_change.role = desc.role; @@ -325,9 +325,15 @@ int bt_le_nimble_scan_stop(void) LOG_DBG("[N]ScanStop"); rc = ble_gap_disc_cancel(); - if (rc) { + if (rc && rc != BLE_HS_EALREADY) { LOG_ERR("[N]ScanStopFail[%d]", rc); } + /* EALREADY (not scanning, e.g. after privacy preemption): treat as success + * so bt_le_scan_stop clears a stale BT_DEV_SCANNING instead of locking out. */ + if (rc == BLE_HS_EALREADY) { + rc = 0; + } + return nimble_err_to_errno(rc); } diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.c b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.c index 5056b7eebd9..116d832bec7 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.c @@ -45,7 +45,7 @@ void bt_le_nimble_gatt_post_event(void *param) } qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); switch (ev->type) { case BLE_GAP_EVENT_MTU: @@ -79,7 +79,7 @@ void bt_le_nimble_gatt_post_event(void *param) qev->gattc_notify_rx.len = total_len; qev->gattc_notify_rx.value = bt_le_ext_calloc(1, total_len); - assert(qev->gattc_notify_rx.value); + BT_LE_ASSERT(qev->gattc_notify_rx.value); os_mbuf_copydata(ev->notify_rx.om, 0, total_len, qev->gattc_notify_rx.value); } @@ -129,7 +129,7 @@ int bt_le_nimble_gatt_post_disc_event(uint16_t conn_handle, ble_uuid16_t *uuid, } qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_DISCOVER_EVENT; @@ -156,7 +156,7 @@ int bt_le_nimble_gatt_post_disc_cmpl_event(uint16_t conn_handle, uint8_t status) LOG_DBG("[N]GattcDiscCmplEvtHdlr[%u][%02x]", conn_handle, status); qev = bt_le_ext_calloc(1, sizeof(*qev)); - assert(qev); + BT_LE_ASSERT(qev); qev->type = BT_LE_GATTC_DISC_CMPL_EVENT; @@ -299,7 +299,7 @@ void bt_le_nimble_gatt_handle_event(uint8_t *data, size_t data_len) break; default: - assert(0); + BT_LE_ASSERT(0); break; } @@ -317,16 +317,19 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_ void *buf, uint16_t buf_len, uint16_t offset, const void *value, uint16_t value_len) { - uint16_t len; + ssize_t len; if (buf_len == UINT16_MAX) { /* TODO: A better solution for using a variable for the cb pointer */ struct bt_le_nimble_gatt_read_cb *cb = buf; - assert(cb->read_cb); + BT_LE_ASSERT(cb->read_cb); len = cb->read_cb(cb->read_arg, offset, value, value_len); + if (len < 0) { + return len; + } } else { - assert(attr); + BT_LE_ASSERT(attr); LOG_DBG("[N]Hdl[%u]", attr->handle); @@ -339,7 +342,7 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_ memcpy(buf, (uint8_t *)value + offset, len); } - LOG_DBG("[N]GattsAttrRd[%u]", len); + LOG_DBG("[N]GattsAttrRd[%u]", (unsigned)len); return len; } @@ -375,7 +378,7 @@ static int gatts_notify(struct bt_conn *conn, struct bt_gatt_notify_params *para if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) { struct bt_gatt_chrc *chrc; - assert(data.attr->user_data); + BT_LE_ASSERT(data.attr->user_data); chrc = data.attr->user_data; if ((chrc->properties & BT_GATT_CHRC_NOTIFY) == 0) { @@ -486,7 +489,7 @@ int bt_le_nimble_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover_pa return bt_le_nimble_gattc_db_disc_all_chrs(conn, params); case BT_GATT_DISCOVER_DESCRIPTOR: - assert(params->uuid); + BT_LE_ASSERT(params->uuid); /* Only descriptors can be filtered */ if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 || diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.db.c b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.db.c index 4ef5d898c55..36d88c7ba76 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.db.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.db.c @@ -157,7 +157,7 @@ static void gattc_db_del(struct gattc_db *adb) struct gattc_db_svc *asvc; sys_snode_t *node; - assert(adb); + BT_LE_ASSERT(adb); while ((node = sys_slist_get(&adb->svc_list)) != NULL) { asvc = SYS_SLIST_CONTAINER(node, asvc, node); @@ -182,7 +182,7 @@ static void gattc_db_svc_insert(struct gattc_db *adb, const struct ble_gatt_svc struct gattc_db_svc *asvc; asvc = bt_le_ext_calloc(1, sizeof(*asvc)); - assert(asvc); + BT_LE_ASSERT(asvc); memcpy(&asvc->svc, svc, sizeof(asvc->svc)); sys_slist_init(&asvc->inc_svc_list); @@ -198,7 +198,7 @@ static void gattc_db_inc_svc_insert(struct gattc_db_svc *asvc, const struct ble_ struct gattc_db_inc_svc *ainc_svc; ainc_svc = bt_le_ext_calloc(1, sizeof(*ainc_svc)); - assert(ainc_svc); + BT_LE_ASSERT(ainc_svc); memcpy(&ainc_svc->svc, inc_svc, sizeof(ainc_svc->svc)); sys_slist_init(&ainc_svc->chrc_list); @@ -220,7 +220,7 @@ static void gattc_db_chrc_insert(sys_slist_t *chrc_list, const struct ble_gatt_c struct gattc_db_chrc *achrc; achrc = bt_le_ext_calloc(1, sizeof(*achrc)); - assert(achrc); + BT_LE_ASSERT(achrc); memcpy(&achrc->chrc, chrc, sizeof(achrc->chrc)); @@ -238,7 +238,10 @@ static void gattc_db_dsc_cccd_store(sys_slist_t *chrc_list, /* LOG_DBG("[N]GattcDbDscCccdStore[%u]", chr_val_handle); */ SYS_SLIST_FOR_EACH_CONTAINER(chrc_list, achrc, node) { - if (achrc->chrc.val_handle == dsc->handle - 1) { + /* Match by the char value handle NimBLE reports this descriptor belongs + * to, not dsc->handle-1: a descriptor between value and CCCD would break + * the offset assumption and leave CCCD unstored. */ + if (achrc->chrc.val_handle == chr_val_handle) { if (achrc->cccd.handle) { LOG_WRN("[N]GattcDbCccAlreadyUpd[%u][%u]", chr_val_handle, achrc->cccd.handle); return; @@ -582,12 +585,12 @@ static int gattc_db_disc_all_svcs_cb_safe(uint16_t conn_handle, bt_le_host_lock(); adb = arg; - assert(adb); - assert(adb->conn_handle == conn_handle); + BT_LE_ASSERT(adb); + BT_LE_ASSERT(adb->conn_handle == conn_handle); switch (error->status) { case 0: - assert(svc); + BT_LE_ASSERT(svc); LOG_DBG("[N]GattcDbDiscAllSvcs[0x%04x][%u][%u]", svc->uuid.u16.value, @@ -628,7 +631,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle, int rc = 0; asvc = arg; - assert(asvc); + BT_LE_ASSERT(asvc); /* LOG_DBG("[N]GattcDbFindIncSvcsCb[%u][%04x]", conn_handle, error->status); */ @@ -643,7 +646,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(svc); + BT_LE_ASSERT(svc); LOG_DBG("[N]GattcDbFindIncSvcs[0x%04x][%u][%u]", svc->uuid.u16.value, @@ -716,7 +719,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle, int rc = 0; ainc_svc = arg; - assert(ainc_svc); + BT_LE_ASSERT(ainc_svc); /* LOG_DBG("[N]GattcDbDiscAllIncChrsCb[%u][%04x]", conn_handle, error->status); */ @@ -731,7 +734,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(chrc); + BT_LE_ASSERT(chrc); LOG_DBG("[N]GattcDbDiscAllIncChrs[%u][%u]", chrc->def_handle, chrc->val_handle); @@ -774,7 +777,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle, int rc = 0; asvc = arg; - assert(asvc); + BT_LE_ASSERT(asvc); /* LOG_DBG("[N]GattcDbDiscAllChrsCb[%u][%04x]", conn_handle, error->status); */ @@ -789,7 +792,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(chrc); + BT_LE_ASSERT(chrc); LOG_DBG("[N]GattcDbDiscAllChrs[%u][%u]", chrc->def_handle, chrc->val_handle); @@ -833,7 +836,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle, int rc = 0; ainc_svc = arg; - assert(ainc_svc); + BT_LE_ASSERT(ainc_svc); LOG_DBG("[N]GattcDbDiscAllIncDscsCb[%u][%04x][%u]", conn_handle, error->status, chr_val_handle); @@ -849,7 +852,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(dsc); + BT_LE_ASSERT(dsc); if (dsc->uuid.u.type == BLE_UUID_TYPE_16 && dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) { @@ -896,7 +899,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle, int rc = 0; asvc = arg; - assert(asvc); + BT_LE_ASSERT(asvc); /* LOG_DBG("[N]GattcDbDiscAllDscsCb[%u][%04x][%u]", */ /* conn_handle, error->status, chr_val_handle); */ @@ -912,7 +915,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(dsc); + BT_LE_ASSERT(dsc); if (dsc->uuid.u.type == BLE_UUID_TYPE_16 && dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) { @@ -958,7 +961,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle, int rc = 0; achrc = arg; - assert(achrc); + BT_LE_ASSERT(achrc); /* LOG_DBG("[N]GattcDbEnableNtfCb[%u][%04x]", conn_handle, error->status); */ @@ -973,7 +976,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(attr); + BT_LE_ASSERT(attr); LOG_DBG("[N]GattcDbEnableNotify[%u]", attr->handle); break; @@ -1135,13 +1138,18 @@ static uint8_t gattc_disc_chr_each(struct bt_conn *conn, chrc_uuid.val = achrc->chrc.uuid.u16.value; break; default: - return BT_GATT_ITER_STOP; + /* Skip unsupported UUID types (e.g. 128-bit custom) instead of aborting + * the whole discovery; LE Audio only uses 16-bit characteristics. */ + LOG_WRN("[N]GattcDbChrcSkipUuid[%u][%u][%u]", + conn->handle, achrc->chrc.uuid.u.type, achrc->chrc.val_handle); + return BT_GATT_ITER_CONTINUE; } chrc.uuid = &chrc_uuid.uuid; chrc.value_handle = achrc->chrc.val_handle; chrc.properties = achrc->chrc.properties; + attr.uuid = BT_UUID_GATT_CHRC; /* declaration attr UUID */ attr.user_data = &chrc; attr.handle = achrc->chrc.def_handle; @@ -1247,7 +1255,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event) goto end; } - assert(event->params); + BT_LE_ASSERT(event->params); switch (event->type) { case GATTC_DISC_TYPE_SVC_BY_UUID: @@ -1273,7 +1281,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event) break; default: - assert(0); + BT_LE_ASSERT(0); break; } @@ -1344,7 +1352,7 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn, */ sub_params = params->sub_params; - assert(sub_params); + BT_LE_ASSERT(sub_params); LOG_DBG("[N]GattcDbHdlDiscAllDscs[%u][%u]", conn->handle, sub_params->value_handle); @@ -1394,6 +1402,12 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn, } end: + if (found == NULL && rc == 0) { + /* Char missing, or char found with cccd.handle==0 — not success. + * Returning 0 here made bt_gatt_subscribe() append a dead node. */ + rc = -ENODEV; + } + params->func(conn, found, params); return rc; diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c index a690b7aae10..cd78c2aa300 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c @@ -101,11 +101,11 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle, int rc = 0; read_params = arg; - assert(read_params); - assert(read_params->func); - assert(read_params->handle_count == 0); - assert(read_params->by_uuid.uuid); - assert(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16); + BT_LE_ASSERT(read_params); + BT_LE_ASSERT(read_params->func); + BT_LE_ASSERT(read_params->handle_count == 0); + BT_LE_ASSERT(read_params->by_uuid.uuid); + BT_LE_ASSERT(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16); LOG_DBG("[N]GattcNrpRdByUuidCb[%u][%04x][%s]", conn_handle, error->status, bt_uuid_str(read_params->by_uuid.uuid)); @@ -123,19 +123,19 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle, * 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); + BT_LE_ASSERT(nrp); node = sys_slist_peek_head(&nrp->list); - assert(node); + BT_LE_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); + BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_BY_UUID); + BT_LE_ASSERT(nrp_node->read_by_uuid.params == read_params); iter_stopped = nrp_node->read_by_uuid.iter_stopped; switch (error->status) { case 0: - assert(attr); - assert(attr->om); + BT_LE_ASSERT(attr); + BT_LE_ASSERT(attr->om); LOG_DBG("[N]GattcNrpHdl[%u][%u][%u]Len[%u]", attr->handle, read_params->by_uuid.start_handle, @@ -198,10 +198,10 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle, int rc = 0; read_params = arg; - assert(read_params); - assert(read_params->func); - assert(read_params->handle_count == 1); - assert(read_params->single.offset != 0); + BT_LE_ASSERT(read_params); + BT_LE_ASSERT(read_params->func); + BT_LE_ASSERT(read_params->handle_count == 1); + BT_LE_ASSERT(read_params->single.offset != 0); LOG_DBG("[N]GattcNrpRdLongCb[%u][%04x]", conn_handle, error->status); @@ -217,19 +217,19 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle, /* 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); + BT_LE_ASSERT(nrp); node = sys_slist_peek_head(&nrp->list); - assert(node); + BT_LE_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); + BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_LONG); + BT_LE_ASSERT(nrp_node->read_long.params == read_params); iter_stopped = nrp_node->read_long.iter_stopped; switch (error->status) { case 0: - assert(attr); - assert(attr->om); + BT_LE_ASSERT(attr); + BT_LE_ASSERT(attr->om); LOG_DBG("[N]GattcNrpHdl[%u][%u]Offset[%u]Len[%u]", attr->handle, read_params->single.handle, attr->offset, attr->om->om_len); @@ -289,9 +289,9 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle, original = nrp_node->read_single.params; func = params_copy->func; - assert(func); - assert(params_copy->handle_count == 1); - assert(params_copy->single.offset == 0); + BT_LE_ASSERT(func); + BT_LE_ASSERT(params_copy->handle_count == 1); + BT_LE_ASSERT(params_copy->single.offset == 0); LOG_DBG("[N]GattcNrpRdSingleCb[%u][%04x]", conn_handle, error->status); @@ -309,8 +309,8 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(attr); - assert(attr->om); + BT_LE_ASSERT(attr); + BT_LE_ASSERT(attr->om); LOG_DBG("[N]GattcNrpHdl[%u][%u]Len[%u]", attr->handle, original->single.handle, attr->om->om_len); @@ -405,8 +405,8 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle, int rc = 0; write_params = arg; - assert(write_params); - assert(write_params->func); + BT_LE_ASSERT(write_params); + BT_LE_ASSERT(write_params->func); LOG_DBG("[N]GattcNrpWrCb[%u][%04x]", conn_handle, error->status); @@ -424,7 +424,7 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(attr); + BT_LE_ASSERT(attr); LOG_DBG("[N]GattcNrpHdl[%u][%u]", attr->handle, write_params->handle); write_params->func(conn, 0, write_params); @@ -483,7 +483,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle, int rc = 0; sub_params = arg; - assert(sub_params); + BT_LE_ASSERT(sub_params); LOG_DBG("[N]GattcNrpSubCb[%u][%04x]", conn_handle, error->status); @@ -501,7 +501,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle, switch (error->status) { case 0: - assert(attr); + BT_LE_ASSERT(attr); LOG_DBG("[N]GattcNrpHdl[%u]", attr->handle); #if 0 @@ -561,7 +561,7 @@ static int gattc_nrp_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_pa void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle, int16_t attr_handle, - uint8_t status) + int status) { struct bt_gatt_indicate_params params = {0}; struct bt_gatt_attr attr = {0}; @@ -604,13 +604,13 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para struct os_mbuf *om = NULL; int rc; - assert(conn && params); - assert(params->attr); + BT_LE_ASSERT(conn && params); + BT_LE_ASSERT(params->attr); /* 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); + BT_LE_ASSERT(params->len == 0 || params->data != NULL); data.attr = params->attr; data.handle = bt_gatt_attr_get_handle(data.attr); @@ -635,7 +635,7 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) { struct bt_gatt_chrc *chrc; - assert(data.attr->user_data); + BT_LE_ASSERT(data.attr->user_data); chrc = data.attr->user_data; if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) { @@ -694,7 +694,7 @@ static void gatt_nrp_del(struct gatt_nrp *nrp) struct gatt_nrp_node *nrp_head; sys_snode_t *node; - assert(nrp); + BT_LE_ASSERT(nrp); while (1) { node = sys_slist_get(&nrp->list); @@ -735,7 +735,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params) } nrp_node = bt_le_ext_calloc(1, sizeof(*nrp_node)); - assert(nrp_node); + BT_LE_ASSERT(nrp_node); nrp_node->type = type; @@ -755,7 +755,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params) nrp_node->write_req.params = wp; nrp_node->write_req.data_copy = NULL; if (wp->length > 0) { - assert(wp->data); + BT_LE_ASSERT(wp->data); nrp_node->write_req.data_copy = bt_le_ext_malloc(wp->length); if (nrp_node->write_req.data_copy == NULL) { LOG_ERR("[N]GattNrpWrDataAllocFail[%u]", wp->length); @@ -775,7 +775,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params) nrp_node->indicate.params_copy = *ip; nrp_node->indicate.data_copy = NULL; if (ip->len > 0) { - assert(ip->data); + BT_LE_ASSERT(ip->data); nrp_node->indicate.data_copy = bt_le_ext_malloc(ip->len); if (nrp_node->indicate.data_copy == NULL) { LOG_ERR("[N]GattNrpIndDataAllocFail[%u]", ip->len); @@ -789,7 +789,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params) break; } default: - assert(0); + BT_LE_ASSERT(0); } if (sys_slist_is_empty(&nrp->list)) { @@ -889,31 +889,31 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param /* Fetch and remove the first node from list */ node = sys_slist_get(&nrp->list); - assert(node); + BT_LE_ASSERT(node); nrp_head = CONTAINER_OF(node, struct gatt_nrp_node, node); - assert(nrp_head->type == type); + BT_LE_ASSERT(nrp_head->type == type); switch (type) { case GATTC_NRP_READ_BY_UUID: - assert(nrp_head->read_by_uuid.params == params); + BT_LE_ASSERT(nrp_head->read_by_uuid.params == params); break; case GATTC_NRP_READ_LONG: - assert(nrp_head->read_long.params == params); + BT_LE_ASSERT(nrp_head->read_long.params == params); break; case GATTC_NRP_READ_SINGLE: - assert(nrp_head->read_single.params == params); + BT_LE_ASSERT(nrp_head->read_single.params == params); break; case GATTC_NRP_WRITE_REQ: - assert(nrp_head->write_req.params == params); + BT_LE_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); + BT_LE_ASSERT(nrp_head->subscribe.params == params); break; case GATTS_NRP_INDICATE: - assert(nrp_head->indicate.params); + BT_LE_ASSERT(nrp_head->indicate.params); /* 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). @@ -937,7 +937,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param nrp_head->indicate.data_copy = NULL; break; default: - assert(0); + BT_LE_ASSERT(0); } LOG_DBG("[N]GattNrpFree[%p]", nrp_head); @@ -958,7 +958,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param if (rc) { LOG_ERR("[N]GattcNrpRdByUuidFail[%d]", rc); - assert(nrp_head->read_by_uuid.params->func); + BT_LE_ASSERT(nrp_head->read_by_uuid.params->func); nrp_head->read_by_uuid.params->func(conn, rc, nrp_head->read_by_uuid.params, NULL, 0); } } else if (nrp_head->type == GATTC_NRP_READ_LONG) { @@ -966,7 +966,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param if (rc) { LOG_ERR("[N]GattcNrpRdLongFail[%d]", rc); - assert(nrp_head->read_long.params->func); + BT_LE_ASSERT(nrp_head->read_long.params->func); nrp_head->read_long.params->func(conn, rc, nrp_head->read_long.params, NULL, 0); } } else if (nrp_head->type == GATTC_NRP_READ_SINGLE) { @@ -974,7 +974,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param if (rc) { LOG_ERR("[N]GattcNrpRdSingleFail[%d]", rc); - assert(nrp_head->read_single.params_copy.func); + BT_LE_ASSERT(nrp_head->read_single.params_copy.func); nrp_head->read_single.params_copy.func(conn, rc, nrp_head->read_single.params, NULL, 0); } @@ -985,7 +985,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param if (rc) { LOG_ERR("[N]GattcNrpWrFail[%d]", rc); - assert(nrp_head->write_req.params->func); + BT_LE_ASSERT(nrp_head->write_req.params->func); nrp_head->write_req.params->func(conn, rc, nrp_head->write_req.params); } } else if (nrp_head->type == GATTC_NRP_SUBSCRIBE) { @@ -998,7 +998,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param if (rc) { LOG_ERR("[N]GattsNrpIndFail[%d]", rc); - assert(nrp_head->indicate.params->func); + BT_LE_ASSERT(nrp_head->indicate.params->func); nrp_head->indicate.params->func(conn, nrp_head->indicate.params, rc); } } diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/include/nimble/gatt.h b/components/bt/esp_ble_iso/host/adapter/nimble/include/nimble/gatt.h index 9f189238422..5542ae3f6c2 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/include/nimble/gatt.h +++ b/components/bt/esp_ble_iso/host/adapter/nimble/include/nimble/gatt.h @@ -8,13 +8,14 @@ #define HOST_NIMBLE_GATT_H_ #include -#include #include #include "host/ble_uuid.h" #include "host/ble_gatt.h" +#include "utils/assert.h" + #ifdef __cplusplus extern "C" { #endif @@ -35,7 +36,7 @@ static inline uint8_t BT_LE_NIMBLE_GATT_UUID_TO_Z(uint8_t type) } else if (type == BLE_UUID_TYPE_128) { return BT_UUID_TYPE_128; } else { - assert(0); + BT_LE_ASSERT(0); return BT_UUID_TYPE_16; } } @@ -113,7 +114,7 @@ enum { void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle, int16_t attr_handle, - uint8_t status); + int status); int bt_le_nimble_gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params); diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/iso.c b/components/bt/esp_ble_iso/host/adapter/nimble/iso.c index 78bbe740ea2..0fe7a73f302 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/iso.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/iso.c @@ -37,9 +37,9 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp) uint16_t conn_handle; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 5); + BT_LE_ASSERT(buf->len >= 5); conn_handle = sys_get_le16(buf->data + 3); @@ -87,9 +87,9 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) uint8_t *rp; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 18); + BT_LE_ASSERT(buf->len >= 18); cig_id = buf->data[3]; sdu_interval_c_to_p = sys_get_le24(buf->data + 4); @@ -101,17 +101,17 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp) mtl_p_to_c = sys_get_le16(buf->data + 15); cis_count = buf->data[17]; - assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params)); + BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params)); /* The cis_count can be 0 */ if (cis_count) { cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params)); - assert(cis_params); + BT_LE_ASSERT(cis_params); } rp_len = sizeof(struct ble_hci_le_set_cig_params_rp) + cis_count * 2; rp = bt_le_ext_calloc(1, rp_len); - assert(rp); + BT_LE_ASSERT(rp); for (size_t i = 0; i < cis_count; i++) { cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)]; @@ -180,9 +180,9 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp uint8_t *rp; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 18); + BT_LE_ASSERT(buf->len >= 18); cig_id = buf->data[3]; sdu_interval_c_to_p = sys_get_le24(buf->data + 4); @@ -195,17 +195,17 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp framing = buf->data[16]; cis_count = buf->data[17]; - assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test)); + BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test)); /* The cis_count can be 0 */ if (cis_count) { cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test)); - assert(cis_params); + BT_LE_ASSERT(cis_params); } rp_len = sizeof(struct ble_hci_le_set_cig_params_test_rp) + cis_count * 2; rp = bt_le_ext_calloc(1, rp_len); - assert(rp); + BT_LE_ASSERT(rp); for (size_t i = 0; i < cis_count; i++) { cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)]; @@ -269,15 +269,15 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 4); + BT_LE_ASSERT(buf->len >= 4); /* The cis_count shall be at least 1 */ cis_count = buf->data[3]; - assert(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params)); + BT_LE_ASSERT(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params)); cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_create_cis_params)); - assert(cis_params); + BT_LE_ASSERT(cis_params); for (size_t i = 0; i < cis_count; i++) { cis_params[i].cis_handle = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_le_create_cis_params)); @@ -306,7 +306,7 @@ static int hci_cmd_remove_cig(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 4); + BT_LE_ASSERT(buf->len >= 4); cig_id = buf->data[3]; @@ -331,7 +331,7 @@ static int hci_cmd_accept_cis_req(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 5); + BT_LE_ASSERT(buf->len >= 5); cis_handle = sys_get_le16(buf->data + 3); @@ -357,7 +357,7 @@ static int hci_cmd_reject_cis_req(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 6); + BT_LE_ASSERT(buf->len >= 6); cis_handle = sys_get_le16(buf->data + 3); reason = buf->data[5]; @@ -394,7 +394,7 @@ static int hci_cmd_create_big(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 34); + BT_LE_ASSERT(buf->len >= 34); big_handle = buf->data[3]; adv_handle = buf->data[4]; @@ -456,7 +456,7 @@ static int hci_cmd_create_big_test(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 39); + BT_LE_ASSERT(buf->len >= 39); big_handle = buf->data[3]; adv_handle = buf->data[4]; @@ -512,7 +512,7 @@ static int hci_cmd_terminate_big(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 5); + BT_LE_ASSERT(buf->len >= 5); big_handle = buf->data[3]; reason = buf->data[4]; @@ -545,7 +545,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp) ARG_UNUSED(rsp); - assert(buf->len >= 27); + BT_LE_ASSERT(buf->len >= 27); big_handle = buf->data[3]; sync_handle = sys_get_le16(buf->data + 4); @@ -556,7 +556,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp) num_bis = buf->data[26]; bis = buf->data + 27; - assert(buf->len >= 27 + num_bis); + BT_LE_ASSERT(buf->len >= 27 + num_bis); ble_hs_lock(); @@ -584,9 +584,9 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp) uint8_t big_handle; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 4); + BT_LE_ASSERT(buf->len >= 4); big_handle = buf->data[3]; @@ -625,9 +625,9 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp uint8_t *codec_cfg; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 16); + BT_LE_ASSERT(buf->len >= 16); conn_handle = sys_get_le16(buf->data + 3); data_path_direction = buf->data[5]; @@ -639,7 +639,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp codec_cfg_len = buf->data[15]; codec_cfg = buf->data + 16; - assert(buf->len >= 16 + codec_cfg_len); + BT_LE_ASSERT(buf->len >= 16 + codec_cfg_len); ble_hs_lock(); @@ -677,9 +677,9 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs uint16_t conn_handle; int status; - assert(rsp); + BT_LE_ASSERT(rsp); - assert(buf->len >= 6); + BT_LE_ASSERT(buf->len >= 6); conn_handle = sys_get_le16(buf->data + 3); data_path_direction = buf->data[5]; @@ -793,7 +793,7 @@ static void iso_evt_rx(uint8_t event, const void *data, qdata_len = len + 2; qdata = bt_le_ext_calloc(1, qdata_len); - assert(qdata); + BT_LE_ASSERT(qdata); qdata[0] = le_meta; qdata[1] = event; diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c b/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c deleted file mode 100644 index 64d7dd7dd82..00000000000 --- a/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include <../host/conn_internal.h> - -#include "host/ble_hs.h" -#include "host/ble_l2cap.h" -#include "host/ble_hs_mbuf.h" -#include "../../../nimble/host/src/ble_l2cap_priv.h" - -#include "common/host.h" - -#include "nimble/hs_error.h" - -LOG_MODULE_REGISTER(ISO_N2CAP, CONFIG_BT_ISO_LOG_LEVEL); - -_Static_assert(CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM && "At least one L2CAP coc shall be supported"); - -#define OTS_L2CAP_BUF_COUNT (3 * CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM) -#define OTS_L2CAP_MEM_SIZE OS_MEMPOOL_SIZE(OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2) - -static BT_ISO_EXT_RAM_BSS_ATTR os_membuf_t ots_mem[OTS_L2CAP_MEM_SIZE]; -static BT_ISO_EXT_RAM_BSS_ATTR struct os_mempool ots_mbuf_mempool; -static BT_ISO_EXT_RAM_BSS_ATTR struct os_mbuf_pool ots_mbuf_pool; - -static BT_ISO_EXT_RAM_BSS_ATTR struct ble_l2cap_chan *ots_chan; - -static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan) -{ - struct os_mbuf *sdu_rx; - int rc; - - LOG_DBG("[N]L2capOtsRecvReady"); - - sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); - if (sdu_rx == NULL) { - LOG_ERR("[N]L2capNoBufForL2capRecv"); - return -ENOMEM; - } - - rc = ble_l2cap_recv_ready(chan, sdu_rx); - if (rc) { - LOG_ERR("[N]L2capRecvFail[%d]", rc); - - os_mbuf_free_chain(sdu_rx); - return rc; - } - - return 0; -} - -static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg) -{ - struct ble_l2cap_chan_info chan_info; - uint16_t result = 0; - size_t sdu_len; - uint8_t *sdu; - int err; - - LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type); - - switch (event->type) { - case BLE_L2CAP_EVENT_COC_CONNECTED: - if (event->connect.status) { - LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status); - return 0; - } - - if (ots_chan) { - LOG_ERR("[N]L2capCocChanExist"); - return 0; - } - - ots_chan = event->connect.chan; - - if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) { - LOG_ERR("[N]L2capCocGetChanInfoFail"); - /* Roll back the latch so the next COC_CONNECTED isn't refused - * by the if (ots_chan) guard above. */ - ots_chan = NULL; - return -EIO; - } - - LOG_INF("[N]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]", - event->connect.conn_handle, chan_info.scid, chan_info.dcid, - chan_info.psm, chan_info.our_l2cap_mtu, chan_info.peer_l2cap_mtu, - chan_info.our_coc_mtu, chan_info.peer_coc_mtu); - - bt_le_l2cap_connected(event->connect.conn_handle, chan_info.psm, - chan_info.dcid, chan_info.peer_coc_mtu, - chan_info.scid, chan_info.our_coc_mtu); - return 0; - - case BLE_L2CAP_EVENT_COC_DISCONNECTED: - if (ots_chan != event->disconnect.chan) { - LOG_ERR("[N]L2capDisconnectInvCocChan"); - return 0; - } - - LOG_INF("[N]L2capCocDisconnect[%u][%04x]", - event->disconnect.conn_handle, event->disconnect.chan->psm); - - bt_le_l2cap_disconnected(event->disconnect.conn_handle, event->disconnect.chan->psm); - - ots_chan = NULL; - return 0; - - case BLE_L2CAP_EVENT_COC_ACCEPT: - /* Don't reject on peer_sdu_size: LE CoC allows asymmetric MTUs. It's the peer's - * RX MTU (our TX ceiling) — harmless; our RX is bounded by ble_l2cap_create_server. - * (PTS uses 1024 > our 256; rejecting broke SCP.) */ - - LOG_DBG("[N]L2capCocAccept[%u][%04x][%04x][%u][%u][%u][%u]", - event->accept.conn_handle, event->accept.chan->psm, - event->accept.chan->dcid, event->accept.chan->coc_tx.mtu, - event->accept.chan->peer_coc_mps, event->accept.peer_sdu_size, - event->accept.chan->coc_tx.credits); - - err = bt_le_l2cap_accept(event->accept.conn_handle, - event->accept.chan->psm, - event->accept.chan->dcid, - event->accept.chan->coc_tx.mtu, - event->accept.chan->peer_coc_mps, - event->accept.chan->coc_tx.credits, - &result); - if (err) { - return err; - } - - ARG_UNUSED(result); - - return ots_l2cap_recv_ready(event->accept.chan); - - case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: - if (ots_chan != event->receive.chan) { - LOG_ERR("[N]L2capRecvOnInvCocChan"); - return 0; - } - - assert(event->receive.sdu_rx); - - LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]", - event->receive.conn_handle, event->receive.chan->psm, - event->receive.sdu_rx->om_len); - - sdu_len = OS_MBUF_PKTLEN(event->receive.sdu_rx); - - sdu = bt_le_ext_calloc(1, sdu_len); - assert(sdu); - - err = os_mbuf_copydata(event->receive.sdu_rx, 0, sdu_len, sdu); - assert(err == 0); - - bt_le_l2cap_received(event->receive.conn_handle, - event->receive.chan->psm, - sdu, sdu_len); - - os_mbuf_free_chain(event->receive.sdu_rx); - free(sdu); - - return ots_l2cap_recv_ready(event->receive.chan); - - case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: - if (ots_chan != event->tx_unstalled.chan) { - LOG_ERR("[N]L2capTxUnstalledOnInvCocChan"); - return 0; - } - - LOG_WRN("[N]L2capCocTxUnstalled[%u][%d]", - event->tx_unstalled.conn_handle, event->tx_unstalled.status); - - /* TODO: transmit the remaining data */ - return 0; - - default: - return 0; - } -} - -int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle) -{ - struct os_mbuf *sdu_rx; - int rc; - - if (ots_chan) { - LOG_WRN("[N]L2capOtsChanExist"); - return -EALREADY; - } - - sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); - if (sdu_rx == NULL) { - LOG_ERR("[N]L2capNoBufForConnect"); - return -ENOMEM; - } - - rc = ble_l2cap_connect(conn_handle, L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, - sdu_rx, ots_l2cap_event_cb, NULL); - if (rc) { - LOG_ERR("[N]L2capConnectFail[%d]", rc); - - os_mbuf_free_chain(sdu_rx); - return nimble_err_to_errno(rc); - } - - return 0; -} - -int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) -{ - int rc; - - if (ots_chan == NULL) { - LOG_WRN("[N]L2capNoOtsChan"); - return -ENOTCONN; - } - - if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) { - LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]", - ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle); - return -EINVAL; - } - - rc = ble_l2cap_disconnect(ots_chan); - if (rc) { - LOG_ERR("[N]L2capDisconnectFail[%d]", rc); - return nimble_err_to_errno(rc); - } - - return 0; -} - -int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) -{ - struct os_mbuf *sdu_tx; - int rc; - - if (ots_chan == NULL) { - LOG_WRN("[N]L2capNoOtsChan"); - return -ENOTCONN; - } - - if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) { - LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]", - ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle); - return -EINVAL; - } - - sdu_tx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0); - if (sdu_tx == NULL) { - LOG_ERR("[N]L2capNoBufForSend"); - return -ENOMEM; - } - - rc = os_mbuf_append(sdu_tx, buf->data, buf->len); - if (rc) { - LOG_ERR("[N]L2capAppendBufFail[%d]", rc); - os_mbuf_free_chain(sdu_tx); - return -EIO; - } - - rc = ble_l2cap_send(ots_chan, sdu_tx); - if (rc) { - if (rc == BLE_HS_ESTALLED) { - /* sdu is queued in tx->sdus[0]; NimBLE will continue on - * BLE_L2CAP_EVENT_COC_TX_UNSTALLED. Do NOT free here. - */ - LOG_WRN("[N]L2capMoreCreditsForSend"); - } else if (rc == BLE_HS_EBADDATA || rc == BLE_HS_EBUSY) { - /* sdu was rejected before being queued; caller still owns it. */ - LOG_ERR("[N]L2capSendFail[%d]", rc); - os_mbuf_free_chain(sdu_tx); - } else { - /* Internal error inside continue_tx; NimBLE already freed sdu. */ - LOG_ERR("[N]L2capSendInternalFail[%d]", rc); - } - - return nimble_err_to_errno(rc); - } - - /* Payload copied into sdu_tx; consume buf here (no async TX-done to unref it), - * else the 1-buffer ot_chan_tx_pool leaks. Failure paths leave buf to caller. */ - net_buf_unref(buf); - - return 0; -} - -int bt_le_nimble_l2cap_init(void) -{ - int rc; - - rc = os_mempool_init(&ots_mbuf_mempool, OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2, ots_mem, "ots_pool"); - if (rc) { - LOG_ERR("[N]L2capInitOtsMempoolFail[%d]", rc); - return rc; - } - - rc = os_mbuf_pool_init(&ots_mbuf_pool, &ots_mbuf_mempool, L2CAP_LE_OTS_MTU, OTS_L2CAP_BUF_COUNT); - if (rc) { - LOG_ERR("[N]L2capInitOtsMbufPoolFail[%d]", rc); - return rc; - } - -#if CONFIG_BT_OTS - rc = ble_l2cap_create_server(L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, ots_l2cap_event_cb, NULL); - if (rc) { - LOG_ERR("[N]L2capCreateL2capSrvFail[%d]", rc); - return rc; - } -#endif /* CONFIG_BT_OTS */ - - return 0; -} - -void bt_le_nimble_l2cap_deinit(void) -{ - LOG_DBG("[N]L2capDeinit"); - - /* TODO: free the ots_mbuf_pool and ots_mbuf_mempool */ - -#if CONFIG_BT_OTS - /* TODO: destroy the server */ -#endif /* CONFIG_BT_OTS */ -} diff --git a/components/bt/esp_ble_iso/host/common/app/gap.c b/components/bt/esp_ble_iso/host/common/app/gap.c index 607bcb5da46..3a5fa3c0e5a 100644 --- a/components/bt/esp_ble_iso/host/common/app/gap.c +++ b/components/bt/esp_ble_iso/host/common/app/gap.c @@ -175,7 +175,7 @@ static void handle_pa_sync_event_safe(struct bt_le_gap_app_param *param) } err = bt_le_per_adv_sync_establish_listener(event.pa_sync.sync_handle); - assert(err == 0); + BT_LE_ASSERT(err == 0); end: bt_le_host_unlock(); @@ -203,6 +203,29 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param) bt_le_host_lock(); +#if CONFIG_BT_BLUEDROID_ENABLED + /* See handle_security_change_event_safe: the producer runs on BTU and cannot + * read gatt_conns[], so it sends src_addr and the handle is resolved here. + * HCI identifies the PAST sender by handle and gives no address for it, so + * btu_hcif recovers the BDA from the LCB without its type - match on value. */ + { + struct gatt_conn *gatt_conn; + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, + param->pa_sync_past.src_addr.val, + true); + if (gatt_conn == NULL) { + /* ACL disconnected between PAST enqueue and host processing (scan.c + * names this race). Keep the sync connection-less, like the non-PAST + * path and NimBLE — synced cb fires with conn=NULL. */ + LOG_WRN("GapPastUnknownSrc"); + event.pa_sync_past.conn_handle = BT_CONN_HANDLE_INVALID; + } else { + event.pa_sync_past.conn_handle = gatt_conn->conn_handle; + } + } +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ + if (event.pa_sync_past.status) { goto end; } @@ -220,7 +243,7 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param) } err = bt_le_per_adv_sync_establish_listener(event.pa_sync_past.sync_handle); - assert(err == 0); + BT_LE_ASSERT(err == 0); end: bt_le_host_unlock(); @@ -246,7 +269,7 @@ static void handle_pa_sync_lost_event_safe(struct bt_le_gap_app_param *param) } err = bt_le_per_adv_sync_delete(event.pa_sync_lost.sync_handle); - assert(err == 0); + BT_LE_ASSERT(err == 0); end: bt_le_host_unlock(); @@ -321,7 +344,7 @@ static void handle_acl_connect_event_safe(struct bt_le_gap_app_param *param) } err = bt_le_acl_conn_connected_listener(event.acl_connect.conn_handle); - assert(err == 0); + BT_LE_ASSERT(err == 0); end: bt_le_host_unlock(); @@ -348,7 +371,7 @@ static void handle_acl_disconnect_event_safe(struct bt_le_gap_app_param *param) } err = bt_le_acl_conn_delete(event.acl_disconnect.conn_handle); - assert(err == 0); + BT_LE_ASSERT(err == 0); end: bt_le_host_unlock(); @@ -378,6 +401,27 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param) bt_le_host_lock(); +#if CONFIG_BT_BLUEDROID_ENABLED + /* Bluedroid's AUTH_CMPL arrives on BTC, which carries no conn_handle and may + * not read gatt_conns[] (iso_task mutates it under this lock). So the producer + * sends dst only and the identity is resolved here instead. NimBLE gets both + * from its own event and fills them at post time. */ + { + struct gatt_conn *gatt_conn; + + gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event.security_change.dst.type, + event.security_change.dst.val, + false); + if (gatt_conn == NULL) { + LOG_ERR("GapSecChgUnknownDev"); + goto end; + } + + event.security_change.conn_handle = gatt_conn->conn_handle; + event.security_change.role = gatt_conn->role; + } +#endif /* CONFIG_BT_BLUEDROID_ENABLED */ + if (event.security_change.status) { goto end; } @@ -393,11 +437,16 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param) if (err) { goto end; } + + /* Created already encrypted — mirror update()'s *encrypted out-param. */ + if (event.security_change.sec_level > BT_SECURITY_L1) { + encrypted = true; + } } else { err = bt_le_acl_conn_update(event.security_change.conn_handle, event.security_change.sec_level, &encrypted); - assert(err == 0); + BT_LE_ASSERT(err == 0); } /* Point conn->le.keys at the bonded LTK the adapter captured, so the lib's @@ -413,12 +462,11 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param) err = bt_le_acl_conn_security_changed_listener(event.security_change.conn_handle, event.security_change.sec_level); - assert(err == 0); + BT_LE_ASSERT(err == 0); if (encrypted) { /* TODO: check if bonded or not */ - err = bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true); - assert(err == 0); + (void)bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true); } end: @@ -483,7 +531,7 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len) { struct bt_le_gap_app_param *param; - assert(data && data_len); + BT_LE_ASSERT(data && data_len); param = (struct bt_le_gap_app_param *)data; @@ -519,14 +567,14 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len) handle_bond_delete_event_safe(param); break; default: - assert(0); + BT_LE_ASSERT(0); break; } free(data); } -void bt_le_gap_app_post_event(uint8_t type, void *param) +void bt_le_gap_app_post_event(uint16_t type, void *param) { #if CONFIG_BT_BLUEDROID_ENABLED /* For Bluedroid, post the typed event to the ISO task instead. */ diff --git a/components/bt/esp_ble_iso/host/common/conn.c b/components/bt/esp_ble_iso/host/common/conn.c index c616ab3cdc8..41419465364 100644 --- a/components/bt/esp_ble_iso/host/common/conn.c +++ b/components/bt/esp_ble_iso/host/common/conn.c @@ -12,6 +12,7 @@ #include #include +#include #include #include <../host/keys.h> @@ -53,9 +54,10 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id, ARG_UNUSED(id); - assert(conn && peer); + BT_LE_ASSERT(conn && peer); + + /* TODO(privacy): also match init/resp or resolved identity; dst may stay RPA. */ - /* Check against conn dst address as it may be the identity address */ if (bt_addr_le_eq(peer, &conn->le.dst)) { return true; } @@ -63,24 +65,12 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id, return false; } -_LIB_IDF -struct bt_conn *bt_conn_ref(struct bt_conn *conn) -{ - return conn; -} - -_LIB_IDF -void bt_conn_unref(struct bt_conn *conn) -{ - ARG_UNUSED(conn); -} - _LIB_ONLY void bt_conn_foreach(enum bt_conn_type type, void (*func)(struct bt_conn *conn, void *data), void *data) { - assert(type == BT_CONN_TYPE_LE); + BT_LE_ASSERT(type == BT_CONN_TYPE_LE); for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) { struct bt_conn *conn = &acl_conns[i]; @@ -95,7 +85,7 @@ void bt_conn_foreach(enum bt_conn_type type, _LIB_ONLY const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn) { - assert(conn); + BT_LE_ASSERT(conn); return &conn->le.dst; } @@ -105,19 +95,19 @@ uint8_t bt_conn_index(const struct bt_conn *conn) { ptrdiff_t index = 0; - assert(conn); + BT_LE_ASSERT(conn); switch (conn->type) { case BT_CONN_TYPE_LE: index = conn - acl_conns; - assert(index >= 0 && index < ARRAY_SIZE(acl_conns)); + BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(acl_conns)); break; case BT_CONN_TYPE_ISO: index = conn - iso_conns; - assert(index >= 0 && index < ARRAY_SIZE(iso_conns)); + BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(iso_conns)); break; default: - assert(0); + BT_LE_ASSERT(0); break; } @@ -126,19 +116,6 @@ uint8_t bt_conn_index(const struct bt_conn *conn) return (uint8_t)index; } -#if CONFIG_BT_SMP -_IDF_ONLY -int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec) -{ - /* This function is used by CIS to set security level, - * currently always return failure here to make sure - * that before creating CIS, the ACL connection is - * encrypted. - */ - return -ENOTSUP; -} -#endif /* CONFIG_BT_SMP */ - static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state) { switch (state) { @@ -152,7 +129,7 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state) case BT_CONN_DISCONNECTING: return BT_CONN_STATE_DISCONNECTING; default: - assert(0); + BT_LE_ASSERT(0); return 0; } } @@ -160,7 +137,10 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state) _LIB_ONLY int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info) { - assert(conn && info); + BT_LE_ASSERT(conn && info); + + /* Callers today only pass ACL (BT_CONN_TYPE_LE). ISO would need + * info->le.* from conn->iso.acl (union); not implemented until needed. */ info->type = conn->type; info->role = conn->role; @@ -218,7 +198,6 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb) return err; } -_NOT_USED int bt_conn_cb_unregister(struct bt_conn_cb *cb) { LOG_DBG("ConnCbUnreg"); @@ -236,6 +215,16 @@ int bt_conn_cb_unregister(struct bt_conn_cb *cb) return 0; } +_IDF_ONLY +int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb) +{ + int err; + bt_le_host_lock(); + err = bt_conn_cb_unregister(cb); + bt_le_host_unlock(); + return err; +} + _LIB_ONLY int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb) { @@ -277,7 +266,7 @@ int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb) _IDF_ONLY struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size) { - assert(conns); + BT_LE_ASSERT(conns); for (size_t i = 0; i < size; i++) { struct bt_conn *conn = &conns[i]; @@ -322,7 +311,7 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type) { struct bt_conn *conn = NULL; - assert(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO); + BT_LE_ASSERT(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO); /* LOG_DBG("ConnLookupHdl[%u][%u]", handle, type); */ @@ -337,11 +326,20 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type) struct bt_conn *bt_conn_lookup_index(uint8_t index) { + struct bt_conn *conn; + if (index >= ARRAY_SIZE(acl_conns)) { return NULL; } - return bt_conn_ref(&acl_conns[index]); + conn = &acl_conns[index]; + + /* Free/wiped slots are type NONE after acl_conn_delete. */ + if (conn->type == BT_CONN_TYPE_NONE) { + return NULL; + } + + return conn; } _IDF_ONLY @@ -352,17 +350,6 @@ struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle) return bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE); } -_IDF_ONLY -struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle) -{ - struct bt_conn *conn = NULL; - /* LOG_DBG("AclConnFind[%u]", conn_handle); */ - bt_le_host_lock(); - conn = bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE); - bt_le_host_unlock(); - return conn; -} - _IDF_ONLY int bt_le_acl_conn_new(uint16_t conn_handle, uint8_t role, @@ -371,7 +358,7 @@ int bt_le_acl_conn_new(uint16_t conn_handle, { struct bt_conn *conn; - assert(dst); + BT_LE_ASSERT(dst); LOG_DBG("AclNew[%u][%u][%u][%s]", conn_handle, role, sec_level, bt_addr_le_str(dst)); @@ -383,6 +370,10 @@ int bt_le_acl_conn_new(uint16_t conn_handle, conn->role = role; conn->state = BT_CONN_CONNECTED; conn->sec_level = sec_level; + /* AUTH-before-ACL-slot path may create with L2+ directly. */ + if (sec_level > BT_SECURITY_L1) { + conn->encrypt = 1; + } memcpy(&conn->le.dst, dst, sizeof(conn->le.dst)); } else { LOG_ERR("NoFreeConn[%u]", conn_handle); @@ -418,6 +409,20 @@ void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk) LOG_INF("ConnSetLtk[%u][%s]", conn->handle, bt_hex(ltk, 16)); } +#if CONFIG_BT_ISO_UNICAST +/* Defer ACL slot wipe while a CIS still holds iso.acl. */ +static bool bt_iso_acl_has_cis(const struct bt_conn *acl) +{ + for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) { + if (iso_conns[i].type == BT_CONN_TYPE_ISO && iso_conns[i].iso.acl == acl) { + return true; + } + } + + return false; +} +#endif /* CONFIG_BT_ISO_UNICAST */ + _IDF_ONLY int bt_le_acl_conn_delete(uint16_t conn_handle) { @@ -432,6 +437,27 @@ int bt_le_acl_conn_delete(uint16_t conn_handle) return -ENOTCONN; } +#if CONFIG_BT_ISO_UNICAST + if (bt_iso_acl_has_cis(conn)) { + LOG_INF("AclConnDelDeferred[%u]", conn_handle); + return 0; + } +#endif /* CONFIG_BT_ISO_UNICAST */ + + /* OTS CoC (and any L2CAP) keep chan->conn on this list. Wipe without + * detaching leaves dangling pointers if ACL slot is reused. */ + { + struct bt_l2cap_chan *chan, *tmp; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&conn->channels, chan, tmp, node) { + sys_slist_find_and_remove(&conn->channels, &chan->node); + if (chan->ops != NULL && chan->ops->disconnected != NULL) { + chan->ops->disconnected(chan); + } + chan->conn = NULL; + } + } + /* Wipe this connection's LTK slot (key hygiene); the memset below then nulls * conn->le.keys. */ memset(&conn_ltk[conn - acl_conns], 0, sizeof(conn_ltk[0])); @@ -461,8 +487,8 @@ int bt_le_acl_conn_update(uint16_t conn_handle, } if (conn->sec_level < sec_level) { - /* No encryption to encryption */ - if (conn->encrypt == 0 && conn->sec_level == BT_SECURITY_L1) { + /* Mark encrypted on first step into L2+ (not only from L1). */ + if (conn->encrypt == 0 && sec_level > BT_SECURITY_L1) { conn->encrypt = 1; if (encrypted) { @@ -519,8 +545,8 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state) { bt_conn_state_t old_state; - assert(conn); - assert(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO); LOG_DBG("ConnSetState[%u][%u]", conn->state, state); @@ -556,7 +582,7 @@ static int iso_disconnect(struct bt_conn *conn, uint8_t reason) _IDF_ONLY int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) { - assert(conn); + BT_LE_ASSERT(conn); LOG_DBG("ConnDisconnect[%u][%u][%02x]", conn->state, conn->type, reason); @@ -567,10 +593,14 @@ int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) } if (conn->type == BT_CONN_TYPE_ISO) { - return iso_disconnect(conn, reason); + int err = iso_disconnect(conn, reason); + if (err == 0) { + bt_conn_set_state(conn, BT_CONN_DISCONNECTING); + } + return err; } - assert(0); + BT_LE_ASSERT(0); case BT_CONN_DISCONNECTING: return 0; case BT_CONN_DISCONNECTED: @@ -627,9 +657,8 @@ int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason) } } -#if CONFIG_BT_ISO_UNICAST + /* GATT sub/CCC cleanup is ACL-scoped, not ISO-unicast. */ bt_le_acl_conn_disconnected_gatt_listener(conn_handle); -#endif /* CONFIG_BT_ISO_UNICAST */ return 0; } @@ -676,6 +705,8 @@ int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle, return -ENOTCONN; } + /* TODO(privacy): copy identity into conn->le.dst once adapters post resolve. */ + SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) { if (listener->identity_resolved) { listener->identity_resolved(conn, rpa, identity); diff --git a/components/bt/esp_ble_iso/host/common/gatt.c b/components/bt/esp_ble_iso/host/common/gatt.c index 24e53f50e4c..30cc4c4564e 100644 --- a/components/bt/esp_ble_iso/host/common/gatt.c +++ b/components/bt/esp_ble_iso/host/common/gatt.c @@ -88,7 +88,7 @@ int bt_gatt_service_register(struct bt_gatt_service *svc) } uuid = svc->attrs[0].user_data; - assert(uuid); + BT_LE_ASSERT(uuid); LOG_DBG("GattSvc[%04x]", uuid->val); @@ -154,7 +154,7 @@ uint16_t bt_gatt_get_mtu(struct bt_conn *conn) { uint16_t mtu; - assert(conn); + BT_LE_ASSERT(conn); #if CONFIG_BT_BLUEDROID_ENABLED mtu = bt_le_bluedroid_gatt_get_mtu(conn); @@ -230,7 +230,7 @@ uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr) if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) { struct bt_gatt_chrc *chrc = attr->user_data; - assert(chrc); + BT_LE_ASSERT(chrc); handle = chrc->value_handle; if (handle == 0) { @@ -267,7 +267,7 @@ struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr) LOG_DBG("GattAttrNext"); - assert(attr); + BT_LE_ASSERT(attr); handle = bt_gatt_attr_get_handle(attr); bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next); @@ -359,7 +359,7 @@ static void foreach_attr_type_dyndb(uint16_t start_handle, for (size_t i = 0; i < svc->attr_count; i++) { struct bt_gatt_attr *attr = &svc->attrs[i]; - assert(attr->uuid); + BT_LE_ASSERT(attr->uuid); if (gatt_foreach_iter(attr, attr->handle, start_handle, @@ -448,7 +448,7 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t buf_len, uint16_t offset, const void *value, uint16_t value_len) { - assert(buf); + BT_LE_ASSERT(buf); LOG_DBG("GattAttrRd[%u][%u][%u]", buf_len, offset, value_len); @@ -471,7 +471,7 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params { int err; - assert(params); + BT_LE_ASSERT(params); LOG_DBG("GattNtfCb[%u]", params->len); @@ -486,10 +486,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params err = bt_le_nimble_gatts_notify(conn, params); #endif - /* 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) { + /* Sync notify: complete cb only when conn is known (not broadcast-all). */ + if (err == 0 && params->func != NULL && conn != NULL) { params->func(conn, params->user_data); } @@ -499,8 +497,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params _LIB_IDF int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *params) { - assert(params); - assert(params->attr && params->attr->uuid); + BT_LE_ASSERT(params); + BT_LE_ASSERT(params->attr && params->attr->uuid); LOG_DBG("GattInd[%s]", bt_uuid_str(params->attr->uuid)); @@ -534,7 +532,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn, { struct bt_uuid *uuid = attr->user_data; - assert(uuid); + BT_LE_ASSERT(uuid); LOG_DBG("GattAttrRdSvc[%s]", bt_uuid_str(uuid)); @@ -545,7 +543,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn, } /* BT_UUID_TYPE_32 is not expected for service UUIDs in LE Audio */ - assert(uuid->type == BT_UUID_TYPE_128); + BT_LE_ASSERT(uuid->type == BT_UUID_TYPE_128); return bt_gatt_attr_read(conn, attr, buf, len, offset, BT_UUID_128(uuid)->val, 16); } @@ -581,8 +579,8 @@ ssize_t bt_gatt_attr_read_included(struct bt_conn *conn, uint16_t handle; incl = attr->user_data; - assert(incl); - assert(incl->user_data); + BT_LE_ASSERT(incl); + BT_LE_ASSERT(incl->user_data); handle = bt_gatt_attr_get_handle(incl); uuid = incl->user_data; @@ -618,8 +616,8 @@ ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn, struct gatt_chrc pdu; uint8_t value_len; - assert(chrc); - assert(chrc->uuid); + BT_LE_ASSERT(chrc); + BT_LE_ASSERT(chrc->uuid); pdu.properties = chrc->properties; pdu.value_handle = sys_cpu_to_le16(bt_gatt_attr_value_handle(attr)); @@ -661,7 +659,7 @@ static struct bt_gatt_ccc_cfg *gatts_find_free_ccc_cfg(struct bt_gatt_ccc_manage static struct bt_gatt_ccc_cfg *gatts_find_ccc_cfg_by_conn(const struct bt_conn *conn, struct bt_gatt_ccc_managed_user_data *ccc) { - assert(conn); + BT_LE_ASSERT(conn); for (size_t i = 0; i < ccc->cfg_count; i++) { struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; @@ -784,10 +782,10 @@ ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn, const struct bt_gatt_ccc_cfg *cfg; uint16_t value; - assert(attr); + BT_LE_ASSERT(attr); ccc = attr->user_data; - assert(ccc); + BT_LE_ASSERT(ccc); LOG_DBG("GattAttrRdCcc[%u][%u]", len, offset); @@ -814,10 +812,10 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn, bool new_entry; uint16_t value; - assert(attr); + BT_LE_ASSERT(attr); ccc = attr->user_data; - assert(ccc); + BT_LE_ASSERT(ccc); LOG_DBG("GattAttrWrCcc[%u][%u][%02x]", len, offset, flags); @@ -910,8 +908,8 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn, { const struct bt_gatt_ccc_managed_user_data *ccc; - assert(conn); - assert(attr && attr->uuid); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(attr && attr->uuid); LOG_DBG("GattIsSub[%04x][%s]", ccc_type, bt_uuid_str(attr->uuid)); @@ -950,7 +948,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn, } attr = bt_gatt_attr_next(attr); - assert(attr && attr->uuid); + BT_LE_ASSERT(attr && attr->uuid); LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__); } @@ -962,7 +960,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn, return false; } - assert(attr->uuid); + BT_LE_ASSERT(attr->uuid); LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__); } @@ -978,7 +976,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn, } } - assert(attr->uuid); + BT_LE_ASSERT(attr->uuid); LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__); @@ -987,7 +985,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn, } ccc = attr->user_data; - assert(ccc); + BT_LE_ASSERT(ccc); /* Check if the connection is subscribed */ for (size_t i = 0; i < ccc->cfg_count; i++) { @@ -1013,7 +1011,7 @@ static int gatts_ccc_cfg_update(struct bt_conn *conn, bool value_changed; ccc = attr->user_data; - assert(ccc); + BT_LE_ASSERT(ccc); /* LOG_DBG("GattsCccCfgUpd[%04x]", value); */ @@ -1130,10 +1128,10 @@ int bt_gattc_disc_start_safe(uint16_t conn_handle) _LIB_ONLY int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params) { - assert(conn); - assert(params); - assert(params->start_handle && params->end_handle); - assert(params->start_handle <= params->end_handle); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(params); + BT_LE_ASSERT(params->start_handle && params->end_handle); + BT_LE_ASSERT(params->start_handle <= params->end_handle); LOG_DBG("GattDisc[%u][%u]", params->start_handle, params->end_handle); @@ -1276,7 +1274,7 @@ static int gattc_ccc_discover(struct bt_conn *conn, struct bt_gatt_subscribe_par .val = BT_UUID_GATT_CCC_VAL, }; - assert(params->disc_params); + BT_LE_ASSERT(params->disc_params); LOG_DBG("GattcCccDisc[%u][%u]", params->value_handle, params->end_handle); @@ -1310,11 +1308,11 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par bool has_subscription = false; struct gattc_sub *sub; - assert(conn); - assert(params); - assert(params->notify); - assert(params->value); - assert(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE || + BT_LE_ASSERT(conn); + BT_LE_ASSERT(params); + BT_LE_ASSERT(params->notify); + BT_LE_ASSERT(params->value); + BT_LE_ASSERT(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE || (params->end_handle && params->disc_params)); LOG_DBG("GattcSub[%u][%u][%u][%04x]", @@ -1369,6 +1367,14 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par params->value_handle = 0; /* unlinked: clear retry guard */ return err; } + + /* Adapter may have returned 0 while leaving ccc unresolved + * (cb got attr==NULL). Do not append a dead subscription. */ + if (params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE) { + LOG_ERR("GattcSubCccUnresolved[%u]", params->value_handle); + params->value_handle = 0; + return -ENOENT; + } } else { /* Note: * If the CCC handle is auto-discovered, and during the @@ -1403,8 +1409,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p bool found = false; int err = 0; - assert(conn); - assert(params); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(params); LOG_DBG("GattcUnsub[%04x][%u][%u][%u]", params->value, params->value_handle, params->ccc_handle, params->end_handle); @@ -1469,10 +1475,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p LOG_DBG("GattcUnsub[%u][%d]", has_subscription, err); - if (has_subscription || err) { - /* Notify with NULL data to complete unsubscribe */ - params->notify(conn, params, NULL, 0); - } + /* Notify with NULL data to complete unsubscribe */ + params->notify(conn, params, NULL, 0); return 0; } @@ -1480,8 +1484,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p _LIB_IDF int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params) { - assert(conn); - assert(params); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(params); LOG_DBG("GattRd[%u]", params->handle_count); @@ -1496,7 +1500,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params) } if (params->handle_count == 0) { - assert(params->by_uuid.uuid); + BT_LE_ASSERT(params->by_uuid.uuid); if (params->by_uuid.uuid->type != BT_UUID_TYPE_16) { LOG_ERR("GattRdInvUuid[%s]", bt_uuid_str(params->by_uuid.uuid)); @@ -1514,11 +1518,11 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params) _LIB_IDF int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params) { - assert(conn); - assert(params); - assert(params->func); - assert(params->handle); - assert((!params->data ^ !params->length) == 0); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(params); + BT_LE_ASSERT(params->func); + BT_LE_ASSERT(params->handle); + BT_LE_ASSERT((!params->data ^ !params->length) == 0); LOG_DBG("GattWr[%u][%u][%u]", params->handle, params->length, params->offset); @@ -1545,9 +1549,9 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle, bool sign, bt_gatt_complete_func_t func, void *user_data) { - assert(conn); - assert(handle); - assert((!data ^ !length) == 0); + BT_LE_ASSERT(conn); + BT_LE_ASSERT(handle); + BT_LE_ASSERT((!data ^ !length) == 0); ARG_UNUSED(func); ARG_UNUSED(user_data); @@ -1629,7 +1633,7 @@ 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) { - assert(data && data_len); + BT_LE_ASSERT(data && data_len); #if CONFIG_BT_BLUEDROID_ENABLED bt_le_bluedroid_gatt_handle_event(data, data_len); diff --git a/components/bt/esp_ble_iso/host/common/hci.c b/components/bt/esp_ble_iso/host/common/hci.c index 399794ff19a..baa59c9a711 100644 --- a/components/bt/esp_ble_iso/host/common/hci.c +++ b/components/bt/esp_ble_iso/host/common/hci.c @@ -69,7 +69,7 @@ struct net_buf *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len) LOG_DBG("HciCmdCreate[%04x][%u]", opcode, param_len); buf = net_buf_alloc(&hci_cmd_pool, K_NO_WAIT); - assert(buf); + BT_LE_ASSERT(buf); net_buf_reserve(buf, BT_BUF_RESERVE); diff --git a/components/bt/esp_ble_iso/host/common/host.c b/components/bt/esp_ble_iso/host/common/host.c index 8fef5976970..4d2f72e1074 100644 --- a/components/bt/esp_ble_iso/host/common/host.c +++ b/components/bt/esp_ble_iso/host/common/host.c @@ -40,7 +40,7 @@ void bt_le_host_lock(void) if (err) { /* K_MUTEX_SHORT 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 + * than BT_LE_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. */ @@ -82,22 +82,15 @@ int bt_le_host_init(void) goto delete_mutex; } -#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT - err = bt_le_l2cap_init(); - if (err) { - goto deinit_scan; - } -#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */ - #if CONFIG_BT_BLUEDROID_ENABLED err = bt_le_bluedroid_gap_init(); if (err) { - goto deinit_l2cap; + goto deinit_scan; } err = bt_le_bluedroid_gatt_init(); if (err) { - goto deinit_bluedroid_gatt; + goto deinit_gatt; } #else /* nimble: reset the per-conn GATT cache/NRP arrays (now .bss/PSRAM, no @@ -108,7 +101,7 @@ int bt_le_host_init(void) err = bt_le_iso_init(); if (err) { - goto deinit_bluedroid_gatt; + goto deinit_gatt; } err = bt_le_iso_task_init(); @@ -120,15 +113,11 @@ int bt_le_host_init(void) deinit_iso: bt_le_iso_deinit(); -deinit_bluedroid_gatt: +deinit_gatt: #if CONFIG_BT_BLUEDROID_ENABLED bt_le_bluedroid_gatt_deinit(); -deinit_l2cap: +deinit_scan: #endif /* CONFIG_BT_BLUEDROID_ENABLED */ -#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT - bt_le_l2cap_deinit(); -deinit_scan: /* only reachable when OTS path is compiled in */ -#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */ bt_le_scan_deinit(); delete_mutex: k_mutex_delete(&host_mutex); @@ -148,9 +137,6 @@ void bt_le_host_deinit(void) bt_le_nimble_gattc_db_deinit(); bt_le_nimble_gatt_nrp_deinit(); #endif /* CONFIG_BT_BLUEDROID_ENABLED */ -#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT - bt_le_l2cap_deinit(); -#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */ bt_le_scan_deinit(); k_mutex_delete(&host_mutex); diff --git a/components/bt/esp_ble_iso/host/common/include/common/app/gap.h b/components/bt/esp_ble_iso/host/common/include/common/app/gap.h index b864d065f60..f2391214f72 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/app/gap.h +++ b/components/bt/esp_ble_iso/host/common/include/common/app/gap.h @@ -57,6 +57,9 @@ struct bt_le_gap_app_pa_sync_past_param { uint8_t adv_phy; uint16_t per_adv_itvl; uint8_t adv_ca; + /* PAST sender, not the advertiser in addr above; conn_handle is resolved + * from it on Bluedroid. */ + struct bt_le_addr src_addr; /* ACL conn that delivered the PAST. */ uint16_t conn_handle; }; @@ -300,7 +303,7 @@ 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_app_post_event(uint8_t type, void *param); +void bt_le_gap_app_post_event(uint16_t type, void *param); #ifdef __cplusplus } diff --git a/components/bt/esp_ble_iso/host/common/include/common/conn.h b/components/bt/esp_ble_iso/host/common/include/common/conn.h index a456c47cb09..41d22eacf83 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/conn.h +++ b/components/bt/esp_ble_iso/host/common/include/common/conn.h @@ -32,8 +32,6 @@ void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count); struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle); -struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle); - int bt_le_acl_conn_new(uint16_t conn_handle, uint8_t role, bt_addr_le_t *dst, diff --git a/components/bt/esp_ble_iso/host/common/include/common/gatt.h b/components/bt/esp_ble_iso/host/common/include/common/gatt.h index d4c2aaabc35..3a286273e14 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/gatt.h +++ b/components/bt/esp_ble_iso/host/common/include/common/gatt.h @@ -77,7 +77,10 @@ struct bt_le_gatts_notify_tx_event { uint16_t conn_handle; uint16_t conn_id; uint16_t attr_handle; - uint8_t status; + /* int, not uint8_t: NimBLE reports BLE_HS_ERR_ATT_BASE(0x100)+att_code for a + * failed indication; uint8_t truncates 0x10e -> 0x0e == BLE_HS_EDONE (success), + * masking the failure as confirmation. */ + int status; }; /* Bluedroid-side adapter events. NimBLE produces ACL connect/disconnect via diff --git a/components/bt/esp_ble_iso/host/common/include/common/host.h b/components/bt/esp_ble_iso/host/common/include/common/host.h index 1765db340f8..b79165cd3be 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/host.h +++ b/components/bt/esp_ble_iso/host/common/include/common/host.h @@ -14,13 +14,13 @@ #include #include +#include "utils/assert.h" #include "utils/iso_attr.h" #include "utils/mem.h" #include "common/adv.h" #include "common/conn.h" #include "common/iso.h" -#include "common/l2cap.h" #include "common/scan.h" #include "common/gatt.h" #include "common/task.h" diff --git a/components/bt/esp_ble_iso/host/common/include/common/iso.h b/components/bt/esp_ble_iso/host/common/include/common/iso.h index 0e2d81c1fc7..8c79528dfdc 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/iso.h +++ b/components/bt/esp_ble_iso/host/common/include/common/iso.h @@ -28,6 +28,7 @@ struct net_buf; struct bt_le_iso_cb { void (*cis_dis)(struct net_buf *buf); void (*cis_est)(struct net_buf *buf); + void (*cis_est_v2)(struct net_buf *buf); void (*cis_req)(struct net_buf *buf); void (*big_create)(struct net_buf *buf); void (*big_term)(struct net_buf *buf); diff --git a/components/bt/esp_ble_iso/host/common/include/common/l2cap.h b/components/bt/esp_ble_iso/host/common/include/common/l2cap.h deleted file mode 100644 index f071ab4afdd..00000000000 --- a/components/bt/esp_ble_iso/host/common/include/common/l2cap.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2016 Intel Corporation - * SPDX-FileCopyrightText: 2023 Nordic Semiconductor - * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef HOST_COMMON_L2CAP_H_ -#define HOST_COMMON_L2CAP_H_ - -#include - -#include "sdkconfig.h" - -#if CONFIG_BT_BLUEDROID_ENABLED -/* TODO */ -#else -#include "nimble/l2cap.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define L2CAP_LE_SUCCESS 0x0000 -#define L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002 -#define L2CAP_LE_ERR_NO_RESOURCES 0x0004 -#define L2CAP_LE_ERR_AUTHENTICATION 0x0005 -#define L2CAP_LE_ERR_AUTHORIZATION 0x0006 -#define L2CAP_LE_ERR_KEY_SIZE 0x0007 -#define L2CAP_LE_ERR_ENCRYPTION 0x0008 -#define L2CAP_LE_ERR_INVALID_SCID 0x0009 -#define L2CAP_LE_ERR_SCID_IN_USE 0x000A -#define L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B -#define L2CAP_LE_ERR_INVALID_PARAMS 0x000C - -#define L2CAP_LE_OTS_PSM 0x0025 -#define L2CAP_LE_OTS_MTU MIN(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, \ - CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU) - -int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm, - uint16_t scid, uint16_t mtu, - uint16_t mps, uint16_t credits, - uint16_t *result); - -void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm, - uint16_t tx_cid, uint16_t tx_mtu, - uint16_t rx_cid, uint16_t rx_mtu); - -void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm); - -void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm, - uint8_t *data, uint16_t len); - -int bt_le_l2cap_init(void); - -void bt_le_l2cap_deinit(void); - -#ifdef __cplusplus -} -#endif - -#endif /* HOST_COMMON_L2CAP_H_ */ diff --git a/components/bt/esp_ble_iso/host/common/include/common/scan.h b/components/bt/esp_ble_iso/host/common/include/common/scan.h index 2a1d7b13edf..894e22794a4 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/scan.h +++ b/components/bt/esp_ble_iso/host/common/include/common/scan.h @@ -35,6 +35,8 @@ void bt_le_scan_recv_listener(uint16_t event_type, uint8_t data_len, uint8_t *data); +struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t sync_handle); + struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle); int bt_le_per_adv_sync_new(uint16_t sync_handle, diff --git a/components/bt/esp_ble_iso/host/common/include/common/task.h b/components/bt/esp_ble_iso/host/common/include/common/task.h index 5a3a8dfc395..77c388c10ae 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/task.h +++ b/components/bt/esp_ble_iso/host/common/include/common/task.h @@ -60,6 +60,7 @@ enum iso_queue_item_type { ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, ISO_QUEUE_ITEM_TYPE_GAP_EVENT, ISO_QUEUE_ITEM_TYPE_GATT_EVENT, + ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT, ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT, /* floodable: high-volume best-effort reports, posted non-blocking, newest diff --git a/components/bt/esp_ble_iso/host/common/iso.c b/components/bt/esp_ble_iso/host/common/iso.c index e87cba7d501..8cf5a36cd24 100644 --- a/components/bt/esp_ble_iso/host/common/iso.c +++ b/components/bt/esp_ble_iso/host/common/iso.c @@ -60,6 +60,7 @@ static struct bt_le_iso_cb iso_cb = { #if CONFIG_BT_ISO_UNICAST .cis_dis = hci_le_cis_disconnected, .cis_est = hci_le_cis_established, + .cis_est_v2 = hci_le_cis_established_v2, #if CONFIG_BT_ISO_PERIPHERAL .cis_req = hci_le_cis_req, #endif /* CONFIG_BT_ISO_PERIPHERAL */ @@ -135,12 +136,11 @@ static void hci_le_cis_disconnected(struct net_buf *buf) LOG_DBG("CisDisconnectedEvt[%u]", evt->handle); iso = bt_conn_lookup_handle(evt->handle, BT_CONN_TYPE_ISO); - assert(iso); + BT_LE_ASSERT(iso); iso->err = evt->reason; bt_iso_disconnected(iso); - bt_conn_unref(iso); } static int iso_cis_disconn_evt_listener_safe(uint8_t *data) @@ -189,6 +189,27 @@ static int iso_cis_est_evt_listener_safe(uint8_t *data) return 0; } +static int iso_cis_est_evt_v2_listener_safe(uint8_t *data) +{ + struct bt_le_iso_cb *listener = NULL; + struct net_buf buf = {0}; + + LOG_DBG("CisEstEvtV2Listener"); + + bt_le_host_lock(); + + SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) { + if (listener->cis_est_v2) { + buf.data = data + 1; /* The first octet is subev_code */ + listener->cis_est_v2(&buf); + } + } + + bt_le_host_unlock(); + + return 0; +} + #if CONFIG_BT_ISO_PERIPHERAL static int iso_cis_req_evt_listener_safe(uint8_t *data) { @@ -346,7 +367,7 @@ static void handle_iso_event(uint8_t *data, size_t data_len) #if CONFIG_BT_ISO_UNICAST if (le_meta == false) { - assert(event == BT_HCI_EVT_DISCONN_COMPLETE); + BT_LE_ASSERT(event == BT_HCI_EVT_DISCONN_COMPLETE); iso_cis_disconn_evt_listener_safe(data + 2); return; } @@ -359,6 +380,9 @@ static void handle_iso_event(uint8_t *data, size_t data_len) case BT_HCI_EVT_LE_CIS_ESTABLISHED: iso_cis_est_evt_listener_safe(data + 2); break; + case BT_HCI_EVT_LE_CIS_ESTABLISHED_V2: + iso_cis_est_evt_v2_listener_safe(data + 2); + break; #if CONFIG_BT_ISO_PERIPHERAL case BT_HCI_EVT_LE_CIS_REQ: iso_cis_req_evt_listener_safe(data + 2); @@ -385,14 +409,14 @@ static void handle_iso_event(uint8_t *data, size_t data_len) break; #endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ default: - assert(0); + LOG_ERR("HandleIsoEvtUnknown[%u]", event); break; } } void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len) { - assert(data && data_len); + BT_LE_ASSERT(data && data_len); handle_iso_event(data, data_len); free(data); } @@ -442,6 +466,15 @@ static void iso_tx_sdu_clear(uint16_t handle, bool is_big) SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&iso_tx_sdu_list, sdu_node, sdu_tmp, node) { iso = sdu_node->chan->iso; + /* Peripheral CIS clears chan->iso during disconnect (before this runs); + * the SDU is orphaned — drop it instead of dereferencing NULL. */ + if (iso == NULL) { + sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node); + free((void *)sdu_node->sdu); + free(sdu_node); + continue; + } + if ((is_big == false && iso->handle == handle) || (is_big && iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER && iso->iso.info.broadcaster.big_handle == handle)) { @@ -614,7 +647,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len) void *ud; int err; - assert(data && data_len == sizeof(*evt)); + BT_LE_ASSERT(data && data_len == sizeof(*evt)); bt_le_host_lock(); @@ -647,7 +680,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len) * If using controller from other vendors, the pkt buffer may * needs to be freed here. */ - LOG_WRN("IsoTxCompFail[%d]", err); + LOG_WRN("IsoTxCompFail[%d]", err); } free(sdu_node); @@ -659,7 +692,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len) } chan = iso->iso.chan; - assert(chan); + BT_LE_ASSERT(chan); cb = NULL; ud = NULL; @@ -687,7 +720,7 @@ static void iso_tx_comp_cb(uint16_t conn_handle, void *info, size_t size) * the event to the ISO task for processing. */ - assert(size == sizeof(struct bt_iso_tx_cb_info)); + BT_LE_ASSERT(size == sizeof(struct bt_iso_tx_cb_info)); evt = bt_le_int_calloc(1, sizeof(*evt)); if (evt == NULL) { @@ -717,7 +750,7 @@ void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len) { struct net_buf buf = {0}; - assert(data && data_len); + BT_LE_ASSERT(data && data_len); bt_le_host_lock(); net_buf_simple_init_with_data(&buf.b, (void *)data, data_len); diff --git a/components/bt/esp_ble_iso/host/common/l2cap.c b/components/bt/esp_ble_iso/host/common/l2cap.c deleted file mode 100644 index ca868ee83c6..00000000000 --- a/components/bt/esp_ble_iso/host/common/l2cap.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2016 Intel Corporation - * SPDX-FileCopyrightText: 2023 Nordic Semiconductor - * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include - -#include -#include -#include - -#include <../host/conn_internal.h> -#include - -#include "common/host.h" -#include "common/app/gap.h" - -LOG_MODULE_REGISTER(ISO_L2CAP, CONFIG_BT_ISO_LOG_LEVEL); - -#define L2CAP_LE_MIN_MTU 23 -#define L2CAP_ECRED_MIN_MTU 64 - -#define L2CAP_LE_CID_DYN_START 0x0040 -#define L2CAP_LE_CID_DYN_END 0x007F -#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END) - -#define L2CAP_LE_PSM_FIXED_START 0x0001 -#define L2CAP_LE_PSM_FIXED_END 0x007F -#define L2CAP_LE_PSM_DYN_START 0x0080 -#define L2CAP_LE_PSM_DYN_END 0x00FF -#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END) - -static BT_ISO_EXT_RAM_BSS_ATTR sys_slist_t l2cap_servers; - -static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid) -{ - struct bt_l2cap_chan *chan; - - SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { - if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) { - return chan; - } - } - - return NULL; -} - -__attribute__((unused)) -static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid) -{ - struct bt_l2cap_chan *chan; - - SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { - if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) { - return chan; - } - } - - return NULL; -} - -static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm) -{ - struct bt_l2cap_chan *chan; - - SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { - if (BT_L2CAP_LE_CHAN(chan)->psm == psm) { - return chan; - } - } - - return NULL; -} - -static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm) -{ - LOG_DBG("L2capChanAdd[%04x]", psm); - - /* Attach channel to the connection */ - if (sys_slist_find(&conn->channels, &chan->node, NULL)) { - LOG_WRN("L2capChanExists[%04x]", psm); - return false; - } - - sys_slist_append(&conn->channels, &chan->node); - chan->conn = conn; - - /* Set channel PSM */ - BT_L2CAP_LE_CHAN(chan)->psm = psm; - - return true; -} - -static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm) -{ - struct bt_l2cap_server *server = NULL; - - SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) { - if (server->psm == psm) { - break; - } - } - - return server; -} - -static inline uint16_t err_to_result(int err) -{ - switch (err) { - case -ENOMEM: - return L2CAP_LE_ERR_NO_RESOURCES; - case -EACCES: - return L2CAP_LE_ERR_AUTHORIZATION; - case -EPERM: - return L2CAP_LE_ERR_KEY_SIZE; - case -ENOTSUP: - /* This handle the cases where a fixed channel is registered but - * for some reason (e.g. controller not supporting a feature) - * cannot be used. - */ - return L2CAP_LE_ERR_PSM_NOT_SUPP; - default: - return L2CAP_LE_ERR_UNACCEPT_PARAMS; - } -} - -_IDF_ONLY -int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm, - uint16_t scid, uint16_t mtu, - uint16_t mps, uint16_t credits, - uint16_t *result) -{ - struct bt_l2cap_server *server; - struct bt_l2cap_chan *chan; - struct bt_conn *conn; - int err; - - ARG_UNUSED(credits); - - LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps); - - conn = bt_le_acl_conn_find(conn_handle); - if (conn == NULL || conn->state != BT_CONN_CONNECTED) { - LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); - - *result = L2CAP_LE_ERR_INVALID_PARAMS; - return -ENOTCONN; - } - - /* Check if there is a server registered */ - server = l2cap_server_lookup_psm(psm); - if (server == NULL) { - LOG_ERR("SrvNotReg[%04x]", psm); - - *result = L2CAP_LE_ERR_PSM_NOT_SUPP; - return -ENOTSUP; - } - - if (!L2CAP_LE_CID_IS_DYN(scid)) { - LOG_ERR("NotDynScid[%04x]", scid); - - *result = L2CAP_LE_ERR_INVALID_SCID; - return -EINVAL; - } - - chan = l2cap_lookup_tx_cid(conn, scid); - if (chan) { - LOG_WRN("ScidUsed[%04x]", scid); - - *result = L2CAP_LE_ERR_SCID_IN_USE; - return -EALREADY; - } - - if (server->accept == NULL) { - LOG_ERR("SrvAcceptNull"); - - *result = L2CAP_LE_ERR_INVALID_PARAMS; - return -EIO; - } - - err = server->accept(conn, server, &chan); - if (err) { - LOG_ERR("SrvAcceptFail[%d]", err); - - *result = err_to_result(err); - return -EIO; - } - - if (chan == NULL) { - LOG_ERR("SrvAcceptNullChan"); - *result = L2CAP_LE_ERR_NO_RESOURCES; - return -ENOMEM; - } - - if (l2cap_chan_add(conn, chan, psm) == false) { - *result = L2CAP_LE_ERR_NO_RESOURCES; - return -ENOMEM; - } - - *result = L2CAP_LE_SUCCESS; - return 0; -} - -_IDF_ONLY -void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm, - uint16_t tx_cid, uint16_t tx_mtu, - uint16_t rx_cid, uint16_t rx_mtu) -{ - struct bt_l2cap_chan *chan; - struct bt_conn *conn; - - LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]", - conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu); - - conn = bt_le_acl_conn_find(conn_handle); - if (conn == NULL || conn->state != BT_CONN_CONNECTED) { - LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); - return; - } - - chan = l2cap_lookup_psm(conn, psm); - if (chan == NULL) { - LOG_ERR("PsmNotFound[%04x]", psm); - return; - } - - BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid; - BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu; - BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid; - BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu; - - if (chan->ops->connected) { - chan->ops->connected(chan); - } -} - -#define L2CAP_UGLY_TX_DONE_WORKAROUND 1 - -_IDF_ONLY -void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm) -{ -#if L2CAP_UGLY_TX_DONE_WORKAROUND - struct bt_gatt_ots_l2cap *l2cap_ctx; - struct bt_l2cap_le_chan *l2chan; -#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */ - struct bt_l2cap_chan *chan; - struct bt_conn *conn; - - LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm); - - conn = bt_le_acl_conn_find(conn_handle); - if (conn == NULL || conn->state != BT_CONN_CONNECTED) { - LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); - return; - } - - chan = l2cap_lookup_psm(conn, psm); - if (chan == NULL) { - LOG_ERR("PsmNotFound[%04x]", psm); - return; - } - -#if L2CAP_UGLY_TX_DONE_WORKAROUND - l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan); - l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan); - - if (chan->ops->sent && l2cap_ctx->tx_done) { - chan->ops->sent(chan); - } -#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */ - - if (chan->ops->disconnected) { - chan->ops->disconnected(chan); - } - - sys_slist_find_and_remove(&conn->channels, &chan->node); - chan->conn = NULL; -} - -_IDF_ONLY -void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm, - uint8_t *data, uint16_t len) -{ - struct bt_l2cap_chan *chan; - struct net_buf buf = {0}; - struct bt_conn *conn; - - LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len); - - conn = bt_le_acl_conn_find(conn_handle); - if (conn == NULL || conn->state != BT_CONN_CONNECTED) { - LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn)); - return; - } - - chan = l2cap_lookup_psm(conn, psm); - if (chan == NULL) { - LOG_ERR("PsmNotFound[%04x]", psm); - return; - } - - buf.data = data; - buf.len = len; - - if (chan->ops->recv) { - chan->ops->recv(chan, &buf); - } -} - -_IDF_ONLY -int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm) -{ -#if !CONFIG_BT_BLUEDROID_ENABLED - int err; -#endif - - LOG_DBG("L2capChanConnect[%04x]", psm); - - if (chan == NULL) { - LOG_ERR("L2capChanNull"); - return -EINVAL; - } - - if (conn == NULL || conn->state != BT_CONN_CONNECTED) { - LOG_ERR("L2capChanNotConn[%p]", conn); - return -ENOTCONN; - } - - if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) { - LOG_ERR("InvPsm[%04x]", psm); - return -EINVAL; - } - -#if CONFIG_BT_BLUEDROID_ENABLED - /* L2CAP COC is not yet implemented in the Bluedroid adapter (no - * bt_le_bluedroid_l2cap_chan_connect exists). Return early so callers - * like bt_gatt_ots_l2cap_connect see a clean -ENOTSUP. */ - return -ENOTSUP; -#else - err = bt_le_nimble_l2cap_chan_connect(conn->handle); - if (err) { - return err; - } - - l2cap_chan_add(conn, chan, psm); - - return 0; -#endif -} - -_IDF_ONLY -int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) -{ -#if !CONFIG_BT_BLUEDROID_ENABLED - int err; -#endif - - LOG_DBG("L2capChanDisconnect"); - - if (chan == NULL) { - LOG_ERR("L2capChanNull"); - return -EINVAL; - } - - if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) { - LOG_ERR("L2capChanNotConn[%p]", chan->conn); - return -ENOTCONN; - } - -#if CONFIG_BT_BLUEDROID_ENABLED - return -ENOTSUP; -#else - err = bt_le_nimble_l2cap_chan_disconnect(chan); - if (err) { - /* If the disconnect failed, remove the channel from the connection. - * Otherwise the removal will be handled by the disconnect callback. - */ - sys_slist_find_and_remove(&chan->conn->channels, &chan->node); - chan->conn = NULL; - } - - return err; -#endif -} - -_IDF_ONLY -int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) -{ - if (chan == NULL || buf == NULL) { - LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf); - return -EINVAL; - } - - LOG_DBG("L2capChanSend[%u]", buf->len); - - if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) { - LOG_ERR("L2capChanNotConn[%p]", chan->conn); - return -ENOTCONN; - } - - if (buf->len > L2CAP_LE_OTS_MTU) { - LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len, L2CAP_LE_OTS_MTU); - return -EMSGSIZE; - } - -#if CONFIG_BT_BLUEDROID_ENABLED - return -ENOTSUP; -#else - return bt_le_nimble_l2cap_chan_send(chan, buf); -#endif -} - -_IDF_ONLY -int bt_l2cap_server_register(struct bt_l2cap_server *server) -{ - LOG_DBG("L2capSrvReg"); - - if (server == NULL) { - LOG_ERR("SrvNull"); - return -EINVAL; - } - - if (server->accept == NULL) { - LOG_ERR("SrvAcceptNull"); - return -EINVAL; - } - - if (server->sec_level > BT_SECURITY_L4) { - LOG_ERR("InvSecLevel[%u]", server->sec_level); - return -EINVAL; - } - - if (server->psm) { - if (server->psm < L2CAP_LE_PSM_FIXED_START || - server->psm > L2CAP_LE_PSM_DYN_END) { - LOG_ERR("InvPsm[%04x]", server->psm); - return -EINVAL; - } - - /* Check if given PSM is already in use */ - if (l2cap_server_lookup_psm(server->psm)) { - LOG_WRN("PsmReg"); - return -EADDRINUSE; - } - - LOG_DBG("SrvPsm[%04x]", server->psm); - } else { - uint16_t psm; - - for (psm = L2CAP_LE_PSM_DYN_START; - psm <= L2CAP_LE_PSM_DYN_END; psm++) { - if (l2cap_server_lookup_psm(psm) == NULL) { - break; - } - } - - if (psm > L2CAP_LE_PSM_DYN_END) { - LOG_ERR("NoFreeDynPsm"); - return -EADDRNOTAVAIL; - } - - LOG_DBG("PsmNew[%04x]", psm); - - server->psm = psm; - } - - if (server->sec_level < BT_SECURITY_L1) { - server->sec_level = BT_SECURITY_L1; - } - - sys_slist_append(&l2cap_servers, &server->node); - - return 0; -} - -int bt_le_l2cap_init(void) -{ - int err; - - LOG_DBG("L2capInit"); - -#if CONFIG_BT_OTS - extern int bt_gatt_ots_conn_cb_register(void); - err = bt_gatt_ots_conn_cb_register(); - if (err) { - LOG_ERR("OtsConnCbRegFail[%d]", err); - return err; - } - - extern int bt_gatt_ots_instances_prepare_v2(void); - err = bt_gatt_ots_instances_prepare_v2(); - if (err) { - LOG_ERR("PrepOtsInstsFail[%d]", err); - return err; - } -#endif /* CONFIG_BT_OTS */ - -#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT - extern int bt_gatt_ots_l2cap_init_v2(void); - err = bt_gatt_ots_l2cap_init_v2(); - if (err) { - LOG_ERR("OtsL2capInitFail[%d]", err); - return err; - } -#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */ - -#if CONFIG_BT_BLUEDROID_ENABLED - /* No adapter-level L2CAP init exists (COC not implemented). Don't fail - * host init here — the host-agnostic OTS registrations above are still - * valid; any actual COC connect attempt later returns -ENOTSUP in - * bt_l2cap_chan_connect, so OTS features fail gracefully at use time - * instead of preventing the whole host from coming up. */ -#else - err = bt_le_nimble_l2cap_init(); - if (err) { - return err; - } -#endif - - return 0; -} - -void bt_le_l2cap_deinit(void) -{ - LOG_DBG("L2capDeinit"); - - /* TODO: L2CAP server deinit */ -} diff --git a/components/bt/esp_ble_iso/host/common/scan.c b/components/bt/esp_ble_iso/host/common/scan.c index e34d7f6a528..36e49c2a6c0 100644 --- a/components/bt/esp_ble_iso/host/common/scan.c +++ b/components/bt/esp_ble_iso/host/common/scan.c @@ -198,7 +198,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad { struct bt_le_per_adv_sync *per_adv_sync = NULL; - assert(adv_addr); + BT_LE_ASSERT(adv_addr); LOG_INF("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid); @@ -218,7 +218,8 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad return per_adv_sync; } -static struct bt_le_per_adv_sync *per_adv_sync_find(uint16_t handle) +_IDF_ONLY +struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t handle) { struct bt_le_per_adv_sync *per_adv_sync = NULL; @@ -239,7 +240,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle) struct bt_le_per_adv_sync *per_adv_sync = NULL; LOG_DBG("PaSyncFind[%u]", sync_handle); bt_le_host_lock(); - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); bt_le_host_unlock(); return per_adv_sync; } @@ -284,7 +285,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle, return -EINVAL; } - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync) { LOG_WRN("PaSyncExist[%u]", sync_handle); return -EEXIST; @@ -327,7 +328,7 @@ int bt_le_per_adv_sync_delete(uint16_t sync_handle) LOG_DBG("PaSyncDelete[%u]", sync_handle); - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync == NULL) { LOG_ERR("PaSyncNotFound[%u]", sync_handle); return -ENODEV; @@ -347,7 +348,7 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle) LOG_DBG("PaSyncEstabListener[%u]", sync_handle); - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync == NULL) { LOG_ERR("PaSyncNotFound[%u]", sync_handle); return -ENODEV; @@ -374,10 +375,6 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle) } } - if (info.conn) { - bt_conn_unref(info.conn); - } - return 0; } @@ -390,7 +387,7 @@ int bt_le_per_adv_sync_lost_listener(uint16_t sync_handle) LOG_DBG("PaSyncLostListener[%u]", sync_handle); - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync == NULL) { LOG_ERR("PaSyncNotFound[%u]", sync_handle); return -ENODEV; @@ -426,7 +423,7 @@ int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle, return -EINVAL; } - per_adv_sync = per_adv_sync_find(sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(sync_handle); if (per_adv_sync == NULL) { LOG_ERR("PaSyncNotFound[%u]", sync_handle); return -ENODEV; @@ -458,7 +455,7 @@ void hci_le_biginfo_adv_report(struct net_buf *buf) /* LOG_DBG("BigInfoRecvListener[%u]", evt->sync_handle); */ - per_adv_sync = per_adv_sync_find(evt->sync_handle); + per_adv_sync = bt_le_per_adv_sync_find(evt->sync_handle); if (per_adv_sync == NULL) { LOG_ERR("PaSyncNotFound[%u]", evt->sync_handle); return; diff --git a/components/bt/esp_ble_iso/host/common/task.c b/components/bt/esp_ble_iso/host/common/task.c index e9d07fdeac8..a7ae764af21 100644 --- a/components/bt/esp_ble_iso/host/common/task.c +++ b/components/bt/esp_ble_iso/host/common/task.c @@ -36,7 +36,14 @@ static BT_ISO_CTRL_BSS_ATTR QueueSetHandle_t iso_queue_set; static BT_ISO_CTRL_BSS_ATTR TaskHandle_t iso_task_handle; -extern void bt_le_timer_handle_event(void *arg); +extern void bt_le_timer_handle_event(void *arg, size_t gen); + +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT +/* Defined in esp_ble_audio (ots/adapter/l2cap.c) - the only L2CAP consumer is + * 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); +#endif #if CONFIG_BT_ISO_DISPATCH_MONITOR /* Per-type dispatch timing, indexed by iso_queue_item_type. Written only by @@ -94,7 +101,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item) switch (item->type) { case ISO_QUEUE_ITEM_TYPE_TIMER_EVENT: - bt_le_timer_handle_event(item->data); + bt_le_timer_handle_event(item->data, item->data_len); break; case ISO_QUEUE_ITEM_TYPE_GAP_EVENT: case ISO_QUEUE_ITEM_TYPE_EXT_ADV_REPORT: @@ -104,6 +111,11 @@ static void iso_dispatch_item(const struct iso_queue_item *item) case ISO_QUEUE_ITEM_TYPE_GATT_EVENT: bt_le_gatt_handle_event(item->data, item->data_len); break; +#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT + case ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT: + bt_le_l2cap_handle_event(item->data, item->data_len); + break; +#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */ case ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT: case ISO_QUEUE_ITEM_TYPE_BIGINFO_ADV_REPORT: bt_le_iso_handle_hci_event(item->data, item->data_len); @@ -118,7 +130,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item) if (item->data) { free(item->data); } - assert(0); + BT_LE_ASSERT(0); break; } @@ -188,9 +200,13 @@ int bt_le_iso_task_post(enum iso_queue_item_type type, wait = 0; break; default: - /* Timer / GATT / HCI / GAP lifecycle: reliable, block until space. */ + /* Timer / GATT / HCI / GAP lifecycle. A self-post from iso_task must + * not block (it is the sole consumer -> blocking on its own full queue + * deadlocks); external producers get a bounded wait, not portMAX_DELAY, + * so a wedged iso_task can't freeze esp_timer / the host task and stall + * the ISO data path. */ queue = iso_normal_queue; - wait = portMAX_DELAY; + wait = (xTaskGetCurrentTaskHandle() == iso_task_handle) ? 0 : K_QUEUE_SHORT; break; } diff --git a/components/bt/esp_ble_iso/host/iso/iso.c b/components/bt/esp_ble_iso/host/iso/iso.c index 99537c51d08..eb896b9cc2a 100644 --- a/components/bt/esp_ble_iso/host/iso/iso.c +++ b/components/bt/esp_ble_iso/host/iso/iso.c @@ -86,7 +86,7 @@ static void bt_iso_sent_cb(struct bt_conn *iso, void *user_data, int err) struct bt_iso_chan *chan = iso->iso.chan; struct bt_iso_chan_ops *ops; - assert(chan != NULL && "NUllConnForISOSentCb"); + BT_LE_ASSERT(chan != NULL && "NUllConnForISOSentCb"); ops = chan->ops; @@ -135,7 +135,6 @@ void hci_iso(struct net_buf *buf) iso(buf)->index = bt_conn_index(iso); bt_iso_recv(iso, buf, flags); - bt_conn_unref(iso); } static void iso_get_and_clear_cb(struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t *cb, @@ -288,6 +287,14 @@ static int validate_iso_setup_data_path_parms(const struct bt_iso_chan *chan, ui return -EINVAL; } + /* LE Setup ISO Data Path is one HCI command (251B usable in the 255B pool): + * sizeof(cp)+cc_len must fit, else uint8 param_len wraps and overflows. */ + CHECKIF(path->cc_len > 251 - sizeof(struct bt_hci_cp_le_setup_iso_path)) { + LOG_ERR("InvCcLenTooLarge[%u]", path->cc_len); + + return -EINVAL; + } + return 0; } @@ -432,14 +439,14 @@ void bt_iso_connected(struct bt_conn *iso) bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED); - if (chan->ops->connected) { + if (chan->ops && chan->ops->connected) { chan->ops->connected(chan); } } static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason) { - assert(chan->iso != NULL && "NullConnForIsoChan"); + BT_LE_ASSERT(chan->iso != NULL && "NullConnForIsoChan"); const uint8_t conn_type = chan->iso->iso.info.type; @@ -452,12 +459,12 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason) * the callback and to be more similar to the ACL disconnected callback. This also means * that the channel cannot be reused or memset in the callback */ - if (chan->ops->disconnected) { + if (chan->ops && chan->ops->disconnected) { chan->ops->disconnected(chan, reason); } /* The peripheral does not have the concept of a CIG, so once a CIS - * disconnects it is completely freed by unref'ing it + * disconnects it is completely freed. */ if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) && (conn_type == BT_ISO_CHAN_TYPE_CENTRAL || conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL)) { @@ -466,7 +473,6 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason) if (conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL) { /* Release iso conn slot back to the pool. */ chan->iso->type = BT_CONN_TYPE_NONE; - bt_conn_unref(chan->iso); chan->iso = NULL; #if defined(CONFIG_BT_ISO_CENTRAL) } else { @@ -476,7 +482,7 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason) /* Update CIG state */ cig = get_cig(chan); - assert(cig != NULL && "CigNull"); + BT_LE_ASSERT(cig != NULL && "CigNull"); is_chan_connected = false; SYS_SLIST_FOR_EACH_CONTAINER(&cig->cis_channels, cis_chan, node) { @@ -679,7 +685,7 @@ void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags) chan = iso_chan(iso); if (chan == NULL) { LOG_ERR("NoChanForIsoRecv"); - } else if (chan->ops->recv != NULL) { + } else if (chan->ops && chan->ops->recv) { chan->ops->recv(chan, &iso_info, buf->data, buf->len); } } @@ -854,12 +860,22 @@ int bt_iso_chan_disconnect(struct bt_iso_chan *chan) void bt_iso_cleanup_acl(struct bt_conn *iso) { - LOG_DBG("IsoCleanupAcl[%u]", iso->iso.acl ? iso->iso.acl->handle : UINT16_MAX); + struct bt_conn *acl = iso->iso.acl; - if (iso->iso.acl) { - bt_conn_unref(iso->iso.acl); - iso->iso.acl = NULL; + LOG_INF("IsoCleanupAcl[%u]", acl ? acl->handle : UINT16_MAX); + + if (acl == NULL) { + return; } + + iso->iso.acl = NULL; + +#if CONFIG_BT_ISO_UNICAST + /* Finalize deferred ACL wipe once no CIS still points at it. */ + if (acl->state == BT_CONN_DISCONNECTED) { + (void)bt_le_acl_conn_delete(acl->handle); + } +#endif /* CONFIG_BT_ISO_UNICAST */ } static void store_cis_info(const struct bt_hci_evt_le_cis_established *evt, struct bt_conn *iso) @@ -1009,6 +1025,10 @@ static void store_cis_info_v2(const struct bt_hci_evt_le_cis_established_v2 *evt LOG_DBG("StoreCisInfoV2"); chan = iso_conn->chan; + if (chan == NULL || chan->qos == NULL) { + LOG_ERR("CisChanOrQosNullV2[%p]", chan); + return; + } rx = chan->qos->rx; tx = chan->qos->tx; @@ -1085,8 +1105,6 @@ void hci_le_cis_established(struct net_buf *buf) iso->err = evt->status; bt_iso_disconnected(iso); } /* else we wait for disconnect event */ - - bt_conn_unref(iso); } void hci_le_cis_established_v2(struct net_buf *buf) @@ -1127,8 +1145,6 @@ void hci_le_cis_established_v2(struct net_buf *buf) iso->err = evt->status; bt_iso_disconnected(iso); } /* else we wait for disconnect event */ - - bt_conn_unref(iso); } #if defined(CONFIG_BT_ISO_PERIPHERAL) @@ -1286,7 +1302,6 @@ void hci_le_cis_req(struct net_buf *buf) if (iso) { LOG_ERR("InvCisHdl[%u]", cis_handle); hci_le_reject_cis(cis_handle, BT_HCI_ERR_CONN_LIMIT_EXCEEDED); - bt_conn_unref(iso); return; } @@ -1301,8 +1316,6 @@ void hci_le_cis_req(struct net_buf *buf) /* Add ISO connection */ iso = bt_conn_add_iso(acl); - bt_conn_unref(acl); - if (!iso) { LOG_ERR("AddCisToAclFail[%u]", acl_handle); hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); @@ -1318,7 +1331,8 @@ void hci_le_cis_req(struct net_buf *buf) if (err) { LOG_INF("AppRejectedCis[%d]", err); bt_iso_cleanup_acl(iso); - bt_conn_unref(iso); + /* Release the ISO slot back to the pool. */ + iso->type = BT_CONN_TYPE_NONE; hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); return; } @@ -1329,8 +1343,10 @@ void hci_le_cis_req(struct net_buf *buf) err = hci_le_accept_cis(cis_handle); if (err) { - bt_iso_cleanup_acl(iso); - bt_conn_unref(iso); + /* iso_accept already bound the app's chan: full teardown so it isn't left + CONNECTING with a dangling chan->iso once the slot is released. */ + iso->err = BT_HCI_ERR_INSUFFICIENT_RESOURCES; + bt_iso_disconnected(iso); hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); return; } @@ -1347,7 +1363,7 @@ static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl) return NULL; } - iso->iso.acl = bt_conn_ref(acl); + iso->iso.acl = acl; return iso; } @@ -1651,7 +1667,7 @@ static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan) return NULL; } - assert(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID"); + BT_LE_ASSERT(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID"); return &cigs[iso_chan->iso->iso.info.unicast.cig_id]; } @@ -1726,7 +1742,6 @@ static void cleanup_cig(struct bt_iso_cig *cig) if (cis->iso != NULL) { /* Release iso conn slot back to the pool. */ cis->iso->type = BT_CONN_TYPE_NONE; - bt_conn_unref(cis->iso); cis->iso = NULL; } @@ -1761,6 +1776,27 @@ static bool valid_cig_param(const struct bt_iso_cig_param *param, bool advanced, return false; } + /* Built in the 255B HCI cmd pool (hci.c): BT_BUF_RESERVE + the 3B cmd header + * eat into it first, and test params cost 14B per CIS instead of 9B, so + * 31 CIS (spec max) never fits. Cap num_cis to the real budget. */ + { + const size_t avail = 255U - BT_BUF_RESERVE - BT_HCI_CMD_HDR_SIZE; + size_t hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params); + size_t cis_sz = sizeof(struct bt_hci_cis_params); + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + if (advanced) { + hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params_test); + cis_sz = sizeof(struct bt_hci_cis_params_test); + } +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (param->num_cis > (avail - hdr_sz) / cis_sz) { + LOG_ERR("TooLargeNumCisForHciCmd[%u][%u]", param->num_cis, advanced); + return false; + } + } + for (uint8_t i = 0; i < param->num_cis; i++) { struct bt_iso_chan *cis = param->cis_channels[i]; @@ -1991,7 +2027,8 @@ static void restore_cig(struct bt_iso_cig *cig, uint8_t existing_num_cis) * bt_iso_cig_reconfigure was called */ if (cis->iso != NULL && cis->iso->iso.info.unicast.cis_id >= existing_num_cis) { - bt_conn_unref(cis->iso); + /* Release the ISO slot back to the pool. */ + cis->iso->type = BT_CONN_TYPE_NONE; cis->iso = NULL; sys_slist_remove(&cig->cis_channels, prev, &cis->node); @@ -2243,12 +2280,12 @@ int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count) struct bt_iso_chan *iso_chan = param[i].iso_chan; struct bt_iso_cig *cig; - iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl); + iso_chan->iso->iso.acl = param[i].acl; bt_conn_set_state(iso_chan->iso, BT_CONN_INITIATING); bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING); cig = get_cig(iso_chan); - assert(cig && "CigNull"); + BT_LE_ASSERT(cig && "CigNull"); cig->state = BT_ISO_CIG_STATE_ACTIVE; } @@ -2313,7 +2350,6 @@ static void cleanup_big(struct bt_iso_big *big) if (bis->iso != NULL) { /* Release iso conn slot back to the pool. */ bis->iso->type = BT_CONN_TYPE_NONE; - bt_conn_unref(bis->iso); bis->iso = NULL; } @@ -2428,7 +2464,7 @@ static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big, } bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node); - assert(bis != NULL && "BisNull"); + BT_LE_ASSERT(bis != NULL && "BisNull"); /* All BIS will share the same QOS */ qos = bis->qos->tx; @@ -2491,7 +2527,7 @@ static int hci_le_create_big_test(const struct bt_le_ext_adv *padv, struct bt_is } bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node); - assert(bis != NULL && "BisNull"); + BT_LE_ASSERT(bis != NULL && "BisNull"); /* All BIS will share the same QOS */ qos = bis->qos; @@ -2972,7 +3008,7 @@ int bt_iso_big_terminate(struct bt_iso_big *big) } bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node); - assert(bis != NULL && "BisNull"); + BT_LE_ASSERT(bis != NULL && "BisNull"); if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER) { diff --git a/components/bt/esp_ble_iso/host/utils/assert.c b/components/bt/esp_ble_iso/host/utils/assert.c new file mode 100644 index 00000000000..d01bdbc8e43 --- /dev/null +++ b/components/bt/esp_ble_iso/host/utils/assert.c @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "esp_log.h" + +#include "utils/assert.h" + +#define TAG "ISO_ASSERT" + +/* Always logged (no LOG_LEVEL gate) — this is the last message before abort, and + * the user needs the context to diagnose. */ +void bt_le_assert(const char *tag, size_t info, + const char *file, int line, const char *func) +{ + esp_log_write(ESP_LOG_ERROR, TAG, + BT_ISO_LOG_COLOR_E + "E (%lu) %s: Assert[%s][info=%u][%s:%d][%s]" + BT_ISO_LOG_RESET_COLOR "\n", + esp_log_timestamp(), TAG, + tag, (unsigned)info, file, line, func); + abort(); +} diff --git a/components/bt/esp_ble_iso/host/utils/include/utils/assert.h b/components/bt/esp_ble_iso/host/utils/include/utils/assert.h new file mode 100644 index 00000000000..9fc2b3891d0 --- /dev/null +++ b/components/bt/esp_ble_iso/host/utils/include/utils/assert.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Also registered as lib_ext_funcs._assert, so tag/info stay free-form for the + * lib's own call sites; BT_LE_ASSERT() passes the stringified condition. */ +void bt_le_assert(const char *tag, size_t info, + const char *file, int line, const char *func) __attribute__((noreturn)); + +/* 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__); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif diff --git a/components/bt/esp_ble_iso/host/utils/keys.c b/components/bt/esp_ble_iso/host/utils/keys.c index 4198a2446dd..969d2d9b0b7 100644 --- a/components/bt/esp_ble_iso/host/utils/keys.c +++ b/components/bt/esp_ble_iso/host/utils/keys.c @@ -24,6 +24,8 @@ #else #include "host/ble_gap.h" #include "host/ble_store.h" + +#include "utils/assert.h" #endif LOG_MODULE_REGISTER(ISO_UTILS, CONFIG_BT_ISO_LOG_LEVEL); @@ -40,7 +42,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info, int num = esp_ble_get_bond_device_num(); esp_ble_bond_dev_t *list; - assert(func); + BT_LE_ASSERT(func); (void)id; LOG_DBG("[B]ForeachBond[%d]", num); @@ -135,7 +137,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info, ble_addr_t peers[CONFIG_BT_MAX_PAIRED]; int num = 0; - assert(func); + BT_LE_ASSERT(func); (void)id; if (ble_store_util_bonded_peers(peers, &num, ARRAY_SIZE(peers)) != 0) { diff --git a/components/bt/esp_ble_iso/host/utils/timer.c b/components/bt/esp_ble_iso/host/utils/timer.c index 9de91400c8d..4bae756a0c3 100644 --- a/components/bt/esp_ble_iso/host/utils/timer.c +++ b/components/bt/esp_ble_iso/host/utils/timer.c @@ -28,11 +28,11 @@ static void iso_timer_cb(void *arg) struct k_work *work = arg; int err; - assert(work); - assert(work->timer); - assert(work->handler); + BT_LE_ASSERT(work); + BT_LE_ASSERT(work->timer); + BT_LE_ASSERT(work->handler); - err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, 0); + err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, work->gen); if (err) { LOG_ERR("TimerCbPostFail[%d]", err); } @@ -40,7 +40,7 @@ static void iso_timer_cb(void *arg) int k_work_submit(struct k_work *work) { - assert(work); + BT_LE_ASSERT(work); if (work->handler == NULL) { LOG_WRN("TimerSubmitHdlrNull"); @@ -65,7 +65,7 @@ bool k_work_is_pending(struct k_work *work) { bool is_pending; - assert(work); + BT_LE_ASSERT(work); if (work->handler == NULL) { LOG_WRN("TimerIsPendingHdlrNull"); @@ -81,20 +81,24 @@ bool k_work_is_pending(struct k_work *work) void k_work_init(struct k_work *work, k_work_handler_t handler) { - assert(work); + BT_LE_ASSERT(work); + + /* Clear timer/timeout_us/gen too: callers must not have to zero-init */ + memset(work, 0, sizeof(*work)); + work->handler = handler; } struct k_work_delayable *k_work_delayable_from_work(struct k_work *work) { - assert(work); + BT_LE_ASSERT(work); return (struct k_work_delayable *)work; } void k_work_init_delayable(struct k_work_delayable *dwork, k_work_handler_t handler) { - assert(dwork); + BT_LE_ASSERT(dwork); const esp_timer_create_args_t timer_args = { .callback = &iso_timer_cb, @@ -123,13 +127,18 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork) { int err; - assert(dwork); + BT_LE_ASSERT(dwork); if (dwork->work.timer == NULL) { LOG_INF("TimerDeinitNotCreated"); return; } + dwork->work.gen++; + + /* esp_timer_delete rejects a running timer */ + esp_timer_stop(dwork->work.timer); + err = esp_timer_delete(dwork->work.timer); if (err) { LOG_ERR("TimerDeinitDelFail[%d]", err); @@ -141,7 +150,7 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork) int k_work_cancel_delayable(struct k_work_delayable *dwork) { - assert(dwork); + BT_LE_ASSERT(dwork); if (dwork->work.timer == NULL) { LOG_INF("TimerCancelNotCreated"); @@ -149,6 +158,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork) } esp_timer_stop(dwork->work.timer); + dwork->work.gen++; return 0; } @@ -156,7 +166,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork) bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork, struct k_work_sync *sync) { - assert(dwork); + BT_LE_ASSERT(dwork); ARG_UNUSED(sync); @@ -167,6 +177,8 @@ bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork, esp_timer_stop(dwork->work.timer); + dwork->work.gen++; + /* TODO: check the return result */ return false; } @@ -175,7 +187,7 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms) { int err; - assert(dwork); + BT_LE_ASSERT(dwork); if (dwork->work.timer == NULL) { LOG_WRN("TimerSchNotCreated"); @@ -184,6 +196,8 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms) esp_timer_stop(dwork->work.timer); + dwork->work.gen++; + dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000; if (ms == K_NO_WAIT) { @@ -204,7 +218,7 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms) { int err; - assert(dwork); + BT_LE_ASSERT(dwork); if (dwork->work.timer == NULL) { LOG_WRN("TimerReschNotCreated"); @@ -213,6 +227,8 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms) esp_timer_stop(dwork->work.timer); + dwork->work.gen++; + dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000; if (ms == K_NO_WAIT) { @@ -233,8 +249,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_ { int err; - assert(dwork); - assert(period_us > 0); + BT_LE_ASSERT(dwork); + BT_LE_ASSERT(period_us > 0); if (dwork->work.timer == NULL) { LOG_WRN("TimerPeriodicNotCreated"); @@ -243,6 +259,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_ esp_timer_stop(dwork->work.timer); + dwork->work.gen++; + err = esp_timer_start_periodic(dwork->work.timer, period_us); if (err) { LOG_ERR("TimerPeriodicStartFail[%d]", err); @@ -265,7 +283,7 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork) k_timeout_t timeout; int64_t delta_us; - assert(dwork); + BT_LE_ASSERT(dwork); if (dwork->work.timer == NULL) { LOG_WRN("TimerRemainingNotCreated"); @@ -286,9 +304,14 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork) return timeout; } -void bt_le_timer_handle_event(void *arg) +void bt_le_timer_handle_event(void *arg, size_t gen) { struct k_work *work = arg; - k_work_submit_safe(work); + bt_le_host_lock(); + /* Drop the event if the work was re-armed or cancelled after it fired */ + if ((uint32_t)gen == work->gen) { + k_work_submit(work); + } + bt_le_host_unlock(); } diff --git a/components/bt/esp_ble_iso/include/zephyr/autoconf.h b/components/bt/esp_ble_iso/include/zephyr/autoconf.h index 5767bc700f5..fad9457f989 100644 --- a/components/bt/esp_ble_iso/include/zephyr/autoconf.h +++ b/components/bt/esp_ble_iso/include/zephyr/autoconf.h @@ -41,7 +41,6 @@ #endif /* CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER */ #define CONFIG_BT_MAX_CONN CONFIG_BT_ACL_CONNECTIONS -#define CONFIG_BT_SMP CONFIG_BT_BLE_SMP_ENABLE #define CONFIG_BT_MAX_PAIRED CONFIG_BT_SMP_MAX_BONDS #if CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST @@ -57,7 +56,6 @@ _Static_assert(CONFIG_BT_ISO_MAX_CHAN == 0, "Too large ISO channels"); #else /* CONFIG_BT_BLUEDROID_ENABLED */ #define CONFIG_BT_MAX_CONN CONFIG_BT_NIMBLE_MAX_CONNECTIONS -#define CONFIG_BT_SMP CONFIG_BT_NIMBLE_SECURITY_ENABLE #define CONFIG_BT_MAX_PAIRED CONFIG_BT_NIMBLE_MAX_BONDS #if CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES diff --git a/components/bt/esp_ble_iso/include/zephyr/bluetooth/conn.h b/components/bt/esp_ble_iso/include/zephyr/bluetooth/conn.h index e9fcbb6ab6a..02cb1d05049 100644 --- a/components/bt/esp_ble_iso/include/zephyr/bluetooth/conn.h +++ b/components/bt/esp_ble_iso/include/zephyr/bluetooth/conn.h @@ -231,44 +231,6 @@ int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info); */ int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason); -/** @brief Set security level for a connection. - * - * This function enable security (encryption) for a connection. If the device - * has bond information for the peer with sufficiently strong key encryption - * will be enabled. If the connection is already encrypted with sufficiently - * strong key this function does nothing. - * - * If the device has no bond information for the peer and is not already paired - * then the pairing procedure will be initiated. Note that @p sec has no effect - * on the security level selected for the pairing process. The selection is - * instead controlled by the values of the registered @ref bt_conn_auth_cb. If - * the device has bond information or is already paired and the keys are too - * weak then the pairing procedure will be initiated. - * - * This function may return an error if the required level of security defined using - * @p sec is not possible to achieve due to local or remote device limitation - * (e.g., input output capabilities), or if the maximum number of paired devices - * has been reached. - * - * This function may return an error if the pairing procedure has already been - * initiated by the local device or the peer device. - * - * @note When @kconfig{CONFIG_BT_SMP_SC_ONLY} is enabled then the security - * level will always be level 4. - * - * @note When @kconfig{CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY} is enabled then the - * security level will always be level 3. - * - * @note When @ref BT_SECURITY_FORCE_PAIR within @p sec is enabled then the pairing - * procedure will always be initiated. - * - * @param conn Connection object. - * @param sec Requested minimum security level. - * - * @return 0 on success or negative error - */ -int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec); - enum bt_security_err { /** Security procedure successful. */ BT_SECURITY_ERR_SUCCESS, @@ -417,6 +379,7 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb); * @retval -ENOENT if @p cb was not registered */ int bt_conn_cb_unregister(struct bt_conn_cb *cb); +int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb); /** * @brief Register a callback structure for connection events. diff --git a/components/bt/esp_ble_iso/include/zephyr/bluetooth/iso.h b/components/bt/esp_ble_iso/include/zephyr/bluetooth/iso.h index a555865eb22..04bf14e3dc1 100644 --- a/components/bt/esp_ble_iso/include/zephyr/bluetooth/iso.h +++ b/components/bt/esp_ble_iso/include/zephyr/bluetooth/iso.h @@ -1294,6 +1294,8 @@ int bt_iso_big_terminate_safe(struct bt_iso_big *big); * * @return 0 in case of success or negative value in case of error. */ +int bt_iso_big_sync(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param, + struct bt_iso_big **out_big); int bt_iso_big_sync_safe(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param, struct bt_iso_big **out_big); diff --git a/components/bt/esp_ble_iso/include/zephyr/kernel.h b/components/bt/esp_ble_iso/include/zephyr/kernel.h index b3ff4259442..195bf9c1312 100644 --- a/components/bt/esp_ble_iso/include/zephyr/kernel.h +++ b/components/bt/esp_ble_iso/include/zephyr/kernel.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -21,6 +20,8 @@ #include "freertos/semphr.h" #include "toolchain.h" +#include "utils/assert.h" + #ifdef __cplusplus extern "C" { #endif @@ -36,17 +37,17 @@ struct k_mutex { static inline void k_mutex_create(struct k_mutex *mutex) { - assert(mutex); - assert(mutex->handle == NULL); + BT_LE_ASSERT(mutex); + BT_LE_ASSERT(mutex->handle == NULL); mutex->handle = xSemaphoreCreateRecursiveMutex(); - assert(mutex->handle); + BT_LE_ASSERT(mutex->handle); } static inline void k_mutex_delete(struct k_mutex *mutex) { - assert(mutex); - assert(mutex->handle); + BT_LE_ASSERT(mutex); + BT_LE_ASSERT(mutex->handle); vSemaphoreDelete(mutex->handle); mutex->handle = NULL; @@ -65,8 +66,8 @@ static inline void k_mutex_delete(struct k_mutex *mutex) static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout) { - assert(mutex); - assert(mutex->handle); + BT_LE_ASSERT(mutex); + BT_LE_ASSERT(mutex->handle); if (xSemaphoreTakeRecursive(mutex->handle, timeout) == pdTRUE) { return 0; @@ -89,8 +90,8 @@ static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout) static inline int k_mutex_unlock(struct k_mutex *mutex) { - assert(mutex); - assert(mutex->handle); + BT_LE_ASSERT(mutex); + BT_LE_ASSERT(mutex->handle); if (xSemaphoreGiveRecursive(mutex->handle) != pdTRUE) { K_MUTEX_LOG_ERR("UnlockFail"); @@ -112,18 +113,18 @@ struct k_sem { static inline void k_sem_create(struct k_sem *sem) { - assert(sem); - assert(sem->handle == NULL); + BT_LE_ASSERT(sem); + BT_LE_ASSERT(sem->handle == NULL); sem->handle = xSemaphoreCreateBinary(); - assert(sem->handle); + BT_LE_ASSERT(sem->handle); sem->result = 0; } static inline void k_sem_delete(struct k_sem *sem) { - assert(sem); - assert(sem->handle); + BT_LE_ASSERT(sem); + BT_LE_ASSERT(sem->handle); vSemaphoreDelete(sem->handle); sem->handle = NULL; @@ -141,8 +142,8 @@ static inline void k_sem_delete(struct k_sem *sem) static inline int k_sem_take(struct k_sem *sem, uint32_t timeout) { - assert(sem); - assert(sem->handle); + BT_LE_ASSERT(sem); + BT_LE_ASSERT(sem->handle); /* Do NOT touch sem->result here. The producer may have already written * it and called k_sem_give before this take ran (BTU/HCI cb on a @@ -164,8 +165,8 @@ static inline int k_sem_take(struct k_sem *sem, uint32_t timeout) static inline int k_sem_give(struct k_sem *sem) { - assert(sem); - assert(sem->handle); + BT_LE_ASSERT(sem); + BT_LE_ASSERT(sem->handle); if (xSemaphoreGive(sem->handle) != pdTRUE) { K_SEM_LOG_ERR("GiveFail"); @@ -181,13 +182,20 @@ static inline int k_sem_give(struct k_sem *sem) * caller would see uninitialized response data. */ static inline void k_sem_reset(struct k_sem *sem) { - assert(sem); - assert(sem->handle); + BT_LE_ASSERT(sem); + BT_LE_ASSERT(sem->handle); xQueueReset(sem->handle); sem->result = 0; } +/* Queue */ + +/* Bounded wait for a reliable-tier task-queue post instead of portMAX_DELAY, + * so a wedged consumer can't freeze an external producer (esp_timer / host + * task) and stall the ISO data path. */ +#define K_QUEUE_SHORT (1000 / portTICK_PERIOD_MS) + /* Timer */ typedef uint32_t k_timeout_t; @@ -202,6 +210,10 @@ typedef uint32_t k_timeout_t; #define K_MINUTES(m) K_SECONDS((m) * 60) #define K_HOURS(h) K_MINUTES((h) * 60) +/* Defer work onto iso_task. In this port K_NO_WAIT runs the handler INLINE + * (timer.c), which fires e.g. GATT indications before the CP write response. */ +#define K_NO_WAIT_ASYNC K_MSEC(1) + struct k_work; typedef void (*k_work_handler_t)(struct k_work *work); @@ -211,6 +223,8 @@ struct k_work { k_work_handler_t handler; int64_t timeout_us; void *user_data; + uint32_t gen; /* Bumped on (re)schedule/cancel/deinit so a timer event + * queued before the change is skipped (see timer.c). */ }; struct k_work_sync { diff --git a/components/bt/esp_ble_iso/include/zephyr/logging/log.h b/components/bt/esp_ble_iso/include/zephyr/logging/log.h index d061b50e331..715d5d97e6f 100644 --- a/components/bt/esp_ble_iso/include/zephyr/logging/log.h +++ b/components/bt/esp_ble_iso/include/zephyr/logging/log.h @@ -15,6 +15,8 @@ #include "esp_log.h" +#include "utils/assert.h" + /* esp_ble_iso, esp_ble_audio and any future ISO consumer are compiled * under the unified BLE_ISO compression channel and share one * iso_log_index.h with one monotonic log-id counter — no per-consumer @@ -112,13 +114,13 @@ extern "C" { #define NET_BUF_WARN(fmt, args...) /* TBD */ #define NET_BUF_INFO(fmt, args...) /* TBD */ #define NET_BUF_DBG(fmt, args...) /* TBD */ -#define NET_BUF_ASSERT assert +#define NET_BUF_ASSERT BT_LE_ASSERT #define NET_BUF_SIMPLE_ERR(fmt, args...) /* TBD */ #define NET_BUF_SIMPLE_WARN(fmt, args...) /* TBD */ #define NET_BUF_SIMPLE_INFO(fmt, args...) /* TBD */ #define NET_BUF_SIMPLE_DBG(fmt, args...) /* TBD */ -#define NET_BUF_SIMPLE_ASSERT assert +#define NET_BUF_SIMPLE_ASSERT BT_LE_ASSERT #ifdef __cplusplus } diff --git a/components/bt/esp_ble_iso/include/zephyr/sys/__assert.h b/components/bt/esp_ble_iso/include/zephyr/sys/__assert.h index b5564e10f70..13a8c09595d 100644 --- a/components/bt/esp_ble_iso/include/zephyr/sys/__assert.h +++ b/components/bt/esp_ble_iso/include/zephyr/sys/__assert.h @@ -8,15 +8,17 @@ #define ZEPHYR_INCLUDE_SYS_ASSERT_H_ #include -#include + +#include "utils/assert.h" #ifdef __cplusplus extern "C" { #endif -#define __ASSERT_NO_MSG(test) assert(test) +#define __ASSERT_NO_MSG(test) BT_LE_ASSERT(test) -#define __ASSERT(test, fmt, ...) assert(test) +/* fmt is dropped as before; BT_LE_ASSERT logs the stringified test instead. */ +#define __ASSERT(test, fmt, ...) BT_LE_ASSERT(test) #ifdef __cplusplus } diff --git a/components/bt/esp_ble_iso/include/zephyr/toolchain.h b/components/bt/esp_ble_iso/include/zephyr/toolchain.h index 7c7d6c2570e..4255ba03555 100644 --- a/components/bt/esp_ble_iso/include/zephyr/toolchain.h +++ b/components/bt/esp_ble_iso/include/zephyr/toolchain.h @@ -11,8 +11,6 @@ #include #include -#define SYS_INIT(init_fn, level, prio) int init_fn ## _v2(void) { return init_fn(); } - #ifndef BUILD_ASSERT #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, ## MSG) #endif diff --git a/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/README.md b/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/README.md index 109f05c6b86..6ad1df63d7d 100644 --- a/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/README.md +++ b/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/README.md @@ -64,7 +64,7 @@ For `esp32s31`, replace the chip overlay accordingly. 6. `ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC` clears `pa_syncing`, cancels discovery, stores `sync_handle`, and calls `esp_ble_audio_bap_broadcast_sink_create()`. 7. `base_recv_cb` extracts the subgroup count and BIS index bitfield (masked by `bis_index_mask`); when no Broadcast Assistant is connected, `requested_bis_sync` defaults to `ESP_BLE_AUDIO_BAP_BIS_SYNC_NO_PREF`. 8. `syncable_cb` AND-masks the BASE bitfield with the requested mask, copies `TARGET_BROADCAST_CODE` if the BIG is encrypted (unless BASS already supplied one), and calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen mask and `streams_p`. -9. `stream_started_cb` resets per-stream RX metrics and increments `stream_count_started`; `stream_recv_cb` updates metrics via `example_audio_rx_metrics_on_recv()`. When all streams have stopped, `stream_stopped_cb` deletes the broadcast sink. `pa_sync_lost()` clears the cached `req_recv_state`, deletes any sink, and restarts the scanner. +9. `stream_started_cb` resets per-stream RX metrics and increments `stream_count_started`; `stream_recv_cb` updates metrics via `example_audio_rx_metrics_on_recv()`. When all streams have stopped, `stream_stopped_cb` clears `stream_started`; the sink itself is deleted from `broadcast_sink_stopped_cb` (the `stopped` sink callback), which runs once BASS has cleared `bis_sync` — deleting from `stream_stopped_cb` would race `rem_src` while `bis_sync` is still non-zero. `pa_sync_lost()` clears the cached `req_recv_state`, deletes any sink, and restarts the scanner. ## Expected Log @@ -92,6 +92,7 @@ On teardown / sync loss: ``` I (xxx) BAP_BSNK: [SNK #0] Stream stopped, reason 0x... (.../...) +I (xxx) BAP_BSNK: Broadcast sink stopped, reason 0x... I (xxx) BAP_BSNK: PA sync lost: sync_handle ... reason 0x... I (xxx) BAP_BSNK: PA sync terminated ``` @@ -105,4 +106,4 @@ Run [broadcast_source](../broadcast_source/) on a second board. Expected interac 3. Source's BIGInfo advertises the BIG as encrypted (broadcast code `"1234"`); sink reports `BIG encrypted`. 4. Source starts the BIG and the two BIS streams (`FRONT_LEFT`, `FRONT_RIGHT`); sink calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen BIS bitfield and the matching broadcast code. 5. Source's TX scheduler keeps pushing SDUs at `preset_active.qos.interval`; sink stream `recv` callbacks deliver the data and update RX metrics. -6. Stopping the source (or losing PA sync) triggers `stream_stopped_cb` on the sink, which deletes the broadcast sink and resumes scanning. +6. Stopping the source (or losing PA sync) tears down the BIG; `broadcast_sink_stopped_cb` then deletes the sink (after BASS clears `bis_sync`) and scanning resumes. diff --git a/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/main/main.c b/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/main/main.c index 1516d846817..5f4bd4849da 100644 --- a/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/main/main.c +++ b/examples/bluetooth/esp_ble_audio/bap/broadcast_sink/main/main.c @@ -209,23 +209,14 @@ static int bis_sync_req_cb(esp_ble_conn_t *conn, stream_started ? "streaming" : "not streaming"); if (stream_started && requested_bis_sync == 0) { - /* The stream stopped callback will be called as part of this, and - * we do not need to wait for any events from the controller. Thus, - * when this returns, the `stream_started` is back to false. + /* stop() tears down the BIG; broadcast_sink_stopped_cb deletes the + * sink after BASS bis_sync has been cleared. */ err = esp_ble_audio_bap_broadcast_sink_stop(broadcast_sink); if (err) { ESP_LOGE(TAG, "Failed to stop broadcast sink, err %d", err); return -EIO; } - - err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); - if (err) { - ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); - return -EIO; - } - - broadcast_sink = NULL; } return 0; @@ -319,9 +310,36 @@ static void syncable_cb(esp_ble_audio_bap_broadcast_sink_t *sink, } } +static void broadcast_sink_stopped_cb(esp_ble_audio_bap_broadcast_sink_t *sink, + uint8_t reason) +{ + esp_err_t err; + + ESP_LOGI(TAG, "Broadcast sink stopped, reason 0x%02x", reason); + + stream_started = false; + + /* Called from big_stopped after update_recv_state_big_cleared(), so + * BASS bis_sync is already 0 and rem_src inside delete can succeed. + * Do not delete from stream_ops.stopped — that runs before bis_sync clear. + */ + if (broadcast_sink == NULL) { + return; + } + + err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); + if (err) { + ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); + return; + } + + broadcast_sink = NULL; +} + static esp_ble_audio_bap_broadcast_sink_cb_t broadcast_sink_cbs = { .base_recv = base_recv_cb, .syncable = syncable_cb, + .stopped = broadcast_sink_stopped_cb, }; static int stream_index(const esp_ble_audio_bap_stream_t *stream) @@ -352,21 +370,14 @@ static void stream_started_cb(esp_ble_audio_bap_stream_t *stream) static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason) { - esp_err_t err; - ESP_LOGI(TAG, "[SNK #%d] Stream stopped, reason 0x%02x (%u/%u)", stream_index(stream), reason, stream_count_stopped, stream_count); if (++stream_count_stopped == stream_count) { stream_started = false; - - err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); - if (err) { - ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); - return; - } - - broadcast_sink = NULL; + /* Sink delete is deferred to broadcast_sink_stopped_cb (after BASS + * bis_sync clear). Deleting here races rem_src and leaves WRNs. + */ } } diff --git a/examples/bluetooth/esp_ble_audio/bap/unicast_client/README.md b/examples/bluetooth/esp_ble_audio/bap/unicast_client/README.md index e87b76fc90a..008ac728f81 100644 --- a/examples/bluetooth/esp_ble_audio/bap/unicast_client/README.md +++ b/examples/bluetooth/esp_ble_audio/bap/unicast_client/README.md @@ -61,8 +61,8 @@ For `esp32s31`, replace the chip overlay accordingly. 3. On a connectable match `ext_scan_recv` calls `ext_scan_stop` and then `conn_create` (NimBLE: `ble_gap_connect`; Bluedroid: `esp_ble_gattc_aux_open`); the resulting `ESP_BLE_AUDIO_GAP_EVENT_ACL_CONNECT` saves the handle and calls `pairing_start` (NimBLE: `ble_gap_security_initiate`; Bluedroid: `esp_ble_set_encryption`). 4. After the security-change event the client triggers `exchange_mtu`. On NimBLE this issues `ble_gattc_exchange_mtu`; on Bluedroid the GATTC adapter auto-configures MTU inside `BTA_GATTC_Enh_Open`, so `exchange_mtu` drives `esp_ble_audio_gattc_disc_start` directly. `gatt_mtu_change` then calls `esp_ble_audio_gattc_disc_start` (the retry is idempotent — `-EALREADY` is treated as success), and once both MTU and discovery are reported `discover_sinks` runs. 5. `discover_cb` chains sink discovery into `discover_sources`; `endpoint_cb` records each EP into the next free `sinks[]`/`sources[]` slot, and source completion kicks `configure_stream`, which configures every EP with `unicast_preset.codec_cfg`. -6. After the last `config_cb`, `create_group` builds `pair_params` (sinks first, then sources) with `ESP_BLE_ISO_PACKING_SEQUENTIAL` and calls `esp_ble_audio_bap_unicast_group_create`, then `set_stream_qos`. -7. When `is_all_stream_qos_set` returns true `enable_stream` walks every stream; on the final `enable_cb`, `connect_stream` ISO-connects each pair (sink first if available); when all are up `start_stream` starts only source streams. Sink streams are started by the server, and `stream_started_cb` calls `stream_tx_register` to begin TX. +6. `config_cb` configures each EP per Config CP ACK until all are done (`all_config_acked`); `create_group` (building `pair_params`, sinks first then sources, with `ESP_BLE_ISO_PACKING_SEQUENTIAL`) and `set_stream_qos` then run from `try_create_group_and_qos`, which waits for every EP to reach codec-configured (`stream_configured_cb` / `ops.configured`) — the CP ACK can arrive before the ASE state change, so driving QoS off the ACK alone races the last EP still in idle. +7. `enable_stream` runs once every EP reports qos-configured (`stream_qos_set_cb` / `ops.qos_set`), not off the QoS CP ACK; `connect_stream` ISO-connects each pair (sink first) once every EP reports enabling (`stream_enabled_cb` / `ops.enabled`), not off the Enable CP ACK. (The ACK callbacks `qos_cb` / `enable_cb` only handle rejects — an ACK can outrun the ASE state change.) When all are up `start_stream` starts only source streams. Sink streams are started by the server, and `stream_started_cb` calls `stream_tx_register` to begin TX. ## Expected Log diff --git a/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/main.c b/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/main.c index cb0cf4198f3..89bc72cdb79 100644 --- a/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/main.c +++ b/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/main.c @@ -59,6 +59,12 @@ static size_t configured_sink_stream_count; static size_t configured_source_stream_count; #define configured_stream_count (configured_sink_stream_count + configured_source_stream_count) +/* Number of streams that have entered codec-configured (ops.configured). + * CP config ACK can arrive while the EP is still idle; QoS must wait for this. + */ +static size_t codec_configured_count; +static bool all_config_acked; + static struct stream_pair_state { esp_ble_audio_bap_stream_t *sink_stream; esp_ble_audio_bap_stream_t *source_stream; @@ -147,6 +153,8 @@ static void reset_stream_state(void) { configured_sink_stream_count = 0; configured_source_stream_count = 0; + codec_configured_count = 0; + all_config_acked = false; reset_stream_pair_state(); @@ -388,6 +396,51 @@ static int set_stream_qos(void) return 0; } +static void try_create_group_and_qos(void) +{ + uint8_t stream_count = 0; + int err; + + /* Wait until every successful Config CP has a matching ASE state + * notification (ops.configured → EP codec-configured). Calling + * qos from config_cb alone races the last stream still in idle. + */ + if (!all_config_acked || unicast_group != NULL) { + return; + } + + if (configured_stream_count == 0) { + ESP_LOGW(TAG, "No streams were configured"); + return; + } + + if (codec_configured_count < configured_stream_count) { + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) { + if (sinks[i].configured == ASCS_RSP_SUCCESS) { + streams[stream_count++] = &sinks[i].stream; + } + } + + for (size_t i = 0; i < ARRAY_SIZE(sources); i++) { + if (sources[i].configured == ASCS_RSP_SUCCESS) { + streams[stream_count++] = &sources[i].stream; + } + } + + err = create_group(); + if (err) { + return; + } + + err = set_stream_qos(); + if (err) { + return; + } +} + static void stream_qos_set(esp_ble_audio_bap_stream_t *stream, bool success) { for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) { @@ -682,18 +735,35 @@ static void stream_configured_cb(esp_ble_audio_bap_stream_t *stream, ESP_LOGI(TAG, "[%s #%d] Stream configured, QoS preference:", stream_dir_str(stream), stream_index(stream)); example_print_qos_pref(TAG, pref); + + codec_configured_count++; + + try_create_group_and_qos(); } static void stream_qos_set_cb(esp_ble_audio_bap_stream_t *stream) { - /* QoS set is also reported by qos_cb; skip the duplicate log here. */ - (void)stream; + /* ASE entered qos-configured — safe to enable. */ + stream_qos_set(stream, true); + + if (is_all_stream_qos_set()) { + enable_stream(); + } } static void stream_enabled_cb(esp_ble_audio_bap_stream_t *stream) { - /* Enabled is also reported by enable_cb; skip the duplicate log here. */ - (void)stream; + bool ret; + + /* ASE entered enabling — safe to connect after all enables complete. */ + stream_enabled(stream, true); + + ret = enable_stream(); + if (ret == false) { + return; + } + + connect_stream(); } static void stream_connected_cb(esp_ble_audio_bap_stream_t *stream) @@ -843,9 +913,7 @@ static void config_cb(esp_ble_audio_bap_stream_t *stream, esp_ble_audio_bap_ascs_rsp_code_t rsp_code, esp_ble_audio_bap_ascs_reason_t reason) { - uint8_t stream_count; bool ret; - int err; log_rsp("Config", stream, rsp_code, reason); @@ -856,34 +924,9 @@ static void config_cb(esp_ble_audio_bap_stream_t *stream, return; } - if (configured_stream_count == 0) { - ESP_LOGW(TAG, "No streams were configured"); - return; - } + all_config_acked = true; - stream_count = 0; - - for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) { - if (sinks[i].configured == ASCS_RSP_SUCCESS) { - streams[stream_count++] = &sinks[i].stream; - } - } - - for (size_t i = 0; i < ARRAY_SIZE(sources); i++) { - if (sources[i].configured == ASCS_RSP_SUCCESS) { - streams[stream_count++] = &sources[i].stream; - } - } - - err = create_group(); - if (err) { - return; - } - - err = set_stream_qos(); - if (err) { - return; - } + try_create_group_and_qos(); } static void qos_cb(esp_ble_audio_bap_stream_t *stream, @@ -892,10 +935,14 @@ static void qos_cb(esp_ble_audio_bap_stream_t *stream, { log_rsp("QoS", stream, rsp_code, reason); - stream_qos_set(stream, rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS); - - if (is_all_stream_qos_set()) { - enable_stream(); + /* Mark failure from CP reject. Success waits for ops.qos_set (ASE + * qos-configured) — enabling on CP ACK alone races codec-configured. + */ + if (rsp_code != ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS) { + stream_qos_set(stream, false); + if (is_all_stream_qos_set()) { + enable_stream(); + } } } @@ -903,18 +950,19 @@ static void enable_cb(esp_ble_audio_bap_stream_t *stream, esp_ble_audio_bap_ascs_rsp_code_t rsp_code, esp_ble_audio_bap_ascs_reason_t reason) { - bool ret; - log_rsp("Enable", stream, rsp_code, reason); - stream_enabled(stream, rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS); + /* Same as QoS: connect needs ENABLING from ASE state ntf (ops.enabled). */ + if (rsp_code != ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS) { + bool ret; - ret = enable_stream(); - if (ret == false) { - return; + stream_enabled(stream, false); + + ret = enable_stream(); + if (ret) { + connect_stream(); + } } - - connect_stream(); } static void start_cb(esp_ble_audio_bap_stream_t *stream, diff --git a/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/stream_tx.c b/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/stream_tx.c index caca8cb9515..f9bf77bd488 100644 --- a/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/stream_tx.c +++ b/examples/bluetooth/esp_ble_audio/bap/unicast_client/main/stream_tx.c @@ -101,6 +101,12 @@ int stream_tx_register(esp_ble_audio_bap_stream_t *stream) return -EINVAL; } + for (size_t i = 0; i < ARRAY_SIZE(tx_streams); i++) { + if (tx_streams[i].stream == stream) { + return 0; /* already armed */ + } + } + for (size_t i = 0; i < ARRAY_SIZE(tx_streams); i++) { if (tx_streams[i].stream == NULL) { if (stream->qos == NULL || stream->qos->sdu == 0) { diff --git a/examples/bluetooth/esp_ble_audio/cap/acceptor/README.md b/examples/bluetooth/esp_ble_audio/cap/acceptor/README.md index ba4f1f56898..ed937408d2d 100644 --- a/examples/bluetooth/esp_ble_audio/cap/acceptor/README.md +++ b/examples/bluetooth/esp_ble_audio/cap/acceptor/README.md @@ -128,6 +128,7 @@ Source (SRC) Assistant (ASS) Acceptor (ACC) | `FLAG_BROADCAST_CODE_RECEIVED` | `broadcast_code_cb` (BASS Set Broadcast Code); self-scan local code | `broadcast_sink_reset` | | `FLAG_BROADCAST_SYNC_REQUESTED` | `bis_sync_req_cb` (bitmap ≠ 0); self-scan PA match | `bis_sync_req_cb` (bitmap = 0); `broadcast_sink_reset` | | `FLAG_BROADCAST_RESYNC_PENDING` | `bis_sync_req_cb` before `_stop` (bitmap change while streaming) | `stream_stopped_cb` after driving the re-sync; `_stop` failure; `broadcast_sink_reset` | +| `FLAG_BROADCAST_STOP_PENDING` | `broadcast_stream_stopped_cb` (last BIS stops) | `stopped_cb`; `broadcast_sink_reset` | | `FLAG_BROADCAST_SYNCING` | `check_sync_broadcast` after `_sync` returns OK | `stream_started_cb`; `stream_stopped_cb` | | `FLAG_BROADCAST_SYNCED` | `stream_started_cb` | `stream_stopped_cb` | @@ -161,9 +162,9 @@ BASE_RECEIVED && BROADCAST_SYNCABLE +-----+-----+ | | | BIG drops while PA gone | v - +---------> stream_stopped_cb + !PA_SYNCED + +--> stream_stopped_cb (set STOP_PENDING, !PA_SYNCED) | - | _delete + broadcast_sink_reset + | stopped_cb: _delete + broadcast_sink_reset v [end] ``` @@ -171,8 +172,8 @@ BASE_RECEIVED && BROADCAST_SYNCABLE Key invariants: - **PA loss does NOT tear down a running BIS.** Per BASS § 3.2.1.6 / § 3.2.1.9, `PA_Sync_State` and `BIS_Sync_State` are independent. While BIS is streaming/syncing, `broadcast_pa_lost` only notifies the assistant (`PA_Sync_State = 0x00`) and clears PA-only local state (`sync_handle`, `FLAG_PA_SYNCED`); the BIG keeps running and audio continues to flow. -- **PA loss with BIS idle tears down the sink.** The sink is bound to the now-dead sync handle and its cached BASE / BIGInfo are stale. `broadcast_pa_lost` calls `_delete` and clears `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The assistant's subscription (`requested_bis_sync`, `FLAG_BROADCAST_SYNC_REQUESTED`, `FLAG_BROADCAST_CODE_RECEIVED`) is preserved so the next PA sync re-creates a fresh sink and resumes streaming. -- **Sink deletion happens in `stream_stopped_cb` when both PA and BIS are gone.** Triggers: assistant unsubscribes via `Modify Source bis_sync = 0`, or the broadcaster stops the BIG while PA is already gone. +- **PA loss with BIS idle tears down the sink.** The sink is bound to the now-dead sync handle and its cached BASE / BIGInfo are stale. `broadcast_pa_lost` calls `_delete` (unless `FLAG_BROADCAST_STOP_PENDING` is set, in which case `stopped_cb` deletes after `bis_sync` clear) and clears `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The assistant's subscription (`requested_bis_sync`, `FLAG_BROADCAST_SYNC_REQUESTED`, `FLAG_BROADCAST_CODE_RECEIVED`) is preserved so the next PA sync re-creates a fresh sink and resumes streaming. +- **Sink deletion happens in `stopped_cb` (the `stopped` sink callback) once BASS has cleared `bis_sync`, when PA is gone.** `broadcast_stream_stopped_cb` of the last BIS only sets `FLAG_BROADCAST_STOP_PENDING` (and re-`_sync`s on a pending bitmap change) — it does not delete, because deleting there races `rem_src` while `bis_sync` is still non-zero. Triggers: assistant unsubscribes via `Modify Source bis_sync = 0`, or the broadcaster stops the BIG while PA is already gone. - `bis_sync_req_cb` going `X → 0` (Assistant pause) only issues `_stop`, never `_delete`. Going `X → Y` (BIS bitmap switch) likewise only `_stop`s; the next `check_sync_broadcast` (called from `stream_stopped_cb` when PA still synced) re-`_sync`s the same object. - `pa_sync_term_req_cb` issues the HCI Periodic Advertising Terminate Sync but does **not** clear `broadcast_sink.sync_handle`. Cleanup runs from `BLE_GAP_EVENT_PERIODIC_SYNC_LOST` → `broadcast_pa_lost`. Resetting the handle early would make that gate miss. @@ -181,17 +182,17 @@ Key invariants: | Event | Action | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | | **PA lost while BIS active** (broadcaster moved mid-stream) | Set `PA_Sync_State = 0x00`; clear PA-only state (`sync_handle`, `FLAG_PA_SYNCED`). Sink + BIS untouched; BIG keeps running. | -| **PA lost while BIS idle** (Assistant `PA_Sync = 0`, or PA dropped after Assistant paused BIS) | Set `PA_Sync_State = 0x00` (skipped if BASS already updated it in-place); clear PA-only state; `_delete` the sink and clear `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The next PA sync starts from a clean sink and lets the lib redeliver BASE / BIGInfo. | +| **PA lost while BIS idle** (Assistant `PA_Sync = 0`, or PA dropped after Assistant paused BIS) | Set `PA_Sync_State = 0x00` (skipped if BASS already updated it in-place); clear PA-only state; unless `FLAG_BROADCAST_STOP_PENDING` is set (BIG teardown in flight — `stopped_cb` deletes after `bis_sync` clear), `_delete` the sink and clear `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The next PA sync starts from a clean sink and lets the lib redeliver BASE / BIGInfo. | | **Modify Source `bis_sync = 0`** (PA still synced) | `bis_sync_req_cb` clears `FLAG_BROADCAST_SYNC_REQUESTED` then `_stop`s the BIG. Sink retained. | | **Modify Source bitmap change** (PA still synced, streaming) | Update `requested_bis_sync` + `FLAG_BROADCAST_SYNC_REQUESTED` + set `FLAG_BROADCAST_RESYNC_PENDING`, then `_stop`. `stream_stopped_cb` clears the flag and re-`_sync`s with the new bitmap. | | **BIG drops while PA still synced** (e.g. broadcaster pause) | `stream_stopped_cb` clears SYNCED/SYNCING and exposes the loss via `BIS_Sync_State`. `FLAG_BROADCAST_RESYNC_PENDING` is not set, so no auto-retry — per BASS § 3.2.1.9 the assistant drives recovery via Modify Source. | -| **BIG drops after PA lost** (broadcaster turned off) | `stream_stopped_cb` of the last active stream sees `!PA_SYNCED` → `_delete` + `broadcast_sink_reset`. Multi-BIS: earlier callbacks just decrement `active_streams` so `_delete` is not called while the sink is still in use. | +| **BIG drops after PA lost** (broadcaster turned off) | `broadcast_stream_stopped_cb` of the last active stream sees `!PA_SYNCED` and sets `FLAG_BROADCAST_STOP_PENDING`; `stopped_cb` then runs `_delete` + `broadcast_sink_reset` once BASS has cleared `bis_sync`. Multi-BIS: earlier callbacks just decrement `active_streams` so the flag is not set while the sink is still in use. | | **Assistant Remove Source** | Spec allows only when BIS not synced; lib handles, app sees no special event. | Two recurring patterns that drive the above behavior: - **Update local state before calling `_stop`/`_delete`.** The lib may fire `stream_stopped_cb` synchronously from within `_stop`, so the callback must see the post-stop state. Applies in `bis_sync_req_cb` (updates `requested_bis_sync` + flag before `_stop`) and `broadcast_pa_lost` (no longer calls `_stop`). -- **Sink lifetime is BIS-driven, not PA-driven.** Sink is created on first PA sync and deleted only when the BIG itself stops and PA is also gone. This matches BASS spec's independent PA/BIS state model. +- **Sink lifetime is BIS-driven, not PA-driven.** Sink is created on first PA sync and deleted (from `stopped_cb`, after BASS clears `bis_sync`) only when the BIG itself stops and PA is also gone. This matches BASS spec's independent PA/BIS state model. ### Multi-BIS (stereo) configuration @@ -290,6 +291,7 @@ On PA sync loss: ``` I (xxx) CAP_ACC: [SNK #0] Stream stopped, reason 0x... +I (xxx) CAP_ACC: Broadcast sink stopped, reason 0x... I (xxx) CAP_ACC: PA sync lost: sync_handle ... reason 0x... I (xxx) CAP_ACC: Scanning for broadcast source... ``` diff --git a/examples/bluetooth/esp_ble_audio/cap/acceptor/main/cap_acceptor_broadcast.c b/examples/bluetooth/esp_ble_audio/cap/acceptor/main/cap_acceptor_broadcast.c index ed54dc2e58a..b04e8b289bf 100644 --- a/examples/bluetooth/esp_ble_audio/cap/acceptor/main/cap_acceptor_broadcast.c +++ b/examples/bluetooth/esp_ble_audio/cap/acceptor/main/cap_acceptor_broadcast.c @@ -29,6 +29,11 @@ enum broadcast_flag { FLAG_BROADCAST_SYNCING, FLAG_BROADCAST_SYNCED, FLAG_BROADCAST_RESYNC_PENDING, + /* Set when the last BIS stream stops; cleared in stopped_cb. + * Bridges the window where EPs are IDLE but big_stopped has not yet cleared + * BASS bis_sync — pa_lost must not rem_src/delete during that window. + */ + FLAG_BROADCAST_STOP_PENDING, FLAG_BASE_RECEIVED, FLAG_PA_SYNCING, FLAG_PA_SYNCED, @@ -201,6 +206,7 @@ static void broadcast_sink_reset(void) flag_clear(FLAG_BROADCAST_CODE_RECEIVED); flag_clear(FLAG_BROADCAST_SYNC_REQUESTED); flag_clear(FLAG_BROADCAST_RESYNC_PENDING); + flag_clear(FLAG_BROADCAST_STOP_PENDING); flag_clear(FLAG_SCANNING); #if CONFIG_EXAMPLE_SCAN_SELF @@ -330,8 +336,6 @@ static void broadcast_stream_started_cb(esp_ble_audio_bap_stream_t *stream) static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason) { - esp_err_t err; - ESP_LOGI(TAG, "[SNK #%u] Stream stopped, reason 0x%02x", broadcast_stream_idx(stream), reason); @@ -349,21 +353,14 @@ static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint flag_clear(FLAG_BROADCAST_SYNCING); flag_clear(FLAG_BROADCAST_SYNCED); + /* Mark BIG teardown in progress until stopped_cb runs + * (after update_recv_state_big_cleared). Do not delete here — that + * races rem_src while bis_sync is still non-zero. + */ + flag_set(FLAG_BROADCAST_STOP_PENDING); if (flag_test(FLAG_PA_SYNCED) == false) { - /* Both PA and BIS are gone — no path to recover BIGInfo, so the - * sink can no longer drive a new BIG sync. Delete it and clear all - * state. - */ - if (broadcast_sink.sink) { - err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink.sink); - if (err) { - ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); - return; - } - } - - broadcast_sink_reset(); + /* Both PA and BIS are gone — stopped_cb deletes. */ return; } @@ -378,6 +375,35 @@ static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint } } +static void stopped_cb(esp_ble_audio_bap_broadcast_sink_t *sink, + uint8_t reason) +{ + esp_err_t err; + + ESP_LOGI(TAG, "Broadcast sink stopped, reason 0x%02x", reason); + + flag_clear(FLAG_BROADCAST_STOP_PENDING); + + /* Keep the sink when PA is still synced (assistant pause, BIS bitmap + * resync, or spontaneous BIG drop with PA alive). + */ + if (flag_test(FLAG_PA_SYNCED)) { + return; + } + + if (broadcast_sink.sink == NULL) { + return; + } + + err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink.sink); + if (err) { + ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); + return; + } + + broadcast_sink_reset(); +} + static void broadcast_stream_recv_cb(esp_ble_audio_bap_stream_t *stream, const esp_ble_iso_recv_info_t *info, const uint8_t *data, uint16_t len) @@ -740,14 +766,15 @@ void broadcast_pa_lost(uint16_t sync_handle) flag_clear(FLAG_PA_SYNCED); flag_clear(FLAG_PA_SYNCING); - /* BIS still active → BIG keeps running per § 3.2.1.9, leave the sink; - * stream_stopped_cb will tear it down when BIS eventually stops. + /* BIS still active or BIG teardown pending → leave the sink; + * stopped_cb will tear it down when bis_sync is clear. * Otherwise the sink is bound to a dead PA handle and its cached * BASE / BIGInfo are stale: delete the sink and clear the PA-derived * flags so the next PA sync creates a fresh sink and lets the lib * redeliver BASE / BIGInfo. */ - if (flag_test(FLAG_BROADCAST_SYNCING) || flag_test(FLAG_BROADCAST_SYNCED)) { + if (flag_test(FLAG_BROADCAST_SYNCING) || flag_test(FLAG_BROADCAST_SYNCED) || + flag_test(FLAG_BROADCAST_STOP_PENDING)) { return; } @@ -789,6 +816,7 @@ int cap_acceptor_broadcast_init(void) static esp_ble_audio_bap_broadcast_sink_cb_t broadcast_sink_cbs = { .base_recv = base_recv_cb, .syncable = syncable_cb, + .stopped = stopped_cb, }; static esp_ble_audio_bap_stream_ops_t broadcast_stream_ops = { .started = broadcast_stream_started_cb, diff --git a/examples/bluetooth/esp_ble_audio/tmap/bmr/README.md b/examples/bluetooth/esp_ble_audio/tmap/bmr/README.md index 641d8e7887f..c1c74256c64 100644 --- a/examples/bluetooth/esp_ble_audio/tmap/bmr/README.md +++ b/examples/bluetooth/esp_ble_audio/tmap/bmr/README.md @@ -59,7 +59,7 @@ For `esp32s31`, replace the chip overlay accordingly. 5. On `PA_SYNC` success, cancel scanning, log the sync handle, and create the broadcast sink for the sync handle and broadcast ID. 6. The `base_recv` callback extracts the BIS index bitfield masked by the available stream count; the `syncable` callback then calls `esp_ble_audio_bap_broadcast_sink_sync` with the stream pointer array. 7. Per-stream `started` callback resets RX metrics; `recv` callback feeds each SDU into `example_audio_rx_metrics_on_recv` (tracking valid/error/lost/zero-length counts); `stopped` logs the reason. -8. On `PA_SYNC_LOST` matching the active sync handle, delete the broadcast sink and re-enter scanning. +8. On `PA_SYNC_LOST` matching the active sync handle, the sink is deleted — by `broadcast_sink_stopped_cb` once BASS clears `bis_sync` (deleting from `stream_stopped_cb` races `rem_src` while `bis_sync` is still set), or directly here if the sink never reached BIG sync — and scanning resumes. ## Expected Log @@ -90,6 +90,7 @@ I (xxx) TMAP_BMR: [SNK #0] Stream started Stop / sync loss: ``` I (xxx) TMAP_BMR: [SNK #0] Stream stopped, reason 0x.. +I (xxx) TMAP_BMR: Broadcast sink stopped, reason 0x.. I (xxx) TMAP_BMR: PA sync lost: sync_handle .. reason 0x.. I (xxx) TMAP_BMR: PA sync .. lost with reason .. ``` diff --git a/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c b/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c index 2828bfb3c08..249bf79f4e2 100644 --- a/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c +++ b/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c @@ -21,6 +21,10 @@ static bool tmap_bms_found; static esp_ble_audio_bap_broadcast_sink_t *broadcast_sink; static uint32_t bcast_id; +/* True after a BIS stream stops until broadcast_sink_stopped_cb runs — + * pa_lost must not delete in that window (EPs idle, bis_sync not yet cleared). + */ +static bool stop_pending; static esp_ble_audio_bap_stream_t streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; static esp_ble_audio_bap_stream_t *streams_p[ARRAY_SIZE(streams)]; @@ -72,6 +76,11 @@ static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason { ESP_LOGI(TAG, "[SNK #%d] Stream stopped, reason 0x%02x", stream_index(stream), reason); + /* Sink delete is deferred to broadcast_sink_stopped_cb (after BASS + * bis_sync clear). Deleting while EPs are idle but bis_sync is still + * set races rem_src and leaves WRNs. + */ + stop_pending = true; } static void stream_recv_cb(esp_ble_audio_bap_stream_t *stream, @@ -129,9 +138,35 @@ static void syncable_cb(esp_ble_audio_bap_broadcast_sink_t *sink, } } +static void broadcast_sink_stopped_cb(esp_ble_audio_bap_broadcast_sink_t *sink, + uint8_t reason) +{ + esp_err_t err; + + ESP_LOGI(TAG, "Broadcast sink stopped, reason 0x%02x", reason); + + stop_pending = false; + + /* Called from big_stopped after update_recv_state_big_cleared(), so + * BASS bis_sync is already 0 and rem_src inside delete can succeed. + */ + if (broadcast_sink == NULL) { + return; + } + + err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); + if (err) { + ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err); + return; + } + + broadcast_sink = NULL; +} + static esp_ble_audio_bap_broadcast_sink_cb_t broadcast_sink_cbs = { .base_recv = base_recv_cb, .syncable = syncable_cb, + .stopped = broadcast_sink_stopped_cb, }; static esp_ble_audio_bap_scan_delegator_cb_t scan_delegator_cbs; @@ -254,6 +289,8 @@ void bap_broadcast_pa_sync(esp_ble_audio_gap_app_event_t *event) void bap_broadcast_pa_lost(esp_ble_audio_gap_app_event_t *event) { + esp_err_t err; + if (sync_handle == event->pa_sync_lost.sync_handle) { ESP_LOGI(TAG, "PA sync %u lost with reason %u", sync_handle, event->pa_sync_lost.reason); @@ -261,9 +298,17 @@ void bap_broadcast_pa_lost(esp_ble_audio_gap_app_event_t *event) sync_handle = PA_SYNC_HANDLE_INIT; pa_syncing = false; /* allow the rescan below to sync a fresh broadcaster */ - if (broadcast_sink != NULL) { - esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); - broadcast_sink = NULL; + /* Prefer delete from broadcast_sink_stopped_cb after bis_sync clear. + * Skip while BIG teardown is in flight; if the sink never reached + * BIG sync, delete here. + */ + if (broadcast_sink != NULL && !stop_pending) { + err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); + if (err) { + ESP_LOGW(TAG, "Sink delete deferred to stopped_cb, err %d", err); + } else { + broadcast_sink = NULL; + } } bap_broadcast_sink_scan(); diff --git a/examples/bluetooth/esp_ble_audio/tmap/peripheral/main/bap_unicast_sr.c b/examples/bluetooth/esp_ble_audio/tmap/peripheral/main/bap_unicast_sr.c index 659920c05d6..6867bb769e9 100644 --- a/examples/bluetooth/esp_ble_audio/tmap/peripheral/main/bap_unicast_sr.c +++ b/examples/bluetooth/esp_ble_audio/tmap/peripheral/main/bap_unicast_sr.c @@ -33,7 +33,11 @@ static uint8_t codec_meta[] = static const esp_ble_audio_codec_cap_t lc3_codec_cap = ESP_BLE_AUDIO_CODEC_CAP_LC3(codec_data, codec_meta); -static esp_ble_audio_pacs_cap_t cap = { +static esp_ble_audio_pacs_cap_t cap_sink = { + .codec_cap = &lc3_codec_cap, +}; + +static esp_ble_audio_pacs_cap_t cap_source = { .codec_cap = &lc3_codec_cap, }; @@ -502,7 +506,7 @@ int bap_unicast_sr_init(void) #if CONFIG_BT_PAC_SNK /* Register CT required capabilities */ - err = esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SINK, &cap); + err = esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SINK, &cap_sink); if (err) { ESP_LOGE(TAG, "Failed to register pacs capabilities, err %d", err); return err; @@ -529,7 +533,7 @@ int bap_unicast_sr_init(void) #if CONFIG_BT_PAC_SRC /* Register CT required capabilities */ - err = esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SOURCE, &cap); + err = esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SOURCE, &cap_source); if (err) { ESP_LOGE(TAG, "Failed to register pacs capabilities, err %d", err); return err;