Merge branch 'idf/ble_audio_dynamic_svc_v6.1' into 'release/v6.1'

feat(ble_audio): Backport BLE ISO & BLE Audio MRs to release/v6.1

See merge request espressif/esp-idf!52238
This commit is contained in:
Island
2026-09-04 15:59:13 +08:00
226 changed files with 14121 additions and 3677 deletions
+1
View File
@@ -70,6 +70,7 @@ if(CONFIG_IDF_DOC_BUILD OR CONFIG_BT_ENABLED)
add_subdirectory(esp_ble_audio)
list(APPEND srcs ${ble_audio_srcs})
list(APPEND include_dirs ${ble_audio_include_dirs})
list(APPEND ldscripts ${ble_audio_ldscripts})
# BLE profiles (host-agnostic profile cores + shared profile infrastructure)
add_subdirectory(ble_profiles)
+10
View File
@@ -230,6 +230,16 @@ menu "BT Logs"
Enable mesh log encoding via the async transport.
The per-level compression options are configured in:
Settings of BLE Log Compression → BLE Mesh log compression.
config BT_LOG_CRITICAL_ONLY_ISO
bool "ISO: bandwidth-optimized logging"
depends on BT_ISO
select BLE_ISO_COMPRESSED_LOG_ENABLE
default y
help
Enable BLE Audio/ISO log encoding via the async transport.
The per-level compression options are configured in:
Settings of BLE Log Compression → BLE Audio/ISO log compression.
endif # BT_LOG_CRITICAL_ONLY
source "$IDF_PATH/components/bt/common/ble_log/Kconfig.in"
+31 -1
View File
@@ -4,13 +4,30 @@ function(register_ble_audio_libs)
endif()
if(CONFIG_SOC_BLE_AUDIO_SUPPORTED)
add_prebuilt_library(ble_audio "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib/lib/${idf_target}/libble_audio.a")
# Compressed-log builds link the log_compressed/ prebuilt lib (its logs
# call bt_le_audio_lib_compressed_out(), which is only functional under
# the same config); otherwise link the normal lib.
set(ble_audio_lib_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib/lib/${idf_target}")
if(CONFIG_BLE_ISO_COMPRESSED_LOG_ENABLE)
set(ble_audio_lib "${ble_audio_lib_dir}/log_compressed/libble_audio.a")
if(NOT EXISTS "${ble_audio_lib}")
message(FATAL_ERROR
"BLE_ISO_COMPRESSED_LOG_ENABLE is set but the compressed lib is missing:\n"
" ${ble_audio_lib}\n"
"Build and copy it (in lib/ble_audio_development/tools/lib/):\n"
" ./compile.sh ${idf_target} -c && ./copy_lib.sh ${idf_target} -c")
endif()
else()
set(ble_audio_lib "${ble_audio_lib_dir}/libble_audio.a")
endif()
add_prebuilt_library(ble_audio "${ble_audio_lib}")
target_link_libraries(${COMPONENT_LIB} PRIVATE ble_audio)
endif()
endfunction()
set(ble_audio_srcs "" PARENT_SCOPE)
set(ble_audio_include_dirs "" PARENT_SCOPE)
set(ble_audio_ldscripts "" PARENT_SCOPE)
# API headers that are used in the docs are also compiled
# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y
@@ -144,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"
@@ -176,3 +205,4 @@ endif()
# Export the variables to the parent scope
set(ble_audio_srcs "${ble_audio_srcs}" PARENT_SCOPE)
set(ble_audio_include_dirs "${ble_audio_include_dirs}" PARENT_SCOPE)
set(ble_audio_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker.lf" PARENT_SCOPE)
+39
View File
@@ -6,6 +6,9 @@
config BT_AUDIO
bool # hidden
# esp_ble_audio needs esp_ble_iso's headers (<zephyr/*>) 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
@@ -99,6 +102,40 @@ menu "OTS Options"
rsource "host/services/ots/Kconfig.ots.in"
endmenu
config BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY
bool "Place BLE Audio .bss segment in external SPIRAM"
depends on BT_ISO_BSS_SEG_EXTERNAL_MEMORY
default n
help
Place the zero-initialized (.bss) static data of the BLE Audio
profiles in external SPIRAM instead of internal DRAM. Covers both the
esp_ble_audio IDF source files and the .bss of the prebuilt BLE Audio
library (libble_audio.a), the latter relocated via a linker fragment.
Depends on BT_ISO_BSS_SEG_EXTERNAL_MEMORY: the audio layer sits on top
of the ISO/host layer, so its static data is moved to SPIRAM only when
the ISO layer's is, keeping the placement coherent.
Requires SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY. Only .bss is moved;
initialized (.data) statistics stay internal.
config BT_AUDIO_HEAP_EXTERNAL_MEMORY
bool "Place BLE Audio heap allocations in external SPIRAM"
depends on BT_ISO_HEAP_EXTERNAL_MEMORY
default n
help
Route BLE Audio runtime heap allocations to external SPIRAM instead of
internal DRAM. Covers both the esp_ble_audio profiles and the prebuilt
BLE Audio library (libble_audio.a): both allocate via idf_ext_calloc /
idf_ext_malloc, which this switch points at the ISO layer's SPIRAM
helper (heap_caps_*(MALLOC_CAP_SPIRAM)).
Depends on BT_ISO_HEAP_EXTERNAL_MEMORY: the audio allocator hook reuses
the ISO layer's helper, available only when the ISO layer routes to
SPIRAM too.
On SPIRAM exhaustion allocations return NULL (no internal fallback).
config BT_AUDIO_NO_LOG
bool "Disable BLE Audio Lib Debug Log"
default n
@@ -117,6 +154,8 @@ menu "BLE Audio Lib Debug Log Level"
choice BT_AUDIO_LOG_LEVEL
prompt "BLE_AUDIO_LIB_LOG_LEVEL"
default BT_AUDIO_LOG_LEVEL_INFO if BT_LOG_CRITICAL_ONLY_ISO
default BT_AUDIO_LOG_LEVEL_DEBUG if BLE_COMPRESSED_LOG_ENABLE
default BT_AUDIO_LOG_LEVEL_WARNING
depends on !BT_AUDIO_NO_LOG
help
@@ -6,11 +6,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#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)
@@ -45,6 +54,12 @@ esp_err_t esp_ble_audio_bap_unicast_server_unregister(void)
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (bt_le_ascs_deinit()) {
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}
@@ -150,6 +165,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 +744,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)
@@ -754,6 +778,12 @@ esp_err_t esp_ble_audio_bap_scan_delegator_unregister(void)
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (bt_le_bass_deinit()) {
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}
@@ -798,7 +828,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 +849,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 +991,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 +1022,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 +1322,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 +1445,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;
}
@@ -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;
}
@@ -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;
}
@@ -11,8 +11,12 @@
#include <stdbool.h>
#include <errno.h>
#include <zephyr/logging/log.h>
#include "esp_ble_audio_common_api.h"
LOG_MODULE_REGISTER(LEA_API, CONFIG_BT_ISO_LOG_LEVEL);
esp_err_t esp_ble_audio_data_parse(const uint8_t ltv[], size_t size,
bool (*func)(uint8_t type,
const uint8_t *data,
@@ -65,7 +69,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);
}
@@ -127,6 +131,52 @@ unregister_gap:
return ESP_FAIL;
}
esp_err_t esp_ble_audio_common_deinit(const esp_ble_iso_deinit_info_t *info)
{
bool reset_ext_adv = true;
bool reset_pa_sync = true;
int err;
if (info) {
if (!info->reset_acl_conn) {
LOG_WRN("ConnKeepUnsupported");
return ESP_ERR_NOT_SUPPORTED;
}
reset_ext_adv = info->reset_ext_adv;
reset_pa_sync = info->reset_pa_sync;
}
if (bt_le_host_check_idle()) {
return ESP_ERR_INVALID_STATE;
}
err = bt_le_iso_task_deinit();
if (err) {
return ESP_ERR_TIMEOUT;
}
bt_le_audio_deinit();
err = bt_le_host_deinit();
if (err) {
return ESP_ERR_TIMEOUT;
}
if (reset_ext_adv) {
bt_le_ext_adv_state_reset();
}
if (reset_pa_sync) {
bt_le_per_adv_sync_state_reset();
}
bt_le_gatt_app_cb_unregister();
bt_le_gap_app_cb_unregister();
return ESP_OK;
}
esp_err_t esp_ble_audio_common_start(esp_ble_audio_start_info_t *info)
{
int err;
@@ -59,6 +59,10 @@ esp_err_t esp_ble_audio_csip_set_member_unregister(esp_ble_audio_csip_set_member
return ESP_ERR_INVALID_ARG;
}
if (bt_le_csis_deinit(svc_inst)) {
return ESP_FAIL;
}
err = bt_csip_set_member_unregister_safe(svc_inst);
if (err) {
return ESP_FAIL;
@@ -276,7 +280,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 +298,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;
}
@@ -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;
@@ -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)
@@ -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)
@@ -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)
@@ -34,6 +34,10 @@ esp_err_t esp_ble_audio_pacs_unregister(void)
return ESP_FAIL;
}
if (bt_le_pacs_deinit()) {
return ESP_FAIL;
}
return ESP_OK;
}
@@ -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;
}
@@ -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)
@@ -369,6 +376,16 @@ esp_err_t esp_ble_audio_tbs_unregister_bearer(uint8_t bearer_index)
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (bearer_index == ESP_BLE_AUDIO_TBS_GTBS_INDEX) {
if (bt_le_gtbs_deinit()) {
return ESP_FAIL;
}
} else if (bt_le_tbs_deinit(bearer_index)) {
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}
#endif /* CONFIG_BT_TBS */
@@ -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);
@@ -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)
@@ -102,7 +102,17 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle);
#define ESP_BLE_AUDIO_GAP_EVENT_ACL_DISCONNECT BT_LE_GAP_APP_EVENT_ACL_DISCONNECT
/*!< Audio GAP Security Change event */
#define ESP_BLE_AUDIO_GAP_EVENT_SECURITY_CHANGE BT_LE_GAP_APP_EVENT_SECURITY_CHANGE
/** Audio GAP application event structure */
/**
* @brief Audio GAP application event structure
*
* @note Addresses carried by these events are in the **active host's own byte
* order**: on-air/LSB-first under NimBLE, MSB-first under Bluedroid
* (the order every `esp_ble_gap_*` API takes). Feeding one straight back
* to a host API is therefore always correct; comparing one against an
* address the audio layer holds, such as a Broadcast Receive State,
* needs a reversal under Bluedroid. See `struct bt_le_addr` in
* common/app/gap.h for the full convention.
*/
typedef struct bt_le_gap_app_event esp_ble_audio_gap_app_event_t;
/*!< Audio GATT MTU exchange complete event */
@@ -121,24 +131,51 @@ typedef struct {
/**
* @brief Post an application-layer GAP event for audio internal usage.
*
* @note This function is only needed while using NimBLE Host.
* @note NimBLE-only: NimBLE has no global GAP callback, so the layers below
* see only what the application forwards here. An event left out is
* dropped silently. Bluedroid's adapter hooks the stack directly.
*
* @param type Event type.
* @param param Event parameters.
* @note Forward every event below that the application's flows can produce.
* Forwarding an unused one is harmless; omitting one leaves the
* matching state uncreated and fails later and elsewhere:
* - BLE_GAP_EVENT_EXT_DISC extended advertising report
* - BLE_GAP_EVENT_CONNECT ACL established or failed
* - BLE_GAP_EVENT_DISCONNECT ACL closed
* - BLE_GAP_EVENT_ENC_CHANGE encryption changed
* - BLE_GAP_EVENT_PERIODIC_SYNC PA sync established
* - BLE_GAP_EVENT_PERIODIC_SYNC_LOST PA sync lost
* - BLE_GAP_EVENT_PERIODIC_REPORT periodic advertising report
* - BLE_GAP_EVENT_PERIODIC_TRANSFER PA sync received over PAST
* - BLE_GAP_EVENT_PERIODIC_TRANSFER_V2 as above, PAwR
*
* GATT events go to esp_ble_audio_gatt_app_post_event() instead.
*
* @note Shares a sink with esp_ble_iso_gap_app_post_event(): forward a given
* event through exactly one of them or it is delivered twice.
*
* @param type Event type, i.e. ble_gap_event::type.
* @param param Event parameters, i.e. the struct ble_gap_event pointer.
*/
void esp_ble_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
/**
* @brief Post an application-layer GATT event for audio internal usage.
*
* @note NimBLE-only. Bluedroid dispatches GATT events directly inside the
* adapter (BTA callbacks), so no app-level post is needed. This
* declaration is hidden from Bluedroid builds to make misuse a
* compile-time error.
* @note NimBLE-only: Bluedroid dispatches GATT events inside the adapter, so
* no app-level post is needed. Hidden from Bluedroid builds to make
* misuse a compile-time error.
*
* @param type Event type.
* @param param Event parameters.
* @note Forward all four below here rather than through
* esp_ble_audio_gap_app_post_event() - GATT has no ISO counterpart.
* Omitting one is silent; ASCS and PACS simply never react.
* - BLE_GAP_EVENT_MTU ATT MTU exchanged
* - BLE_GAP_EVENT_SUBSCRIBE peer wrote a CCC descriptor
* - BLE_GAP_EVENT_NOTIFY_RX notification or indication received
* - BLE_GAP_EVENT_NOTIFY_TX notification or indication sent
*
* @param type Event type, i.e. ble_gap_event::type.
* @param param Event parameters, i.e. the struct ble_gap_event pointer.
*/
void esp_ble_audio_gatt_app_post_event(uint8_t type, void *param);
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
@@ -163,6 +200,30 @@ typedef struct {
uint8_t dummy; /*!< Dummy field to avoid empty struct */
} esp_ble_audio_start_info_t;
/**
* @brief Deinitialize BLE Audio.
*
* The only precondition is that the application has stopped its streams: a live
* stream always has a CIS under it, so one still up makes this return
* ESP_ERR_INVALID_STATE with each offending item logged at ERROR level.
*
* @note Registered profiles need no prior unregister - this releases them
* itself, while the host lock and GATT application are still alive.
* The per-profile esp_ble_audio_*_unregister() calls are for dropping
* one profile while audio stays up.
*
* @note Also releases the ISO layer, so esp_ble_iso_common_deinit() must not
* be called as well. @p info has the same meaning as there.
*
* @param info Which ISO-layer records to clear, or NULL to clear all.
*
* @return ESP_OK on success,
* ESP_ERR_INVALID_STATE if something is still active,
* ESP_ERR_TIMEOUT if the ISO task could not be stopped.
* Nothing is released unless ESP_OK is returned.
*/
esp_err_t esp_ble_audio_common_deinit(const esp_ble_iso_deinit_info_t *info);
/**
* @brief Start BLE Audio services.
*
@@ -29,6 +29,8 @@ extern "C" {
int bt_le_bluedroid_audio_init(void);
void bt_le_bluedroid_audio_deinit(void);
int bt_le_bluedroid_media_proxy_pl_init(void);
int bt_le_bluedroid_vcp_vol_rend_init(void);
@@ -16,6 +16,8 @@ extern "C" {
int bt_le_bluedroid_ascs_init(void);
int bt_le_bluedroid_ascs_deinit(void);
int bt_le_bluedroid_ascs_start(void);
#ifdef __cplusplus
@@ -16,6 +16,8 @@ extern "C" {
int bt_le_bluedroid_bass_init(void);
int bt_le_bluedroid_bass_deinit(void);
int bt_le_bluedroid_bass_start(void);
#ifdef __cplusplus
@@ -14,8 +14,12 @@
extern "C" {
#endif
void bt_le_bluedroid_csis_state_reset(void);
int bt_le_bluedroid_csis_init(void *csis_svc, uint8_t count);
int bt_le_bluedroid_csis_deinit(void *csis_svc);
int bt_le_bluedroid_csis_start(void);
#ifdef __cplusplus
@@ -14,6 +14,8 @@
extern "C" {
#endif
void bt_le_bluedroid_mcs_state_reset(void);
int bt_le_bluedroid_gmcs_init(void);
int bt_le_bluedroid_gmcs_start(void);
@@ -14,6 +14,8 @@
extern "C" {
#endif
void bt_le_bluedroid_mics_state_reset(void);
int bt_le_bluedroid_mics_init(void *micp_inc);
int bt_le_bluedroid_mics_start(void);
@@ -16,6 +16,8 @@ extern "C" {
int bt_le_bluedroid_pacs_init(void);
int bt_le_bluedroid_pacs_deinit(void);
int bt_le_bluedroid_pacs_start(void);
#ifdef __cplusplus
@@ -16,10 +16,14 @@ extern "C" {
int bt_le_bluedroid_gtbs_init(void);
int bt_le_bluedroid_gtbs_deinit(void);
int bt_le_bluedroid_gtbs_start(void);
int bt_le_bluedroid_tbs_init(void);
int bt_le_bluedroid_tbs_deinit(uint8_t bearer_index);
int bt_le_bluedroid_tbs_start(void);
#ifdef __cplusplus
@@ -14,6 +14,8 @@
extern "C" {
#endif
void bt_le_bluedroid_vcs_state_reset(void);
int bt_le_bluedroid_vcs_init(void *vcp_inc);
int bt_le_bluedroid_vcs_start(void);
@@ -162,10 +162,14 @@ enum {
void bt_le_bluedroid_audio_gatts_init(void);
void bt_le_bluedroid_audio_gatts_deinit(void);
int bt_le_bluedroid_set_svc_in_progress(uint8_t value);
int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc);
int bt_le_bluedroid_svc_deinit(struct bt_gatt_service *svc);
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc);
#ifdef __cplusplus
@@ -71,6 +71,19 @@ int bt_le_bluedroid_audio_init(void)
bt_le_bluedroid_audio_gatts_init();
#if CONFIG_BT_CSIP_SET_MEMBER
bt_le_bluedroid_csis_state_reset();
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_VCP_VOL_REND
bt_le_bluedroid_vcs_state_reset();
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_MICP_MIC_DEV
bt_le_bluedroid_mics_state_reset();
#endif /* CONFIG_BT_MICP_MIC_DEV */
#if CONFIG_BT_MCS
bt_le_bluedroid_mcs_state_reset();
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_PACS
err = bt_le_bluedroid_pacs_init();
if (err) {
@@ -123,6 +136,16 @@ int bt_le_bluedroid_audio_init(void)
return err;
}
void bt_le_bluedroid_audio_deinit(void)
{
LOG_DBG("[B]AudioDeinit");
/* The services are removed by the BTA_GATTS_AppDeregister() in the ISO
* layer's gatt deinit - GATT_Deregister() stops every service the app
* owns - so only this layer's own hook is dropped here. */
bt_le_bluedroid_audio_gatts_deinit();
}
#if CONFIG_BT_CSIP_SET_MEMBER
static int bluedroid_gatt_csis_init(struct bt_le_audio_start_info *info,
struct bt_gatt_service **inc_csis_svc)
@@ -38,6 +38,21 @@ int bt_le_bluedroid_ascs_init(void)
return bt_le_bluedroid_svc_init(ascs_svc);
}
int bt_le_bluedroid_ascs_deinit(void)
{
struct bt_gatt_service *ascs_svc;
LOG_DBG("[B]AscsDeinit");
ascs_svc = lib_ascs_svc_get();
if (!ascs_svc) {
LOG_ERR("[B]AscsSvcGetFail");
return -ENODEV;
}
return bt_le_bluedroid_svc_deinit(ascs_svc);
}
int bt_le_bluedroid_ascs_start(void)
{
struct bt_gatt_service *ascs_svc;
@@ -38,6 +38,21 @@ int bt_le_bluedroid_bass_init(void)
return bt_le_bluedroid_svc_init(bass_svc);
}
int bt_le_bluedroid_bass_deinit(void)
{
struct bt_gatt_service *bass_svc;
LOG_DBG("[B]BassDeinit");
bass_svc = lib_bap_bass_svc_get();
if (!bass_svc) {
LOG_ERR("[B]BassSvcGetFail");
return -ENODEV;
}
return bt_le_bluedroid_svc_deinit(bass_svc);
}
int bt_le_bluedroid_bass_start(void)
{
struct bt_gatt_service *bass_svc;
@@ -16,13 +16,14 @@
#include "bluedroid/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_CAS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
static struct inc_svc_inst inc_csis_inst;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_svc_inst inc_csis_inst;
struct inc_svc_inst *cas_not_included_inst(void)
{
@@ -16,6 +16,7 @@
#include "bluedroid/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -23,11 +24,16 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#define CSIS_SVC_COUNT CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT
static struct csis_inst {
static BT_AUDIO_EXT_RAM_BSS_ATTR struct csis_inst {
struct bt_gatt_service *svc_p;
} csis_insts[CSIS_SVC_COUNT];
static uint8_t csis_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t csis_svc_count;
void bt_le_bluedroid_csis_state_reset(void)
{
csis_svc_count = 0;
}
int bt_le_bluedroid_csis_init(void *svc, uint8_t count)
{
@@ -57,6 +63,18 @@ int bt_le_bluedroid_csis_init(void *svc, uint8_t count)
return 0;
}
int bt_le_bluedroid_csis_deinit(void *csis_svc)
{
LOG_DBG("[B]CsisDeinit");
if (csis_svc == NULL) {
LOG_ERR("[B]CsisSvcNull");
return -EINVAL;
}
return bt_le_bluedroid_svc_deinit(csis_svc);
}
int bt_le_bluedroid_csis_start(void)
{
int err;
@@ -16,6 +16,7 @@
#include "bluedroid/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -29,7 +30,7 @@ LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL);
/* GMCS includes a single secondary OTS instance. Mirrors the CAS->CSIS included-
* secondary pattern: create the secondary before the primary so the include
* declaration in the GMCS table resolves to OTS's live handle. */
static struct inc_svc_inst inc_ots_inst;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_svc_inst inc_ots_inst;
struct inc_svc_inst *gmcs_not_included_inst(void)
{
@@ -61,12 +62,28 @@ static int bluedroid_gmcs_ots_init(void)
static int bluedroid_gmcs_ots_start(void)
{
/* svc_p comes from bluedroid_gmcs_ots_init(), which only runs once the app
* calls media_proxy_pl_init(); NULL means MCS was never registered. The
* count-guarded loops in vcs.c / mics.c / csis.c skip for the same reason. */
if (inc_ots_inst.svc_p == NULL) {
LOG_DBG("[B]GmcsOtsNotReg");
return 0;
}
bt_le_bluedroid_set_svc_in_progress(OTS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(inc_ots_inst.svc_p);
}
#endif /* CONFIG_BT_OTS */
void bt_le_bluedroid_mcs_state_reset(void)
{
#if CONFIG_BT_OTS
inc_ots_inst.svc_p = NULL;
inc_ots_inst.included = false;
#endif /* CONFIG_BT_OTS */
}
int bt_le_bluedroid_gmcs_init(void)
{
struct bt_gatt_service *gmcs_svc;
@@ -150,6 +167,7 @@ int bt_le_bluedroid_mcs_init(void)
* (created during gmcs_init). Reset the flag so it re-resolves per instance. */
inc_ots_inst.included = false;
#endif /* CONFIG_BT_OTS */
err = bt_le_bluedroid_svc_init(&list[i]);
if (err) {
return err;
@@ -18,15 +18,17 @@
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
#define AICS_INST_COUNT CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT
static uint8_t inc_aics_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
static struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
struct inc_svc_inst *mics_not_included_inst(void)
{
@@ -39,6 +41,11 @@ struct inc_svc_inst *mics_not_included_inst(void)
return NULL;
}
void bt_le_bluedroid_mics_state_reset(void)
{
inc_aics_svc_count = 0;
}
int bt_le_bluedroid_mics_init(void *micp_inc)
{
struct bt_micp_included *micp_included;
@@ -38,6 +38,21 @@ int bt_le_bluedroid_pacs_init(void)
return bt_le_bluedroid_svc_init(pacs_svc);
}
int bt_le_bluedroid_pacs_deinit(void)
{
struct bt_gatt_service *pacs_svc;
LOG_DBG("[B]PacsDeinit");
pacs_svc = lib_pacs_svc_get();
if (!pacs_svc) {
LOG_ERR("[B]PacsSvcGetFail");
return -ENODEV;
}
return bt_le_bluedroid_svc_deinit(pacs_svc);
}
int bt_le_bluedroid_pacs_start(void)
{
struct bt_gatt_service *pacs_svc;
@@ -36,6 +36,21 @@ int bt_le_bluedroid_gtbs_init(void)
return bt_le_bluedroid_svc_init(gtbs_svc);
}
int bt_le_bluedroid_gtbs_deinit(void)
{
struct bt_gatt_service *gtbs_svc;
LOG_DBG("[B]GtbsDeinit");
gtbs_svc = lib_gtbs_svc_get();
if (!gtbs_svc) {
LOG_ERR("[B]GtbsSvcGetFail");
return -ENODEV;
}
return bt_le_bluedroid_svc_deinit(gtbs_svc);
}
int bt_le_bluedroid_gtbs_start(void)
{
struct bt_gatt_service *gtbs_svc;
@@ -83,6 +98,29 @@ int bt_le_bluedroid_tbs_init(void)
return 0;
}
int bt_le_bluedroid_tbs_deinit(uint8_t bearer_index)
{
#if CONFIG_BT_TBS_BEARER_COUNT > 0
struct bt_gatt_service *tbs_list;
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
LOG_DBG("[B]TbsDeinit[%u]", bearer_index);
#if CONFIG_BT_TBS_BEARER_COUNT > 0
if (bearer_index < CONFIG_BT_TBS_BEARER_COUNT) {
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
return 0;
}
return bt_le_bluedroid_svc_deinit(&tbs_list[bearer_index]);
}
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
LOG_ERR("[B]TbsInvBearerIdx[%u]", bearer_index);
return -EINVAL;
}
int bt_le_bluedroid_tbs_start(void)
{
struct bt_gatt_service *tbs_list;
@@ -17,6 +17,7 @@
#include "bluedroid/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -25,11 +26,11 @@ LOG_MODULE_REGISTER(LEA_VCS, CONFIG_BT_ISO_LOG_LEVEL);
#define VOCS_INST_COUNT CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT
#define AICS_INST_COUNT CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT
static uint8_t inc_vocs_svc_count;
static uint8_t inc_aics_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_vocs_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
static struct inc_svc_inst inc_vocs_insts[VOCS_INST_COUNT];
static struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_svc_inst inc_vocs_insts[VOCS_INST_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
struct inc_svc_inst *vcs_not_included_inst(void)
{
@@ -48,6 +49,12 @@ struct inc_svc_inst *vcs_not_included_inst(void)
return NULL;
}
void bt_le_bluedroid_vcs_state_reset(void)
{
inc_vocs_svc_count = 0;
inc_aics_svc_count = 0;
}
int bt_le_bluedroid_vcs_init(void *vcp_inc)
{
struct bt_vcp_included *vcp_included;
@@ -75,13 +82,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);
@@ -100,7 +104,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);
@@ -115,6 +119,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);
@@ -23,14 +23,48 @@
#include "bluedroid/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
LOG_MODULE_REGISTER(LEA_GSRV, CONFIG_BT_ISO_LOG_LEVEL);
static uint8_t svc_in_progress;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t svc_in_progress;
static uint16_t inc_svc_handle;
static uint16_t svc_handle;
static uint16_t chrc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_svc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t svc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t chrc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t aics_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t csis_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t vocs_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t mcs_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t ots_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t tbs_count;
/* Every service svc_init() created, so the deinit can delete each one:
* BTA_GATTS_AppDeregister() clears BTA's srvc_cb[] but leaves gatt_cb.sr_reg[],
* which is the table GATTS_StartService() checks on the next init. */
static BT_AUDIO_EXT_RAM_BSS_ATTR struct bt_gatt_service *created_svcs[CONFIG_BT_GATT_MAX_SR_PROFILES];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t created_svc_count;
static void svc_created_record(struct bt_gatt_service *svc)
{
for (uint8_t i = 0; i < created_svc_count; i++) {
if (created_svcs[i] == svc) {
/* Re-created in place: gtbs_init has no "already added" guard, and
* the tbs/mcs loops re-enter for each newly registered instance. */
return;
}
}
if (created_svc_count == ARRAY_SIZE(created_svcs)) {
/* Same ceiling as the stack's, so reaching it means the static_assert on
* TOTAL_SERVICE_COUNT is out of date rather than that we over-created. */
LOG_ERR("[B]SvcRecordFull[%u]", svc_in_progress);
return;
}
created_svcs[created_svc_count++] = svc;
}
static bool is_primary_svc(void)
{
@@ -172,15 +206,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);
}
@@ -189,9 +222,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,
@@ -201,6 +233,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;
}
@@ -213,14 +252,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 = {
@@ -232,9 +270,61 @@ static struct gatts_svc_cb svc_cb = {
void bt_le_bluedroid_audio_gatts_init(void)
{
svc_in_progress = 0;
inc_svc_handle = 0;
svc_handle = 0;
chrc_handle = 0;
created_svc_count = 0;
aics_count = 0;
csis_count = 0;
vocs_count = 0;
mcs_count = 0;
ots_count = 0;
tbs_count = 0;
bt_le_bluedroid_gatts_svc_cb_register(&svc_cb);
}
int bt_le_bluedroid_svc_deinit(struct bt_gatt_service *svc)
{
if (svc == NULL || svc->attrs == NULL) {
LOG_ERR("[B]SvcDeinitNoSvc");
return -EINVAL;
}
if (svc->attrs[0].handle == 0) {
LOG_DBG("[B]SvcDeinitNotCreated");
return 0;
}
LOG_DBG("[B]SvcDeinit[0x%04x]", svc->attrs[0].handle);
BTA_GATTS_DeleteService(svc->attrs[0].handle);
svc->attrs[0].handle = 0;
return 0;
}
void bt_le_bluedroid_audio_gatts_deinit(void)
{
/* Reverse creation order, so an included secondary outlives the primary that
* includes it. Each delete logs upstream's "Active Service Found" at ERROR;
* that is its successful lookup, not a failure. */
while (created_svc_count > 0) {
struct bt_gatt_service *svc = created_svcs[--created_svc_count];
created_svcs[created_svc_count] = NULL;
/* Already-unregistered entries fall out as a no-op inside. */
(void)bt_le_bluedroid_svc_deinit(svc);
}
bt_le_bluedroid_gatts_svc_cb_register(NULL);
}
int bt_le_bluedroid_set_svc_in_progress(uint8_t value)
{
if (value >= MAX_IN_PROGRESS) {
@@ -409,17 +499,6 @@ static struct inc_svc_inst *get_not_included_inst(void)
static uint8_t get_svc_inst_id(uint16_t svc_uuid)
{
/* Note:
* For LE Audio, some service could have multiple instances.
*/
static uint8_t aics_count;
static uint8_t csis_count;
static uint8_t vocs_count;
static uint8_t mcs_count;
static uint8_t ots_count;
static uint8_t tbs_count;
switch (svc_uuid) {
case BT_UUID_AICS_VAL:
return aics_count++;
@@ -455,7 +534,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];
@@ -469,7 +548,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);
@@ -495,7 +574,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);
@@ -560,7 +639,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);
@@ -606,18 +685,29 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
}
}
/* Created, so the deinit has to delete it. attr_count 0 is a service the
* application never registered; svc_init walked nothing. */
if (svc->attr_count > 0 && svc->attrs[0].handle != 0) {
svc_created_record(svc);
}
return 0;
}
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc)
{
assert(svc);
BT_LE_ASSERT(svc);
/* App may not register this svc; skip rather than fail audio_start.
* Unused CAP Acceptor capabilities stop at handle 0, GMAS at NULL
* attrs - only bt_gmap_register() allocates them. */
if (svc->attrs == NULL) {
LOG_DBG("[B]SvcNotReg[%u]", svc_in_progress);
return 0;
}
svc_handle = svc->attrs[0].handle;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
if (svc_handle == 0) {
LOG_DBG("[B]SvcNotInit[%u]", svc_in_progress);
return 0;
@@ -627,7 +717,9 @@ int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc)
BTA_GATTS_StartService(svc_handle, BTA_GATT_TRANSPORT_LE);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]SvcStartFail");
/* Which service and which handle: BTA only says "already started" or
* "not created", neither of which names the caller. */
LOG_ERR("[B]SvcStartFail[%u][0x%04x]", svc_in_progress, svc_handle);
return -1;
}
@@ -28,6 +28,8 @@ extern "C" {
int bt_le_nimble_audio_init(void);
void bt_le_nimble_audio_deinit(void);
int bt_le_nimble_media_proxy_pl_init(void);
int bt_le_nimble_vcp_vol_rend_init(void);
@@ -18,6 +18,8 @@ int bt_le_nimble_ascs_attr_handle_set(void);
int bt_le_nimble_ascs_init(void);
int bt_le_nimble_ascs_deinit(void);
#ifdef __cplusplus
}
#endif
@@ -18,6 +18,8 @@ int bt_le_nimble_bass_attr_handle_set(void);
int bt_le_nimble_bass_init(void);
int bt_le_nimble_bass_deinit(void);
#ifdef __cplusplus
}
#endif
@@ -19,8 +19,12 @@ extern "C" {
int bt_le_nimble_csis_attr_handle_set(void);
void bt_le_nimble_csis_state_reset(void);
int bt_le_nimble_csis_init(void *csis_svc, uint8_t count);
int bt_le_nimble_csis_deinit(void *csis_svc);
#ifdef __cplusplus
}
#endif
@@ -19,6 +19,8 @@ extern "C" {
int bt_le_nimble_gmcs_attr_handle_set(void);
void bt_le_nimble_mcs_state_reset(void);
int bt_le_nimble_gmcs_init(bool ots_included);
int bt_le_nimble_mcs_attr_handle_set(void);
@@ -19,6 +19,8 @@ extern "C" {
int bt_le_nimble_mics_attr_handle_set(void);
void bt_le_nimble_mics_state_reset(void);
int bt_le_nimble_mics_init(void *micp_inc);
#ifdef __cplusplus
@@ -21,6 +21,8 @@ int bt_le_nimble_pacs_attr_handle_set(void);
int bt_le_nimble_pacs_init(void);
int bt_le_nimble_pacs_deinit(void);
#ifdef __cplusplus
}
#endif
@@ -18,10 +18,14 @@ int bt_le_nimble_gtbs_attr_handle_set(void);
int bt_le_nimble_gtbs_init(void);
int bt_le_nimble_gtbs_deinit(void);
int bt_le_nimble_tbs_attr_handle_set(void);
int bt_le_nimble_tbs_init(void);
int bt_le_nimble_tbs_deinit(uint8_t bearer_index);
#ifdef __cplusplus
}
#endif
@@ -16,6 +16,8 @@ extern "C" {
int bt_le_nimble_vcs_attr_handle_set(void);
void bt_le_nimble_vcs_state_reset(void);
int bt_le_nimble_vcs_init(void *vcp_inc);
#ifdef __cplusplus
@@ -20,6 +20,7 @@
#include "nimble/init.h"
#include "common/init.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -159,6 +160,9 @@ LOG_MODULE_REGISTER(LEA_NINIT, CONFIG_BT_ISO_LOG_LEVEL);
_Static_assert(TOTAL_CCCDS_COUNT <= CONFIG_BT_NIMBLE_MAX_CCCDS, "Too small BT_NIMBLE_MAX_CCCDS");
static BT_AUDIO_EXT_RAM_BSS_ATTR bool nimble_svcs_registered;
static BT_AUDIO_EXT_RAM_BSS_ATTR bool nimble_gatts_started;
int bt_le_nimble_audio_init(void)
{
int err = 0;
@@ -169,6 +173,23 @@ int bt_le_nimble_audio_init(void)
return err;
}
#if CONFIG_BT_CSIP_SET_MEMBER
bt_le_nimble_csis_state_reset();
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_VCP_VOL_REND
bt_le_nimble_vcs_state_reset();
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_MICP_MIC_DEV
bt_le_nimble_mics_state_reset();
#endif /* CONFIG_BT_MICP_MIC_DEV */
#if CONFIG_BT_MCS
bt_le_nimble_mcs_state_reset();
#endif /* CONFIG_BT_MCS */
if (nimble_svcs_registered) {
return 0;
}
ble_svc_gap_init();
ble_svc_gatt_init();
@@ -222,6 +243,8 @@ int bt_le_nimble_audio_init(void)
#endif /* CONFIG_BT_HAS */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
nimble_svcs_registered = true;
return err;
}
@@ -442,6 +465,14 @@ int bt_le_nimble_micp_mic_dev_init(void)
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
void bt_le_nimble_audio_deinit(void)
{
LOG_DBG("[N]AudioDeinit");
/* Nothing to release: the services added here are boot-scoped and the defs
* NimBLE holds reference no lib memory, so resources_deinit() is safe. */
}
int bt_le_nimble_audio_start(void *info)
{
struct bt_gatt_service *inc_csis_svc = NULL;
@@ -499,10 +530,17 @@ int bt_le_nimble_audio_start(void *info)
}
#endif /* CONFIG_BT_GMAP */
err = ble_gatts_start();
if (err) {
LOG_ERR("[N]GattsStartFail[%d]", err);
return err;
/* Once per boot: ble_gatts_start() resets the whole ATT database and only
* re-registers defs added since the last start, so calling it again drops
* other owners' services. attr_handle_set() still runs every cycle. */
if (!nimble_gatts_started) {
err = ble_gatts_start();
if (err) {
LOG_ERR("[N]GattsStartFail[%d]", err);
return err;
}
nimble_gatts_started = true;
}
err = nimble_gatt_attr_handle_set();
@@ -26,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -35,9 +36,9 @@ static const ble_uuid16_t ascs_uuid_ase_cp = BLE_UUID16_INIT(BT_UUID_ASCS_ASE_CP
static const ble_uuid16_t ascs_uuid_ase_snk = BLE_UUID16_INIT(BT_UUID_ASCS_ASE_SNK_VAL);
static const ble_uuid16_t ascs_uuid_ase_src = BLE_UUID16_INIT(BT_UUID_ASCS_ASE_SRC_VAL);
static uint16_t ase_control_point_handle;
static uint16_t ase_snk_handle[CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT];
static uint16_t ase_src_handle[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ase_control_point_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ase_snk_handle[CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ase_src_handle[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT];
static struct ble_gatt_svc_def gatt_svc_ascs[] = {
{
@@ -61,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;
}
@@ -78,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];
}
@@ -97,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];
}
@@ -127,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 */
@@ -201,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;
@@ -209,8 +211,8 @@ int bt_le_nimble_ascs_init(void)
LOG_DBG("[N]AscsInit[%u]", chr_count);
gatt_svc_ascs[0].characteristics = calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_ascs[0].characteristics);
gatt_svc_ascs[0].characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(gatt_svc_ascs[0].characteristics);
ascs_svc_add_ase_cp_chr((void *)(gatt_svc_ascs[0].characteristics + 0));
@@ -233,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) {
@@ -242,7 +245,18 @@ 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;
}
int bt_le_nimble_ascs_deinit(void)
{
LOG_DBG("[N]AscsDeinit");
return 0;
}
@@ -26,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -34,8 +35,8 @@ LOG_MODULE_REGISTER(LEA_BASS, CONFIG_BT_ISO_LOG_LEVEL);
static const ble_uuid16_t bass_uuid_control_point = BLE_UUID16_INIT(BT_UUID_BASS_CONTROL_POINT_VAL);
static const ble_uuid16_t bass_uuid_recv_state = BLE_UUID16_INIT(BT_UUID_BASS_RECV_STATE_VAL);
static uint16_t bass_control_point_handle;
static uint16_t bass_recv_state_handle[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t bass_control_point_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t bass_recv_state_handle[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT];
static struct ble_gatt_svc_def gatt_svc_bass[] = {
{
@@ -74,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];
}
@@ -103,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 */
@@ -153,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;
@@ -171,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;
@@ -179,8 +181,8 @@ int bt_le_nimble_bass_init(void)
LOG_DBG("[N]BassInit[%u]", chr_count);
gatt_svc_bass->characteristics = calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_bass->characteristics);
gatt_svc_bass->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(gatt_svc_bass->characteristics);
bass_svc_add_control_point_chr((void *)(gatt_svc_bass->characteristics + 0));
@@ -197,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) {
@@ -206,7 +209,18 @@ 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;
}
int bt_le_nimble_bass_deinit(void)
{
LOG_DBG("[N]BassDeinit");
return 0;
}
@@ -18,6 +18,7 @@
#include "host/ble_gatt.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -26,7 +27,7 @@ LOG_MODULE_REGISTER(LEA_CAS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
extern struct ble_gatt_svc_def *cas_get_included_csis(void *csis_svc_p);
static struct bt_gatt_service *inc_csis_svc;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct bt_gatt_service *inc_csis_svc;
static struct ble_gatt_svc_def *cas_inc_svcs[] = {
NULL,
@@ -28,6 +28,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -39,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)
@@ -47,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 \
@@ -62,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)
@@ -75,7 +76,7 @@ static const ble_uuid16_t csis_uuid_set_lock = BLE_UUID16_INIT(BT_UUID_CSIS_SET_
static const ble_uuid16_t csis_uuid_rank = BLE_UUID16_INIT(BT_UUID_CSIS_RANK_VAL);
static const ble_uuid16_t csis_uuid_set_name = BLE_UUID16_INIT(BT_UUID_CSIS_SET_NAME_VAL);
static struct csis_inst {
static BT_AUDIO_EXT_RAM_BSS_ATTR struct csis_inst {
struct bt_gatt_service *svc_p;
uint16_t sirk_handle;
uint16_t set_size_handle;
@@ -84,10 +85,13 @@ static struct csis_inst {
uint16_t set_name_handle;
} csis_insts[CSIS_SVC_COUNT];
static uint8_t csis_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t csis_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t csis_reg_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR bool csis_registered;
/* Extra one for terminating the CSIS service array */
static struct ble_gatt_svc_def gatt_svc_csis[CSIS_SVC_COUNT + 1];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def gatt_svc_csis[CSIS_SVC_COUNT + 1];
struct ble_gatt_svc_def *cas_get_included_csis(void *csis_svc_p)
{
@@ -123,7 +127,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++) {
@@ -134,7 +138,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;
@@ -160,8 +164,15 @@ 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);
/* Zero means no registration round ever covered this instance, so it is
* absent from the ATT database and there is no range to anchor. Happens
* when CSIS is first registered after the boot's ble_gatts_start(). */
if (csis_insts[i].sirk_handle < 2) {
LOG_ERR("[N]CsisNoAttrHdl[%u]", i);
return -1;
}
/* 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 */
@@ -180,7 +191,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;
}
@@ -244,8 +255,8 @@ static void csis_svc_init(struct csis_inst *inst,
svc->uuid = &csis_uuid_svc.u;
svc->includes = NULL;
svc->characteristics = calloc(CSIS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
svc->characteristics = bt_le_ext_calloc(CSIS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
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
@@ -254,7 +265,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;
}
@@ -291,11 +302,17 @@ 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);
}
void bt_le_nimble_csis_state_reset(void)
{
csis_svc_count = 0;
}
int bt_le_nimble_csis_init(void *svc, uint8_t count)
{
bool csis_added = false;
int rc;
LOG_DBG("[N]CsisInit[%u]", count);
@@ -305,11 +322,27 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count)
return -1;
}
/* The attribute table is built by the boot's single ble_gatts_start(), so a
* different instance count now needs a table this boot cannot produce. Fail
* here, not later on a handle NimBLE never assigned. */
if (csis_registered && count != csis_reg_count) {
LOG_ERR("[N]CsisCountChanged[%u][%u]", csis_reg_count, count);
return -1;
}
csis_svc_count = count;
/* Refreshed every cycle: the lib frees and reallocates its service objects,
* while the NimBLE defs below are built once and stay in the database. */
for (size_t i = 0; i < csis_svc_count; i++) {
csis_insts[i].svc_p = ((struct bt_gatt_service **)svc)[i];
}
if (csis_registered) {
return 0;
}
for (size_t i = 0; i < csis_svc_count; i++) {
csis_svc_init(&csis_insts[i], &gatt_svc_csis[i], csis_insts[i].svc_p);
}
@@ -324,19 +357,36 @@ 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) {
goto free;
}
csis_reg_count = count;
csis_registered = true;
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;
}
int bt_le_nimble_csis_deinit(void *csis_svc)
{
ARG_UNUSED(csis_svc);
LOG_DBG("[N]CsisDeinit");
return 0;
}
@@ -24,6 +24,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -35,9 +36,9 @@ LOG_MODULE_REGISTER(LEA_GMAS, CONFIG_BT_ISO_LOG_LEVEL);
#define GMAS_MAX_CHRS (1 + 4) /* GMAP Role + up to one feature char per role */
static const ble_uuid16_t gmas_svc_uuid = BLE_UUID16_INIT(BT_UUID_GMAS_VAL);
static ble_uuid16_t gmas_chr_uuids[GMAS_MAX_CHRS];
static struct ble_gatt_chr_def gmas_chrs[GMAS_MAX_CHRS + 1];
static struct ble_gatt_svc_def gatt_svc_gmas[2];
static BT_AUDIO_EXT_RAM_BSS_ATTR ble_uuid16_t gmas_chr_uuids[GMAS_MAX_CHRS];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_chr_def gmas_chrs[GMAS_MAX_CHRS + 1];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def gatt_svc_gmas[2];
/* Mirror the lib's GMAS table (primary + role char + registered feature chars,
each value attr following a 0x2803 declaration) into the NimBLE service def. */
@@ -49,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;
}
@@ -146,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;
@@ -26,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -35,9 +36,9 @@ static const ble_uuid16_t has_uuid_features = BLE_UUID16_INIT(BT_UUID_HAS_HEARIN
static const ble_uuid16_t has_uuid_control_point = BLE_UUID16_INIT(BT_UUID_HAS_PRESET_CONTROL_POINT_VAL);
static const ble_uuid16_t has_uuid_preset_index = BLE_UUID16_INIT(BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL);
static uint16_t has_features_handle;
static uint16_t has_control_point_handle;
static uint16_t has_preset_index_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t has_features_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t has_control_point_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t has_preset_index_handle;
static struct ble_gatt_svc_def gatt_svc_has[] = {
{
@@ -59,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 */
@@ -77,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;
@@ -96,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;
}
@@ -124,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);
@@ -174,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;
@@ -192,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;
@@ -204,8 +212,8 @@ int bt_le_nimble_has_init(void)
LOG_DBG("[N]HasInit[%u]", chr_count);
gatt_svc_has->characteristics = calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_has->characteristics);
gatt_svc_has->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(gatt_svc_has->characteristics);
has_svc_add_features_chr((void *)(gatt_svc_has->characteristics + 0));
@@ -226,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) {
@@ -235,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;
}
@@ -31,6 +31,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -71,26 +72,26 @@ 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 uint8_t inc_ots_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_ots_svc_count;
static struct bt_ots *ots;
static uint16_t inc_ots_chr_feature_handle;
static uint16_t inc_ots_chr_name_handle;
static uint16_t inc_ots_chr_type_handle;
static uint16_t inc_ots_chr_size_handle;
static uint16_t inc_ots_chr_id_handle;
static uint16_t inc_ots_chr_props_handle;
static uint16_t inc_ots_chr_action_cp_handle;
static uint16_t inc_ots_chr_list_cp_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct bt_ots *ots;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_feature_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_name_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_type_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_size_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_props_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_action_cp_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t inc_ots_chr_list_cp_handle;
static struct ble_gatt_svc_def *gatt_svc_inc_ots;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *gatt_svc_inc_ots;
static struct ble_gatt_svc_def **gmcs_inc_svcs;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def **gmcs_inc_svcs;
static const ble_uuid16_t inc_ots_uuid_svc = BLE_UUID16_INIT(BT_UUID_OTS_VAL);
static const ble_uuid16_t inc_ots_uuid_feature = BLE_UUID16_INIT(BT_UUID_OTS_FEATURE_VAL);
@@ -103,34 +104,34 @@ static const ble_uuid16_t inc_ots_uuid_action_cp = BLE_UUID16_INIT(BT_UUID_OTS_A
static const ble_uuid16_t inc_ots_uuid_list_cp = BLE_UUID16_INIT(BT_UUID_OTS_LIST_CP_VAL);
#endif /* CONFIG_BT_OTS */
static uint16_t mcs_player_name_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_player_name_handle;
#if CONFIG_BT_OTS
static uint16_t mcs_icon_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_icon_obj_id_handle;
#endif /* CONFIG_BT_OTS */
static uint16_t mcs_icon_url_handle;
static uint16_t mcs_track_changed_handle;
static uint16_t mcs_track_title_handle;
static uint16_t mcs_track_duration_handle;
static uint16_t mcs_track_position_handle;
static uint16_t mcs_playback_speed_handle;
static uint16_t mcs_seeking_speed_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_icon_url_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_track_changed_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_track_title_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_track_duration_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_track_position_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_playback_speed_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_seeking_speed_handle;
#if CONFIG_BT_OTS
static uint16_t mcs_track_segments_obj_id_handle;
static uint16_t mcs_current_track_obj_id_handle;
static uint16_t mcs_next_track_obj_id_handle;
static uint16_t mcs_parent_group_obj_id_handle;
static uint16_t mcs_current_group_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_track_segments_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_current_track_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_next_track_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_parent_group_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_current_group_obj_id_handle;
#endif /* CONFIG_BT_OTS */
static uint16_t mcs_playing_order_handle;
static uint16_t mcs_playing_orders_handle;
static uint16_t mcs_media_state_handle;
static uint16_t mcs_media_control_point_handle;
static uint16_t mcs_media_control_opcodes_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_playing_order_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_playing_orders_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_media_state_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_media_control_point_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_media_control_opcodes_handle;
#if CONFIG_BT_OTS
static uint16_t mcs_search_control_point_handle;
static uint16_t mcs_search_results_obj_id_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_search_control_point_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mcs_search_results_obj_id_handle;
#endif /* CONFIG_BT_OTS */
static uint16_t ccid_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ccid_handle;
static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
{
@@ -145,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,
},
@@ -173,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,
}, {
@@ -181,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,
}, {
@@ -189,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,
}, {
@@ -198,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,
@@ -208,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,
@@ -217,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,
},
@@ -236,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,
@@ -246,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,
@@ -255,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,
}, {
@@ -264,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,
@@ -276,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,
@@ -293,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,
}, {
@@ -302,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,
}, {
@@ -310,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,
},
@@ -321,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,
}, {
@@ -329,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,
},
@@ -355,7 +356,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
/* Discrete MCS (0x1848): INSTANCE_COUNT instances sharing GMCS's characteristic table
* and OTS include (val_handle vars are write-only here, so sharing is safe). Built in
* bt_le_nimble_mcs_init as an N+1 NULL-terminated array for the GATT DB lifetime. */
static struct ble_gatt_svc_def *gatt_svc_mcs;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *gatt_svc_mcs;
static const ble_uuid16_t mcs_uuid_svc = BLE_UUID16_INIT(BT_UUID_MCS_VAL);
#if CONFIG_BT_OTS
@@ -365,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 */
@@ -471,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;
@@ -497,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");
@@ -511,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;
@@ -550,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;
@@ -574,8 +575,8 @@ static int inc_ots_svc_init(void)
svc->includes = NULL;
/* An additional characteristic consist of all 0s indicating end of characteristics */
svc->characteristics = calloc(INC_OTS_CHR_COUNT + 1, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
svc->characteristics = bt_le_ext_calloc(INC_OTS_CHR_COUNT + 1, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - OTS Feature */
inc_ots_chr_init((void *)&svc->characteristics[0],
@@ -629,8 +630,18 @@ static int inc_ots_svc_init(void)
}
#endif /* CONFIG_BT_OTS */
void bt_le_nimble_mcs_state_reset(void)
{
#if CONFIG_BT_OTS
inc_ots_svc_count = 0;
#endif /* CONFIG_BT_OTS */
}
int bt_le_nimble_gmcs_init(bool ots_included)
{
#if CONFIG_BT_OTS
bool inc_ots_added = false;
#endif /* CONFIG_BT_OTS */
int rc;
LOG_DBG("[N]GmcsInit[%u]", ots_included);
@@ -640,15 +651,15 @@ int bt_le_nimble_gmcs_init(bool ots_included)
inc_ots_svc_count = 1;
/* Extra one for terminating the included service array with NULL */
gmcs_inc_svcs = calloc(2, sizeof(struct ble_gatt_svc_def *));
assert(gmcs_inc_svcs);
gmcs_inc_svcs = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def *));
BT_LE_ASSERT(gmcs_inc_svcs);
/* Extra one for terminating the OTS service array */
gatt_svc_inc_ots = calloc(2, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_ots);
gatt_svc_inc_ots = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def));
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) {
@@ -668,6 +679,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) {
@@ -702,18 +714,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 */
@@ -778,7 +795,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;
@@ -797,6 +814,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. */
@@ -809,8 +827,8 @@ 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 = calloc(count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_mcs);
gatt_svc_mcs = bt_le_ext_calloc(count + 1, sizeof(struct ble_gatt_svc_def));
BT_LE_ASSERT(gatt_svc_mcs);
for (int i = 0; i < count; i++) {
gatt_svc_mcs[i].type = BLE_GATT_SVC_TYPE_PRIMARY;
@@ -836,6 +854,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) {
@@ -845,7 +864,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;
}
@@ -26,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -34,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)
@@ -43,18 +44,18 @@ 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 uint8_t inc_aics_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
static struct inc_aics_inst {
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_aics_inst {
struct bt_gatt_service *svc_p;
uint16_t state_handle;
uint16_t gain_handle;
@@ -64,7 +65,7 @@ static struct inc_aics_inst {
uint16_t description_handle;
} *inc_aics_insts;
static struct ble_gatt_svc_def *gatt_svc_inc_aics;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *gatt_svc_inc_aics;
static const ble_uuid16_t inc_aics_uuid_svc = BLE_UUID16_INIT(BT_UUID_AICS_VAL);
static const ble_uuid16_t inc_aics_uuid_state = BLE_UUID16_INIT(BT_UUID_AICS_STATE_VAL);
@@ -74,9 +75,9 @@ static const ble_uuid16_t inc_aics_uuid_status = BLE_UUID16_INIT(BT_UUID_AICS_IN
static const ble_uuid16_t inc_aics_uuid_control = BLE_UUID16_INIT(BT_UUID_AICS_CONTROL_VAL);
static const ble_uuid16_t inc_aics_uuid_description = BLE_UUID16_INIT(BT_UUID_AICS_DESCRIPTION_VAL);
static struct ble_gatt_svc_def **mics_inc_svcs;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def **mics_inc_svcs;
static uint16_t mics_mute_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t mics_mute_handle;
static struct ble_gatt_svc_def gatt_svc_mics[] = {
{
@@ -91,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,
@@ -115,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);
@@ -124,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++) {
@@ -135,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;
@@ -175,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;
@@ -197,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 */
@@ -305,8 +306,8 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
svc->uuid = &inc_aics_uuid_svc.u;
svc->includes = NULL;
svc->characteristics = calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Audio Input State */
inc_aics_chr_init((void *)&svc->characteristics[0],
@@ -345,9 +346,15 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
INC_AICS_CHR_FLAGS_DESCRIPTION);
}
void bt_le_nimble_mics_state_reset(void)
{
inc_aics_svc_count = 0;
}
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;
@@ -369,17 +376,17 @@ int bt_le_nimble_mics_init(void *micp_inc)
/* Extra one for terminating the included service array with NULL */
inc_count = inc_aics_svc_count + 1;
mics_inc_svcs = calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
assert(mics_inc_svcs);
mics_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
BT_LE_ASSERT(mics_inc_svcs);
/* MICS may include zero or more instances of AICS */
if (inc_aics_svc_count) {
inc_aics_insts = calloc(inc_aics_svc_count, sizeof(struct inc_aics_inst));
assert(inc_aics_insts);
inc_aics_insts = bt_le_ext_calloc(inc_aics_svc_count, sizeof(struct inc_aics_inst));
BT_LE_ASSERT(inc_aics_insts);
/* Extra one for terminating the AICS service array */
gatt_svc_inc_aics = calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_aics);
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
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]);
@@ -405,6 +412,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) {
@@ -438,22 +446,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;
}
}
@@ -24,27 +24,28 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_PACS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_PAC_SNK
static uint16_t pacs_snk_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_snk_handle;
#if CONFIG_BT_PAC_SNK_LOC
static uint16_t pacs_snk_loc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_snk_loc_handle;
#endif /* CONFIG_BT_PAC_SNK_LOC */
#endif /* CONFIG_BT_PAC_SNK */
#if CONFIG_BT_PAC_SRC
static uint16_t pacs_src_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_src_handle;
#if CONFIG_BT_PAC_SRC_LOC
static uint16_t pacs_src_loc_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_src_loc_handle;
#endif /* CONFIG_BT_PAC_SRC_LOC */
#endif /* CONFIG_BT_PAC_SRC */
static uint16_t pacs_ava_ctx_handle;
static uint16_t pacs_sup_ctx_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_ava_ctx_handle;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t pacs_sup_ctx_handle;
static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
{
@@ -61,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 */
@@ -76,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 */
@@ -98,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 */
@@ -113,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 */
@@ -133,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,
}, {
@@ -142,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 */
@@ -172,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
@@ -233,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;
@@ -274,3 +275,9 @@ int bt_le_nimble_pacs_init(void)
return 0;
}
int bt_le_nimble_pacs_deinit(void)
{
LOG_DBG("[N]PacsDeinit");
return 0;
}
@@ -7,6 +7,8 @@
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
@@ -24,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -42,7 +45,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 +59,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 +89,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 +103,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 +125,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 +139,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. */
@@ -162,19 +165,43 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
},
};
/* Discrete TBS (0x184B) shares GTBS's characteristic table, bound at init (a
* static initializer can't reference another object's member). */
static struct ble_gatt_svc_def gatt_svc_tbs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_VAL),
.includes = NULL,
.characteristics = NULL,
},
{
0, /* No more services. */
},
};
#if CONFIG_BT_TBS_BEARER_COUNT > 0
static const ble_uuid16_t tbs_uuid_svc = BLE_UUID16_INIT(BT_UUID_TBS_VAL);
/* A def and a characteristic table per bearer: val_handle lives in the table,
* so a shared one would only ever hold the last bearer's handles. */
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *tbs_svc_defs[CONFIG_BT_TBS_BEARER_COUNT];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t tbs_anchor_handle[CONFIG_BT_TBS_BEARER_COUNT];
static struct ble_gatt_svc_def *tbs_svc_def_new(uint16_t *anchor)
{
const struct ble_gatt_chr_def *src = gatt_svc_gtbs[0].characteristics;
struct ble_gatt_chr_def *chrs;
struct ble_gatt_svc_def *def;
size_t count = 0;
while (src[count].uuid) {
count++;
}
def = bt_le_ext_calloc(2, sizeof(*def));
chrs = bt_le_ext_calloc(count + 1, sizeof(*chrs));
if (!def || !chrs) {
free(def);
free(chrs);
return NULL;
}
memcpy(chrs, src, count * sizeof(*chrs));
chrs[0].val_handle = anchor;
def[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
def[0].uuid = &tbs_uuid_svc.u;
def[0].characteristics = chrs;
return def;
}
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
int bt_le_nimble_gtbs_attr_handle_set(void)
{
@@ -233,7 +260,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;
@@ -277,34 +304,49 @@ int bt_le_nimble_gtbs_init(void)
int bt_le_nimble_tbs_attr_handle_set(void)
{
#if CONFIG_BT_TBS_BEARER_COUNT > 0
struct bt_gatt_service *tbs_list;
uint16_t handle = 0;
int rc;
const struct bt_uuid_16 *uuid;
uint16_t base;
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_TBS_VAL), &handle);
if (rc) {
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
LOG_DBG("[N]TbsNotInit");
return 0;
}
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
LOG_ERR("[N]TbsSvcListGetFail");
return -ENODEV;
}
for (int i = 0; i < CONFIG_BT_TBS_BEARER_COUNT; i++) {
struct bt_gatt_service *svc = &tbs_list[i];
/* ble_gatts_find_svc returns the first match, so only the single discrete
* bearer is mapped (sufficient for CONFIG_BT_TBS_BEARER_COUNT==1).
*/
LOG_DBG("[N]TbsAttrHdlSet[%u][%u]", handle, tbs_list[0].attr_count);
/* Zero when the bearer was registered after the boot's ble_gatts_start():
* it never reached the ATT database, so there is no range to anchor. */
if (!tbs_svc_defs[i] || tbs_anchor_handle[i] < 2) {
continue;
}
for (size_t i = 0; i < tbs_list[0].attr_count; i++) {
(tbs_list[0].attrs + i)->handle = handle + i;
base = tbs_anchor_handle[i] - 2; /* service decl & char def handle */
/* Holds only while the lib orders this bearer the way NimBLE registered it. */
uuid = svc->attr_count > 2 ? (const struct bt_uuid_16 *)svc->attrs[2].uuid : NULL;
if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16 ||
uuid->val != BT_UUID_TBS_PROVIDER_NAME_VAL) {
LOG_ERR("[N]TbsAnchorMismatch[%d][%u]", i, tbs_anchor_handle[i]);
return -1;
}
LOG_DBG("[N]TbsAttrHdlSet[%d][%u][%u]", i, base, svc->attr_count);
for (size_t j = 0; j < svc->attr_count; j++) {
(svc->attrs + j)->handle = base + j;
}
}
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
return 0;
}
#if CONFIG_BT_TBS_BEARER_COUNT > 0
static int tbs_svc_check(void)
{
struct bt_gatt_service *tbs_list;
@@ -319,7 +361,8 @@ static int tbs_svc_check(void)
LOG_DBG("[N]TbsSvcCheck");
for (const struct ble_gatt_chr_def *chr = gatt_svc_tbs[0].characteristics;
/* Every bearer's table is a copy of this one. */
for (const struct ble_gatt_chr_def *chr = gatt_svc_gtbs[0].characteristics;
chr && chr->uuid; chr++) {
const ble_uuid16_t *check = (const ble_uuid16_t *)chr->uuid;
@@ -328,7 +371,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;
@@ -343,32 +386,70 @@ static int tbs_svc_check(void)
return 0;
}
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
int bt_le_nimble_tbs_init(void)
{
#if CONFIG_BT_TBS_BEARER_COUNT > 0
struct bt_gatt_service *tbs_list;
int rc;
int i;
LOG_DBG("[N]TbsInit");
/* Bind the shared characteristic table (see gatt_svc_tbs comment). */
gatt_svc_tbs[0].characteristics = gatt_svc_gtbs[0].characteristics;
rc = ble_gatts_count_cfg(gatt_svc_tbs);
if (rc) {
LOG_ERR("[N]TbsCountCfgFail[%d]", rc);
return rc;
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
return 0;
}
rc = ble_gatts_add_svcs(gatt_svc_tbs);
if (rc) {
LOG_ERR("[N]TbsAddSvcsFail[%d]", rc);
return rc;
/* Runs once per bearer registration and gets no index, so sweep for the slot
* that is registered but not yet added - as bt_le_bluedroid_tbs_init() does. */
for (i = 0; i < CONFIG_BT_TBS_BEARER_COUNT; i++) {
if (tbs_list[i].attr_count == 0 || tbs_svc_defs[i]) {
continue;
}
LOG_DBG("[N]TbsInit[%d]", i);
tbs_svc_defs[i] = tbs_svc_def_new(&tbs_anchor_handle[i]);
if (!tbs_svc_defs[i]) {
LOG_ERR("[N]TbsSvcDefAllocFail[%d]", i);
return -ENOMEM;
}
rc = ble_gatts_count_cfg(tbs_svc_defs[i]);
if (rc) {
LOG_ERR("[N]TbsCountCfgFail[%d]", rc);
goto free;
}
rc = ble_gatts_add_svcs(tbs_svc_defs[i]);
if (rc) {
LOG_ERR("[N]TbsAddSvcsFail[%d]", rc);
goto free;
}
}
rc = tbs_svc_check();
if (rc) {
return rc;
}
return tbs_svc_check();
free:
/* Only reachable before ble_gatts_add_svcs() succeeded; after that NimBLE
* holds the def and it must be leaked instead. */
free((void *)tbs_svc_defs[i]->characteristics);
free(tbs_svc_defs[i]);
tbs_svc_defs[i] = NULL;
return rc;
#else
return 0;
#endif /* CONFIG_BT_TBS_BEARER_COUNT > 0 */
}
int bt_le_nimble_gtbs_deinit(void)
{
LOG_DBG("[N]GtbsDeinit");
return 0;
}
int bt_le_nimble_tbs_deinit(uint8_t bearer_index)
{
LOG_DBG("[N]TbsDeinit[%u]", bearer_index);
return 0;
}
@@ -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;
@@ -26,6 +26,7 @@
#include "nimble/server.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "../../../lib/include/audio.h"
@@ -36,22 +37,22 @@ 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 uint8_t inc_vocs_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_vocs_svc_count;
static struct inc_vocs_inst {
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_vocs_inst {
struct bt_gatt_service *svc_p;
uint16_t state_handle;
uint16_t location_handle;
@@ -59,7 +60,7 @@ static struct inc_vocs_inst {
uint16_t description_handle;
} inc_vocs_insts[VOCS_INST_COUNT];
static struct ble_gatt_svc_def *gatt_svc_inc_vocs;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *gatt_svc_inc_vocs;
static const ble_uuid16_t inc_vocs_uuid_svc = BLE_UUID16_INIT(BT_UUID_VOCS_VAL);
static const ble_uuid16_t inc_vocs_uuid_state = BLE_UUID16_INIT(BT_UUID_VOCS_STATE_VAL);
@@ -72,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)
@@ -81,18 +82,18 @@ 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 uint8_t inc_aics_svc_count;
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
static struct inc_aics_inst {
static BT_AUDIO_EXT_RAM_BSS_ATTR struct inc_aics_inst {
struct bt_gatt_service *svc_p;
uint16_t state_handle;
uint16_t gain_handle;
@@ -102,7 +103,7 @@ static struct inc_aics_inst {
uint16_t description_handle;
} inc_aics_insts[AICS_INST_COUNT];
static struct ble_gatt_svc_def *gatt_svc_inc_aics;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def *gatt_svc_inc_aics;
static const ble_uuid16_t inc_aics_uuid_svc = BLE_UUID16_INIT(BT_UUID_AICS_VAL);
static const ble_uuid16_t inc_aics_uuid_state = BLE_UUID16_INIT(BT_UUID_AICS_STATE_VAL);
@@ -112,7 +113,7 @@ static const ble_uuid16_t inc_aics_uuid_status = BLE_UUID16_INIT(BT_UUID_AICS_IN
static const ble_uuid16_t inc_aics_uuid_control = BLE_UUID16_INIT(BT_UUID_AICS_CONTROL_VAL);
static const ble_uuid16_t inc_aics_uuid_description = BLE_UUID16_INIT(BT_UUID_AICS_DESCRIPTION_VAL);
static struct ble_gatt_svc_def **vcs_inc_svcs;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ble_gatt_svc_def **vcs_inc_svcs;
static struct ble_gatt_svc_def gatt_svc_vcs[] = {
{
@@ -128,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 */
@@ -145,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 */
@@ -169,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);
@@ -177,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++) {
@@ -188,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;
@@ -214,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);
@@ -222,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++) {
@@ -233,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;
@@ -277,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;
@@ -302,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 */
@@ -338,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 */
@@ -448,8 +449,8 @@ static void inc_vocs_svc_init(struct inc_vocs_inst *inst,
svc->uuid = &inc_vocs_uuid_svc.u;
svc->includes = NULL;
svc->characteristics = calloc(INC_VOCS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
svc->characteristics = bt_le_ext_calloc(INC_VOCS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Volume Offset State */
inc_vocs_chr_init((void *)&svc->characteristics[0],
@@ -501,8 +502,8 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
svc->uuid = &inc_aics_uuid_svc.u;
svc->includes = NULL;
svc->characteristics = calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Audio Input State */
inc_aics_chr_init((void *)&svc->characteristics[0],
@@ -541,9 +542,17 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
INC_AICS_CHR_FLAGS_DESCRIPTION);
}
void bt_le_nimble_vcs_state_reset(void)
{
inc_vocs_svc_count = 0;
inc_aics_svc_count = 0;
}
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;
@@ -569,14 +578,14 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
/* Extra one for terminating the included service array with NULL */
inc_count = inc_vocs_svc_count + inc_aics_svc_count + 1;
vcs_inc_svcs = calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
assert(vcs_inc_svcs);
vcs_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
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 = calloc(inc_vocs_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_vocs);
gatt_svc_inc_vocs = bt_le_ext_calloc(inc_vocs_svc_count + 1, sizeof(struct ble_gatt_svc_def));
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]);
@@ -602,6 +611,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) {
@@ -612,8 +622,8 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
/* VCS may include zero or more instances of AICS */
if (inc_aics_svc_count) {
/* Extra one for terminating the AICS service array */
gatt_svc_inc_aics = calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_aics);
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
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]);
@@ -639,6 +649,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) {
@@ -672,15 +683,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;
}
@@ -688,7 +704,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;
@@ -7,7 +7,6 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
@@ -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;
@@ -88,8 +106,8 @@ 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_DBG("[N]RdGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
LOG_WRN("[N]RdGattErr[%u][%d]", attr_handle, 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 = calloc(1, alloc_len);
assert(data);
data = bt_le_ext_calloc(1, alloc_len);
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 = 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_DBG("[N]WrGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
LOG_WRN("[N]WrGattErr[%u][%d]", attr_handle, rc);
return to_nimble_att_err(rc);
}
return 0;
@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_attr.h"
/* Static (.bss) placement markers for esp_ble_audio source files. Host-neutral:
* used by the bluedroid adapter profiles AND the host-agnostic OTS service, so
* it lives in the audio component's common include (not under bluedroid/).
*
* Distinct from esp_ble_iso's utils/iso_attr.h in both name and include prefix, so
* neither collides on the bt component's shared flat include path.
*
* - BT_AUDIO_EXT_RAM_BSS_ATTR: eligible for external SPIRAM (cold, task-context,
* non-DMA). Moves to PSRAM when BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY is set, else
* expands to nothing.
* - BT_AUDIO_CTRL_BSS_ATTR: must stay in internal DRAM (marker only).
*
* net_buf pools (NET_BUF_POOL_FIXED_DEFINE / NET_BUF_SIMPLE_DEFINE_STATIC) hide
* their .bss backing behind auto-generated names and cannot take a per-variable
* attribute; those objects (e.g. ots_l2cap) are moved via the linker fragment in
* linker_common.lf instead.
*/
#if CONFIG_BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY
#define BT_AUDIO_EXT_RAM_BSS_ATTR EXT_RAM_BSS_ATTR
#else
#define BT_AUDIO_EXT_RAM_BSS_ATTR
#endif
#define BT_AUDIO_CTRL_BSS_ATTR
@@ -45,16 +45,30 @@ 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_ascs_deinit(void);
int bt_le_pacs_deinit(void);
int bt_le_csis_deinit(const void *svc_inst);
int bt_le_bass_init(void);
int bt_le_bass_deinit(void);
int bt_le_tmas_init(void);
int bt_le_gtbs_init(void);
int bt_le_gtbs_deinit(void);
int bt_le_tbs_init(void);
int bt_le_tbs_deinit(uint8_t bearer_index);
int bt_le_has_init(void);
int bt_le_media_proxy_pl_init(void);
+394 -25
View File
@@ -44,12 +44,19 @@
#include <../host/conn_internal.h>
#include <../host/hci_core.h>
#include "utils/assert.h"
#include "utils/mem.h"
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/init.h"
#else
#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"
@@ -178,7 +185,7 @@ static const uint16_t ext_structs[] = {
sizeof(struct bt_bond_info),
};
#define LEA_VERSION (0x20260722)
#define LEA_VERSION (0x20260828)
struct lib_ext_cfgs {
/* BLE */
@@ -1078,16 +1085,20 @@ struct lib_ext_funcs {
/* Scan */
int (*_scan_cb_register)(struct bt_le_scan_cb *cb);
void (*_scan_cb_unregister)(struct bt_le_scan_cb *cb);
int (*_scan_start)(const struct bt_le_scan_param *param, void *cb);
int (*_scan_stop)(void);
int (*_pa_sync_cb_register)(struct bt_le_per_adv_sync_cb *cb);
int (*_pa_sync_cb_unregister)(struct bt_le_per_adv_sync_cb *cb);
int (*_pa_sync_get_info)(struct bt_le_per_adv_sync *per_adv_sync,
struct bt_le_per_adv_sync_info *info);
struct bt_le_per_adv_sync *(*_pa_sync_lookup_addr)(const bt_addr_le_t *adv_addr, uint8_t sid);
/* Connection */
int (*_conn_cb_register)(struct bt_conn_cb *cb);
int (*_conn_cb_unregister)(struct bt_conn_cb *cb);
int (*_conn_auth_info_cb_register)(struct bt_conn_auth_info_cb *cb);
int (*_conn_auth_info_cb_unregister)(struct bt_conn_auth_info_cb *cb);
void (*_conn_foreach)(enum bt_conn_type type,
void (*func)(struct bt_conn *conn, void *data),
void *data);
@@ -1203,32 +1214,21 @@ 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,
._calloc = (void *)bt_le_ext_calloc,
#else
._malloc = (void *)malloc,
._calloc = (void *)calloc,
#endif
._free = (void *)free,
._rand = (void *)bt_rand,
@@ -1299,21 +1299,23 @@ static const struct lib_ext_funcs ext_funcs = {
._bt_foreach_bond = (void *)bt_foreach_bond,
._scan_cb_register = (void *)bt_le_scan_cb_register,
._scan_cb_unregister = (void *)bt_le_scan_cb_unregister,
._scan_start = (void *)bt_le_scan_start,
._scan_stop = (void *)bt_le_scan_stop,
._pa_sync_cb_register = (void *)bt_le_per_adv_sync_cb_register,
._pa_sync_cb_unregister = (void *)bt_le_per_adv_sync_cb_unregister,
._pa_sync_get_info = (void *)bt_le_per_adv_sync_get_info,
._pa_sync_lookup_addr = (void *)bt_le_per_adv_sync_lookup_addr,
._conn_cb_register = (void *)bt_conn_cb_register,
._conn_cb_unregister = (void *)bt_conn_cb_unregister,
._conn_auth_info_cb_register = (void *)bt_conn_auth_info_cb_register,
._conn_auth_info_cb_unregister = (void *)bt_conn_auth_info_cb_unregister,
._conn_foreach = (void *)bt_conn_foreach,
._conn_get_info = (void *)bt_conn_get_info,
._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,
@@ -2081,6 +2083,260 @@ static int lib_audio_resources_init(void)
return err;
}
/* Reverse order of lib_audio_resources_init(): a module may reference an earlier
* one. Failures log and continue - stopping half way leaves more behind. Keep
* every lib_*_init() listed here; a missing counterpart leaks silently. */
static void lib_audio_resources_deinit(void)
{
#if CONFIG_BT_GMAP
/* No init counterpart above: bt_gmap_register() allocates GMAS on demand,
* so only the deinit side is listed. */
if (lib_gmap_server_deinit()) {
LOG_ERR("LibGmapSrvDeinitFail");
}
if (lib_gmap_client_deinit()) {
LOG_ERR("LibGmapCliDeinitFail");
}
#endif /* CONFIG_BT_GMAP */
#if CONFIG_BT_HAS_CLIENT
if (lib_has_client_deinit()) {
LOG_ERR("LibHasCliDeinitFail");
}
#endif /* CONFIG_BT_HAS_CLIENT */
#if CONFIG_BT_HAS
if (lib_has_deinit()) {
LOG_ERR("LibHasDeinitFail");
}
#endif /* CONFIG_BT_HAS */
#if CONFIG_BT_VOCS_CLIENT
if (lib_vocs_client_deinit()) {
LOG_ERR("LibVocsClientDeinitFail");
}
#endif /* CONFIG_BT_VOCS_CLIENT */
#if CONFIG_BT_VOCS || CONFIG_BT_VOCS_CLIENT
if (lib_vocs_deinit()) {
LOG_ERR("LibVocsDeinitFail");
}
#endif /* CONFIG_BT_VOCS || CONFIG_BT_VOCS_CLIENT */
#if CONFIG_BT_VCP_VOL_REND
if (lib_vcp_vol_rend_deinit()) {
LOG_ERR("LibVcpVolRendDeinitFail");
}
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_VCP_VOL_CTLR
if (lib_vcp_vol_ctlr_deinit()) {
LOG_ERR("LibVcpVolCtlrDeinitFail");
}
#endif /* CONFIG_BT_VCP_VOL_CTLR */
#if CONFIG_BT_TMAP
if (lib_tmap_deinit()) {
LOG_ERR("LibTmapDeinitFail");
}
#endif /* CONFIG_BT_TMAP */
#if CONFIG_BT_TBS
if (lib_tbs_deinit()) {
LOG_ERR("LibTbsDeinitFail");
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_TBS_CLIENT
if (lib_tbs_client_deinit()) {
LOG_ERR("LibTbsCliDeinitFail");
}
#endif /* CONFIG_BT_TBS_CLIENT */
#if CONFIG_BT_PBP
if (lib_pbp_deinit()) {
LOG_ERR("LibPbpDeinitFail");
}
#endif /* CONFIG_BT_PBP */
#if CONFIG_BT_MPL
if (lib_mpl_deinit()) {
LOG_ERR("LibMplDeinitFail");
}
#endif /* CONFIG_BT_MPL */
#if CONFIG_BT_MICP_MIC_DEV
if (lib_micp_mic_dev_deinit()) {
LOG_ERR("LibMicpMicDevDeinitFail");
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#if CONFIG_BT_MICP_MIC_CTLR
if (lib_micp_mic_ctlr_deinit()) {
LOG_ERR("LibMicpMicCtlrDeinitFail");
}
#endif /* CONFIG_BT_MICP_MIC_CTLR */
#if CONFIG_BT_MCTL
if (lib_media_proxy_deinit()) {
LOG_ERR("LibMediaProxyDeinitFail");
}
#endif /* CONFIG_BT_MCTL */
#if CONFIG_BT_MCS
if (lib_mcs_deinit()) {
LOG_ERR("LibMcsDeinitFail");
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_MCC
if (lib_mcc_deinit()) {
LOG_ERR("LibMccDeinitFail");
}
#endif /* CONFIG_BT_MCC */
#if CONFIG_BT_CSIP_SET_COORDINATOR
if (lib_csip_set_coordinator_deinit()) {
LOG_ERR("LibCsipSetCoordinatorDeinitFail");
}
#endif /* CONFIG_BT_CSIP_SET_COORDINATOR */
#if CONFIG_BT_CSIP_SET_MEMBER
if (lib_csip_set_member_deinit()) {
LOG_ERR("LibCsipSetMemberDeinitFail");
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_BAP_STREAM
if (lib_codec_deinit()) {
LOG_ERR("LibCodecDeinitFail");
}
#endif /* CONFIG_BT_BAP_STREAM */
#if CONFIG_BT_CCP_CALL_CONTROL_SERVER
if (lib_ccp_call_control_server_deinit()) {
LOG_ERR("LibCcpCallControlServerDeinitFail");
}
#endif /* CONFIG_BT_CCP_CALL_CONTROL_SERVER */
#if CONFIG_BT_CCP_CALL_CONTROL_CLIENT
if (lib_ccp_call_control_client_deinit()) {
LOG_ERR("LibCcpCallControlClientDeinitFail");
}
#endif /* CONFIG_BT_CCP_CALL_CONTROL_CLIENT */
if (lib_ccid_deinit()) {
LOG_ERR("LibCcidDeinitFail");
}
#if CONFIG_BT_CAP_COMMANDER
if (lib_cap_commander_deinit()) {
LOG_ERR("LibCapComDeinitFail");
}
#endif /* CONFIG_BT_CAP_COMMANDER */
#if CONFIG_BT_CAP_ACCEPTOR
if (lib_cap_acceptor_deinit()) {
LOG_ERR("LibCapAccDeinitFail");
}
#endif /* CONFIG_BT_CAP_ACCEPTOR */
#if CONFIG_BT_CAP_INITIATOR
if (lib_cap_initiator_deinit()) {
LOG_ERR("LibCapIniDeinitFail");
}
#endif /* CONFIG_BT_CAP_INITIATOR */
#if CONFIG_BT_CAP
if (lib_cap_stream_deinit()) {
LOG_ERR("LibCapStreamDeinitFail");
}
#endif /* CONFIG_BT_CAP */
#if CONFIG_BT_CAP_INITIATOR_UNICAST || CONFIG_BT_CAP_COMMANDER
if (lib_cap_common_deinit()) {
LOG_ERR("LibCapCommonDeinitFail");
}
#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST || CONFIG_BT_CAP_COMMANDER */
#if CONFIG_BT_BAP_UNICAST_SERVER
if (lib_bap_unicast_server_deinit()) {
LOG_ERR("LibBapUniSrvDeinitFail");
}
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
#if CONFIG_BT_BAP_UNICAST_CLIENT
if (lib_bap_unicast_client_deinit()) {
LOG_ERR("LibBapUniCliDeinitFail");
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if CONFIG_BT_BAP_BROADCAST_ASSISTANT
if (lib_bap_broadcast_assistant_deinit()) {
LOG_ERR("LibBapBaDeinitFail");
}
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
#if CONFIG_BT_BAP_SCAN_DELEGATOR
if (lib_bap_scan_delegator_deinit()) {
LOG_ERR("LibBapSdeDeinitFail");
}
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
#if CONFIG_BT_BAP_BROADCAST_SOURCE
if (lib_bap_broadcast_source_deinit()) {
LOG_ERR("LibBapBsrcDeinitFail");
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
#if CONFIG_BT_BAP_BROADCAST_SINK
if (lib_bap_broadcast_sink_deinit()) {
LOG_ERR("LibBapBsnkDeinitFail");
}
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if CONFIG_BT_PACS
if (lib_pacs_deinit()) {
LOG_ERR("LibPacsDeinitFail");
}
#endif /* CONFIG_BT_PACS */
#if CONFIG_BT_ASCS
if (lib_ascs_deinit()) {
LOG_ERR("LibAscsDeinitFail");
}
#endif /* CONFIG_BT_ASCS */
#if CONFIG_BT_BAP_STREAM
if (lib_bap_stream_deinit()) {
LOG_ERR("LibBapStreamDeinitFail");
}
if (lib_bap_iso_deinit()) {
LOG_ERR("LibBapIsoDeinitFail");
}
#endif /* CONFIG_BT_BAP_STREAM */
#if CONFIG_BT_BAP_BASE
if (lib_bap_base_deinit()) {
LOG_ERR("LibBapBaseDeinitFail");
}
#endif /* CONFIG_BT_BAP_BASE */
#if CONFIG_BT_AICS_CLIENT
if (lib_aics_client_deinit()) {
LOG_ERR("LibAicsClientDeinitFail");
}
#endif /* CONFIG_BT_AICS_CLIENT */
#if CONFIG_BT_AICS || CONFIG_BT_AICS_CLIENT
if (lib_aics_deinit()) {
LOG_ERR("LibAicsDeinitFail");
}
#endif /* CONFIG_BT_AICS || CONFIG_BT_AICS_CLIENT */
}
int bt_le_audio_init(void)
{
int err;
@@ -2118,14 +2374,49 @@ int bt_le_audio_init(void)
err = lib_audio_resources_init();
if (err) {
return err;
goto deinit_lib;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_audio_init();
#else
return bt_le_nimble_audio_init();
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
err = bt_le_l2cap_ots_init();
if (err) {
goto deinit_lib;
}
#endif
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_audio_init();
#else
err = bt_le_nimble_audio_init();
#endif
if (err) {
goto deinit_ots;
}
return 0;
deinit_ots:
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_ots_deinit();
#endif
deinit_lib:
lib_audio_resources_deinit();
return err;
}
void bt_le_audio_deinit(void)
{
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_audio_deinit();
#else
bt_le_nimble_audio_deinit();
#endif
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_ots_deinit();
#endif
lib_audio_resources_deinit();
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
@@ -2140,6 +2431,17 @@ int bt_le_ascs_init(void)
return bt_le_nimble_ascs_init();
#endif
}
int bt_le_ascs_deinit(void)
{
LOG_DBG("AscsDeinit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_ascs_deinit();
#else
return bt_le_nimble_ascs_deinit();
#endif
}
#endif /* CONFIG_BT_ASCS */
#if CONFIG_BT_BAP_SCAN_DELEGATOR
@@ -2153,6 +2455,17 @@ int bt_le_bass_init(void)
return bt_le_nimble_bass_init();
#endif
}
int bt_le_bass_deinit(void)
{
LOG_DBG("BassDeinit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_bass_deinit();
#else
return bt_le_nimble_bass_deinit();
#endif
}
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
#if CONFIG_BT_TMAP
@@ -2180,6 +2493,17 @@ int bt_le_gtbs_init(void)
#endif
}
int bt_le_gtbs_deinit(void)
{
LOG_DBG("GtbsDeinit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gtbs_deinit();
#else
return bt_le_nimble_gtbs_deinit();
#endif
}
int bt_le_tbs_init(void)
{
LOG_DBG("TbsInit");
@@ -2190,6 +2514,17 @@ int bt_le_tbs_init(void)
return bt_le_nimble_tbs_init();
#endif
}
int bt_le_tbs_deinit(uint8_t bearer_index)
{
LOG_DBG("TbsDeinit[%u]", bearer_index);
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_tbs_deinit(bearer_index);
#else
return bt_le_nimble_tbs_deinit(bearer_index);
#endif
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -2245,6 +2580,40 @@ int bt_le_micp_mic_dev_init(void)
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
#if CONFIG_BT_PACS
int bt_le_pacs_deinit(void)
{
LOG_DBG("PacsDeinit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_pacs_deinit();
#else
return bt_le_nimble_pacs_deinit();
#endif
}
#endif /* CONFIG_BT_PACS */
#if CONFIG_BT_CSIP_SET_MEMBER
int bt_le_csis_deinit(const void *svc_inst)
{
struct bt_gatt_service *csis_svc;
LOG_DBG("CsisDeinit");
csis_svc = lib_csip_set_member_svc_get(svc_inst);
if (!csis_svc) {
LOG_ERR("CsisSvcGetFail");
return -ENODEV;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_csis_deinit(csis_svc);
#else
return bt_le_nimble_csis_deinit(csis_svc);
#endif
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
int bt_le_audio_start(void *info)
{
LOG_DBG("AudioStart");
@@ -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.
@@ -0,0 +1,802 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#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");
}
}
@@ -0,0 +1,851 @@
/*
* 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 <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#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);
extern void bt_ots_instances_rewind(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);
extern void bt_ots_client_unregister_all(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);
}
}
_IDF_ONLY
void bt_le_l2cap_event_free(void *data)
{
struct bt_le_l2cap_event *qev = data;
if (qev == NULL) {
return;
}
/* Only RECEIVED carries a buffer of its own; the rest are header-only. */
if (qev->type == BT_LE_L2CAP_EVENT_RECEIVED) {
free(qev->received.data);
}
free(qev);
}
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) {
ISO_POST_FAIL_LOG(err, "L2capPostFail[%d][%u]", err, qev->type);
bt_le_l2cap_event_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");
/* Symmetric with the registers in _init: conn_cbs is never reset elsewhere. */
#if CONFIG_BT_OTS
bt_gatt_ots_conn_cb_unregister();
/* Give the instances handed out by bt_ots_free_instance_get() back to the
* pool; the profiles that took them are released right after this. */
bt_ots_instances_rewind();
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_OTS_CLIENT
bt_gatt_ots_client_conn_cb_unregister();
/* Before lib_audio_resources_deinit() frees the bt_ots_client instances
* the table points at - this runs first in bt_le_audio_deinit(). */
bt_ots_client_unregister_all();
#endif /* CONFIG_BT_OTS_CLIENT */
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_l2cap_deinit();
#else
bt_le_nimble_l2cap_deinit();
#endif
}
@@ -0,0 +1,119 @@
/*
* 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 <stdint.h>
#include "sdkconfig.h"
#include <zephyr/sys/util.h>
#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);
void bt_le_l2cap_event_free(void *data);
int bt_le_l2cap_ots_init(void);
void bt_le_l2cap_ots_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* OTS_ADAPTER_L2CAP_H_ */
@@ -0,0 +1,506 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#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));
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -121,7 +122,7 @@ static ssize_t ots_feature_read(struct bt_conn *conn,
{
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
LOG_DBG("OTS Feature GATT Read Operation");
LOG_DBG("OtsFeatRd");
return bt_gatt_attr_read(conn, attr, buf, len, offset, &ots->features,
sizeof(ots->features));
@@ -133,15 +134,15 @@ static ssize_t ots_obj_name_read(struct bt_conn *conn,
{
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
LOG_DBG("OTS Object Name GATT Read Operation");
LOG_DBG("OtsObjNameRd");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
if (!ots->cur_obj->metadata.name) {
LOG_DBG("Current Object has no name!");
LOG_WRN("OtsCurObjNoName");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
@@ -161,26 +162,26 @@ ssize_t ots_obj_name_write(struct bt_conn *conn,
int rc = 0;
char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1];
LOG_DBG("OTS Object Name GATT Write Operation");
LOG_DBG("OtsObjNameWr");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
LOG_DBG("Rejecting name write for the directory list object.");
LOG_WRN("OtsDirListNameWrRej");
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
}
if (offset > 0) {
LOG_DBG("Rejecting a long write, offset must be 0!");
LOG_WRN("OtsNameWrInvOft");
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
}
if (len == 0 || len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
LOG_DBG("Invalid object name length!");
LOG_WRN("OtsInvObjNameLen");
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
}
@@ -191,7 +192,7 @@ ssize_t ots_obj_name_write(struct bt_conn *conn,
rc = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &obj);
while (rc == 0) {
if (obj != ots->cur_obj && strcmp(name, obj->metadata.name) == 0) {
LOG_DBG("Object name is duplicated!");
LOG_WRN("OtsObjNameDup");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS);
}
rc = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, &obj);
@@ -220,10 +221,10 @@ static ssize_t ots_obj_type_read(struct bt_conn *conn,
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
struct bt_ots_obj_metadata *obj_meta;
LOG_DBG("OTS Object Type GATT Read Operation");
LOG_DBG("OtsObjTypeRd");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
@@ -248,10 +249,10 @@ static ssize_t ots_obj_size_read(struct bt_conn *conn,
{
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
LOG_DBG("OTS Object Size GATT Read Operation");
LOG_DBG("OtsObjSizeRd");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
@@ -268,10 +269,10 @@ static ssize_t ots_obj_id_read(struct bt_conn *conn,
uint8_t id[BT_OTS_OBJ_ID_SIZE];
char id_str[BT_OTS_OBJ_ID_STR_LEN];
LOG_DBG("OTS Object ID GATT Read Operation");
LOG_DBG("OtsObjIdRd");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
@@ -279,7 +280,7 @@ static ssize_t ots_obj_id_read(struct bt_conn *conn,
bt_ots_obj_id_to_str(ots->cur_obj->id, id_str,
sizeof(id_str));
LOG_DBG("Current Object ID: %s", id_str);
LOG_DBG("OtsCurObjId[%s]", id_str);
return bt_gatt_attr_read(conn, attr, buf, len, offset, id, sizeof(id));
}
@@ -290,10 +291,10 @@ static ssize_t ots_obj_prop_read(struct bt_conn *conn,
{
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
LOG_DBG("OTS Object Properties GATT Read Operation");
LOG_DBG("OtsObjPropRd");
if (!ots->cur_obj) {
LOG_DBG("No Current Object selected in OTS!");
LOG_WRN("OtsCurObjNotSel");
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
}
@@ -310,15 +311,25 @@ 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("Directory Listing Object is being read");
LOG_DBG("OtsDirListBusy");
return -EBUSY;
}
err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &new_obj);
if (err) {
LOG_ERR("No space available in the object manager");
LOG_ERR("OtsObjMgrNoSpace");
return err;
}
@@ -330,11 +341,14 @@ 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;
}
if (!ots_obj_validate_prop_against_oacp(created_desc.props, ots->features.oacp)) {
LOG_ERR("Object properties (0x%04X) are not a subset of OACP (0x%04X)",
LOG_ERR("OtsObjPropNotSubsetOacp[%04x][%04x]",
created_desc.props, ots->features.oacp);
(void)bt_ots_obj_delete(ots, new_obj->id);
@@ -342,28 +356,36 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
}
if (created_desc.name == NULL) {
LOG_ERR("Object name must be set by application after object creation.");
LOG_ERR("OtsObjNameNotSet");
(void)bt_ots_obj_delete(ots, new_obj->id);
return -ECANCELED;
}
if (created_desc.size.alloc < param->size) {
LOG_ERR("Object allocated size must >= requested size.");
LOG_ERR("OtsObjAllocSizeTooSmall");
(void)bt_ots_obj_delete(ots, new_obj->id);
return -ECANCELED;
}
} else {
/* obj_created callback is required to populate the descriptor */
LOG_ERR("obj_created callback is not set");
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;
@@ -386,7 +408,7 @@ int bt_ots_obj_add(struct bt_ots *ots, const struct bt_ots_obj_add_param *param)
}
if (obj->metadata.name == NULL) {
LOG_ERR("Object name is NULL");
LOG_ERR("OtsObjNameNull");
(void)bt_ots_obj_delete(ots, obj->id);
return -ECANCELED;
@@ -394,14 +416,14 @@ int bt_ots_obj_add(struct bt_ots *ots, const struct bt_ots_obj_add_param *param)
name_len = strlen(obj->metadata.name);
if (name_len == 0 || name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
LOG_ERR("Invalid name length %zu", name_len);
LOG_ERR("OtsInvNameLen[%zu]", name_len);
(void)bt_ots_obj_delete(ots, obj->id);
return -ECANCELED;
}
if (obj->metadata.size.cur > param->size) {
LOG_ERR("Object current size must be less than or equal to requested size.");
LOG_ERR("OtsObjCurSizeTooBig");
(void)bt_ots_obj_delete(ots, obj->id);
return -ECANCELED;
@@ -416,7 +438,7 @@ int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
struct bt_gatt_ots_object *obj;
CHECKIF(!BT_OTS_VALID_OBJ_ID(id)) {
LOG_DBG("Invalid object ID 0x%016llx", id);
LOG_WRN("OtsInvObjId[%016llx]", id);
return -EINVAL;
}
@@ -432,7 +454,7 @@ int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
!bt_ots_dir_list_is_idle(ots->dir_list)) {
LOG_DBG("Directory Listing Object is being read");
LOG_DBG("OtsDirListBusy");
return -EBUSY;
}
@@ -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,
@@ -506,6 +563,9 @@ int bt_ots_init(struct bt_ots *ots,
/* Check OACP supported features against Kconfig. */
if (ots_init->features.oacp & (~((uint32_t) OACP_FEAT))) {
/* cb is what bt_ots_instances_rewind() reads to decide there is an init
* to reverse, so every bail after the assignment above has to undo it. */
ots->cb = NULL;
return -ENOTSUP;
}
@@ -514,25 +574,32 @@ int bt_ots_init(struct bt_ots *ots,
"Object creation requires object write to be supported");
ots->features.oacp = ots_init->features.oacp;
LOG_DBG("OACP features: 0x%04X", ots->features.oacp);
LOG_DBG("OtsOacpFeat[%04x]", ots->features.oacp);
/* Check OLCP supported features against Kconfig. */
if (ots_init->features.olcp & (~((uint32_t) OLCP_FEAT))) {
ots->cb = NULL;
return -ENOTSUP;
}
ots->features.olcp = ots_init->features.olcp;
LOG_DBG("OLCP features: 0x%04X", ots->features.olcp);
LOG_DBG("OtsOlcpFeat[%04x]", ots->features.olcp);
/* Register L2CAP context. */
err = bt_gatt_ots_l2cap_register(&ots->l2cap);
if (err) {
ots->cb = NULL;
return err;
}
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);
}
ots->cb = NULL;
return err;
}
@@ -540,10 +607,10 @@ 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("Initialized OTS");
LOG_DBG("OtsInit");
return 0;
}
@@ -616,6 +683,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);
@@ -629,18 +697,43 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co
if (strlen(obj->metadata.name) == 0) {
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
LOG_DBG("Deleting object with %s ID due to empty name", id_str);
LOG_DBG("OtsDelEmptyNameObj[%s]", id_str);
if (ots->cb && ots->cb->obj_deleted) {
ots->cb->obj_deleted(ots, conn, obj->id);
}
if (bt_gatt_ots_obj_manager_obj_delete(obj)) {
LOG_ERR("Failed to remove object with %s ID from object manager",
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)
@@ -652,7 +745,10 @@ static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
index < instance_cnt;
instance++, index++) {
LOG_DBG("Processing disconnect for OTS instance %u", index);
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,
@@ -678,7 +774,39 @@ 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)
/* Both pools only count up, so without this a second init finds them exhausted
* (McsNoFreeOtsInst / OtsObjMgrInstUnavail). Safe here: the profile that took an
* instance has dropped its reference by now. */
void bt_ots_instances_rewind(void)
{
for (struct bt_ots *instance = BT_GATT_OTS_INSTANCE_LIST_START;
instance != BT_GATT_OTS_INSTANCE_LIST_END; instance++) {
/* cb is the marker for "bt_ots_init() ran here": it is set on entry and
* cleared again on every failure path, so it is exactly the set of
* instances with something to reverse. Reverse order of that function. */
if (instance->cb != NULL) {
k_work_deinit_delayable(&instance->olcp_ind.work);
k_work_deinit_delayable(&instance->oacp_ind.work);
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
bt_ots_dir_list_deinit(&instance->dir_list);
}
(void)bt_gatt_service_unregister(instance->service);
(void)bt_gatt_ots_l2cap_unregister(&instance->l2cap);
instance->cb = NULL;
}
if (instance->obj_manager != NULL) {
bt_gatt_ots_obj_manager_release(instance->obj_manager);
instance->obj_manager = NULL;
}
}
instance_cnt = 0;
}
int bt_gatt_ots_instances_prepare(void)
{
uint32_t index;
struct bt_ots *instance;
@@ -690,7 +818,7 @@ static int bt_gatt_ots_instances_prepare(void)
instance->obj_manager = bt_gatt_ots_obj_manager_assign();
if (!instance->obj_manager) {
LOG_ERR("OTS Object manager instance not available");
LOG_ERR("OtsObjMgrInstUnavail");
return -ENOMEM;
}
@@ -709,10 +837,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);
}
File diff suppressed because it is too large Load Diff
@@ -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
*/
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2021 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -146,7 +147,7 @@ static int bt_ots_dir_list_search_forward(struct bt_ots_dir_list *dir_list, void
size_t rec_len = dir_list_object_record_size(obj);
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
LOG_DBG("Searching forward for offset %ld starting at %ld with object ID %s",
LOG_DBG("OtsDirListSrchFwd[%ld][%ld][%s]",
(long)offset, (long)dir_list->anchor_offset, id_str);
while (dir_list->anchor_offset + rec_len <= offset) {
@@ -173,7 +174,7 @@ static int bt_ots_dir_list_search_backward(struct bt_ots_dir_list *dir_list, voi
struct bt_gatt_ots_object *obj = dir_list->anchor_object;
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
LOG_DBG("Searching backward for offset %ld starting at %ld with object ID %s",
LOG_DBG("OtsDirListSrchBwd[%ld][%ld][%s]",
(long)offset, (long)dir_list->anchor_offset, id_str);
while (dir_list->anchor_offset > offset) {
@@ -208,7 +209,7 @@ static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, void *obj_ma
} else {
size_t rec_len;
LOG_DBG("Offset %ld is closer to %zu than %ld, start from end",
LOG_DBG("OtsDirListSrchFromEnd[%ld][%zu][%ld]",
(long)offset, last, (long)dir_list->anchor_offset);
err = bt_gatt_ots_obj_manager_last_obj_get(obj_manager, &dir_list->anchor_object);
if (err) {
@@ -222,7 +223,7 @@ static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, void *obj_ma
const size_t mid = dir_list->anchor_offset / 2;
if (offset < mid) {
LOG_DBG("Offset %ld is closer to 0 than %ld, start from beginning",
LOG_DBG("OtsDirListSrchFromStart[%ld][%ld]",
(long)offset, (long)dir_list->anchor_offset);
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
err = bt_ots_dir_list_search_forward(dir_list, obj_manager, offset);
@@ -236,7 +237,7 @@ static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, void *obj_ma
}
bt_ots_obj_id_to_str(dir_list->anchor_object->id, id_str, sizeof(id_str));
LOG_DBG("Found offset %ld starting at %ld in object with ID %s",
LOG_DBG("OtsDirListFoundOft[%ld][%ld][%s]",
(long)offset, (long)dir_list->anchor_offset, id_str);
return 0;
@@ -259,10 +260,16 @@ static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_man
err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
} while (!err);
LOG_DBG("Update directory listing current size to 0x%zx", len);
LOG_DBG("OtsDirListUpdCurSize[%zx]", len);
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)
@@ -314,6 +320,16 @@ void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager)
dir_list_update_size(*dir_list, obj_manager);
}
void bt_ots_dir_list_deinit(struct bt_ots_dir_list **dir_list)
{
if (*dir_list == NULL) {
return;
}
memset(*dir_list, 0, sizeof(**dir_list));
*dir_list = NULL;
}
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)
{
@@ -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,7 +159,9 @@ 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);
void bt_ots_dir_list_deinit(struct bt_ots_dir_list **dir_list);
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);
bool bt_ots_dir_list_is_idle(const struct bt_ots_dir_list *dir_list);
@@ -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 <zephyr/types.h>
#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];
};
@@ -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,10 +47,11 @@ 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("OtsTxBufAllocFail");
LOG_ERR("OtsL2capTxBufAllocFail");
return -ENOMEM;
}
@@ -58,7 +60,7 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
ret = bt_l2cap_chan_send(&l2cap_ctx->ot_chan.chan, buf);
if (ret < 0) {
LOG_ERR("Unable to send data over CoC: %d", ret);
LOG_WRN("OtsL2capChanSendFail[%d]", ret);
net_buf_unref(buf);
return -ENOEXEC;
@@ -67,7 +69,7 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
/* Mark that L2CAP TX was accepted. */
l2cap_ctx->tx.len_sent += len;
LOG_DBG("Sending TX chunk with %d bytes on L2CAP CoC", len);
LOG_DBG("OtsL2capTxChunk[%u]", len);
return 0;
}
@@ -75,9 +77,9 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
{
LOG_DBG("Channel %p allocating buffer", 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
@@ -86,19 +88,18 @@ static void l2cap_sent(struct bt_l2cap_chan *chan)
struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Outgoing data channel %p transmitted", chan);
LOG_DBG("OtsL2capSent");
l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_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. */
LOG_ERR("Failed to send next chunk, aborting TX");
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
if (l2cap_ctx->tx_done) {
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
}
/* 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");
(void)bt_l2cap_chan_disconnect(chan);
}
return;
@@ -107,7 +108,7 @@ static void l2cap_sent(struct bt_l2cap_chan *chan)
/* TX completed - notify upper layers and clean up. */
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
LOG_DBG("Scheduled TX on L2CAP CoC is complete");
LOG_DBG("OtsL2capTxComp");
if (l2cap_ctx->tx_done) {
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
@@ -119,7 +120,7 @@ static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Incoming data channel %p received", chan);
LOG_DBG("OtsL2capRecv");
l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan);
@@ -132,12 +133,12 @@ static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status)
{
LOG_DBG("Channel %p status %lu", chan, atomic_get(status));
LOG_DBG("OtsL2capStatus[%u]", atomic_get(status));
}
static void l2cap_connected(struct bt_l2cap_chan *chan)
{
LOG_DBG("Channel %p connected", chan);
LOG_DBG("OtsL2capConnected");
}
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
@@ -145,7 +146,7 @@ static void l2cap_disconnected(struct bt_l2cap_chan *chan)
struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Channel %p disconnected", chan);
LOG_DBG("OtsL2capDisconnected");
l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan);
@@ -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 = {
@@ -173,7 +180,7 @@ static inline void l2cap_chan_init(struct bt_l2cap_le_chan *chan)
chan->rx.mtu = CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU;
chan->chan.ops = &l2cap_ops;
LOG_DBG("RX MTU set to %u", chan->rx.mtu);
LOG_DBG("OtsL2capRxMtu[%u]", chan->rx.mtu);
}
static struct bt_gatt_ots_l2cap *find_free_l2cap_ctx(void)
@@ -196,7 +203,7 @@ static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server,
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Incoming conn %p", (void *)conn);
LOG_DBG("OtsL2capIncomingConn");
l2cap_ctx = find_free_l2cap_ctx();
if (l2cap_ctx) {
@@ -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;
@@ -224,11 +231,11 @@ static int bt_gatt_ots_l2cap_init(void)
err = bt_l2cap_server_register(&l2cap_server);
if (err) {
LOG_ERR("Unable to register OTS PSM");
LOG_ERR("OtsL2capPsmRegFail");
return err;
}
LOG_DBG("Initialized OTS L2CAP");
LOG_DBG("OtsL2capInit");
return 0;
}
@@ -245,7 +252,7 @@ int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
int err;
if (l2cap_ctx->tx.len != 0) {
LOG_ERR("L2CAP TX in progress");
LOG_WRN("OtsL2capTxInProgress");
return -EAGAIN;
}
@@ -253,11 +260,11 @@ int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
l2cap_ctx->tx.data = data;
l2cap_ctx->tx.len = len;
LOG_DBG("Starting TX on L2CAP CoC with %d byte packet", len);
LOG_DBG("OtsL2capTxStart[%u]", len);
err = ots_l2cap_send(l2cap_ctx);
if (err) {
LOG_ERR("Unable to send data over CoC: %d", err);
LOG_WRN("OtsL2capSendFail[%d]", err);
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
return 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;
@@ -288,31 +304,41 @@ int bt_gatt_ots_l2cap_connect(struct bt_conn *conn,
struct bt_gatt_ots_l2cap *ctx;
if (!conn) {
LOG_WRN("Invalid Connection");
LOG_WRN("OtsL2capConnNull");
return -ENOTCONN;
}
if (!l2cap_ctx) {
LOG_WRN("Invalid context");
LOG_WRN("OtsL2capCtxNull");
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);
(void)memset(&ctx->tx, 0, sizeof(ctx->tx));
LOG_DBG("Connecting L2CAP CoC");
LOG_DBG("OtsL2capConnecting");
err = bt_l2cap_chan_connect(conn, &ctx->ot_chan.chan, BT_GATT_OTS_L2CAP_PSM);
if (err) {
LOG_WRN("Unable to connect to psm %u (err %d)", BT_GATT_OTS_L2CAP_PSM, err);
LOG_WRN("OtsL2capConnFail[%u][%d]", BT_GATT_OTS_L2CAP_PSM, err);
} else {
LOG_DBG("L2CAP connection pending");
LOG_DBG("OtsL2capConnPending");
*l2cap_ctx = ctx;
}
@@ -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);
@@ -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
*/
@@ -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)
@@ -61,14 +65,19 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate(
};
bt_uuid_to_str(&param.type.uuid, str, BT_UUID_STR_LEN);
LOG_DBG("Validating Create procedure with size: 0x%08X and "
"type: %s", param.size, str);
LOG_DBG("OtsOacpValCreate[%08x][%s]", param.size, str);
if (!BT_OTS_OACP_GET_FEAT_CREATE(ots->features.oacp)) {
LOG_DBG("Create Procedure is not supported.");
LOG_DBG("OtsOacpCreateNotSupp");
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, &param, &obj);
if (err) {
goto exit;
@@ -76,21 +85,21 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate(
/* Verify Initialization Metadata */
if (strlen(obj->metadata.name) > 0) {
LOG_ERR("Object name shall be a zero length string after object creation.");
LOG_ERR("OtsOacpObjNameNotEmpty");
(void)bt_ots_obj_delete(ots, obj->id);
err = -ECANCELED;
goto exit;
}
if (obj->metadata.size.cur > 0) {
LOG_ERR("Object current size must be 0.");
LOG_ERR("OtsOacpObjCurSizeNotZero");
(void)bt_ots_obj_delete(ots, obj->id);
err = -ECANCELED;
goto exit;
}
if (!BT_OTS_OBJ_GET_PROP_WRITE(obj->metadata.props)) {
LOG_ERR("Created object must have write property.");
LOG_ERR("OtsOacpObjNoWrProp");
(void)bt_ots_obj_delete(ots, obj->id);
err = -ECANCELED;
goto exit;
@@ -99,7 +108,7 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate(
ots->cur_obj = obj;
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
LOG_DBG("Create procedure is complete");
LOG_DBG("OtsOacpCreateComp");
exit:
switch (err) {
@@ -127,27 +136,27 @@ static enum bt_gatt_ots_oacp_res_code oacp_delete_proc_validate(
int err;
if (!BT_OTS_OACP_GET_FEAT_DELETE(ots->features.oacp)) {
LOG_DBG("Delete Procedure is not supported.");
LOG_DBG("OtsOacpDeleteNotSupp");
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
if (!ots->cur_obj) {
LOG_DBG("No object is selected.");
LOG_DBG("OtsOacpNoObjSel");
return BT_GATT_OTS_OACP_RES_INV_OBJ;
}
if (!BT_OTS_OBJ_GET_PROP_DELETE(ots->cur_obj->metadata.props)) {
LOG_DBG("Object properties do not permit deletion.");
LOG_DBG("OtsOacpDeleteNotPermitted");
return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
}
err = bt_ots_obj_delete(ots, ots->cur_obj->id);
if (err) {
LOG_ERR("Deleting object during Delete procedure failed: %d", err);
LOG_ERR("OtsOacpDeleteObjFail[%d]", err);
goto exit;
}
LOG_DBG("Delete procedure is complete");
LOG_DBG("OtsOacpDeleteComp");
exit:
switch (err) {
@@ -173,8 +182,7 @@ static enum bt_gatt_ots_oacp_res_code oacp_checksum_proc_validate(
int err;
uint32_t checksum;
LOG_DBG("Validating Checksum procedure with offset: 0x%08X and "
"length: 0x%08X", params->offset, params->len);
LOG_DBG("OtsOacpValChecksum[%08x][%08x]", params->offset, params->len);
if (!ots->cur_obj) {
return BT_GATT_OTS_OACP_RES_INV_OBJ;
@@ -201,8 +209,7 @@ static enum bt_gatt_ots_oacp_res_code oacp_checksum_proc_validate(
checksum = bt_ots_client_calc_checksum((const uint8_t *)obj_data, params->len);
net_buf_simple_add_le32(resp_param, checksum);
LOG_DBG("Calculate from offset %u len %u checksum 0x%08x", params->offset,
params->len, checksum);
LOG_DBG("OtsOacpChecksumCalc[%u][%u][%08x]", params->offset, params->len, checksum);
return BT_GATT_OTS_OACP_RES_SUCCESS;
} else {
return BT_GATT_OTS_OACP_RES_OPER_FAILED;
@@ -217,8 +224,12 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
{
const struct bt_gatt_ots_oacp_read_params *params = &proc->read_params;
LOG_DBG("Validating Read procedure with offset: 0x%08X and "
"length: 0x%08X", params->offset, params->len);
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;
@@ -246,7 +257,7 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
memcpy(&ots->cur_obj->state.read_op.oacp_params, &proc->read_params,
sizeof(ots->cur_obj->state.read_op.oacp_params));
LOG_DBG("Read procedure is accepted");
LOG_DBG("OtsOacpRdAccepted");
return BT_GATT_OTS_OACP_RES_SUCCESS;
}
@@ -259,15 +270,14 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate(
{
struct bt_gatt_ots_oacp_write_params *params = &proc->write_params;
LOG_DBG("Validating Write procedure with offset: 0x%08X and "
"length: 0x%08X", params->offset, params->len);
LOG_DBG("OtsOacpValWr[%08x][%08x]", params->offset, params->len);
if (!ots->cur_obj) {
return BT_GATT_OTS_OACP_RES_INV_OBJ;
}
if (!BT_OTS_OACP_GET_FEAT_WRITE(ots->features.oacp)) {
LOG_DBG("Write Procedure is not supported.");
LOG_DBG("OtsOacpWrNotSupp");
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
@@ -302,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;
}
@@ -318,7 +329,7 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate(
memcpy(&ots->cur_obj->state.write_op.oacp_params, params,
sizeof(ots->cur_obj->state.write_op.oacp_params));
LOG_DBG("Write procedure is accepted");
LOG_DBG("OtsOacpWrAccepted");
return BT_GATT_OTS_OACP_RES_SUCCESS;
}
@@ -463,14 +474,14 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
if (!ots->cur_obj) {
LOG_ERR("OTS Read operation failed: no current object");
LOG_ERR("OtsOacpRdNoCurObj");
return;
}
if (ots->cb->obj_read == NULL &&
!(IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST)) {
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
LOG_ERR("OTS Read operation failed: there is no OTS Read callback");
LOG_ERR("OtsOacpRdNoCb");
return;
}
@@ -479,10 +490,10 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
offset = read_op->oacp_params.offset + read_op->sent_len;
if (read_op->sent_len >= read_op->oacp_params.len) {
LOG_DBG("OACP Read Op over L2CAP is completed");
LOG_DBG("OtsOacpRdL2capComp");
if (read_op->sent_len > read_op->oacp_params.len) {
LOG_WRN("More bytes sent that the client requested");
LOG_WRN("OtsOacpSentMoreThanReq");
}
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
@@ -508,7 +519,7 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
}
if (len < 0) {
LOG_ERR("OCAP Read Op failed with error: %zd", len);
LOG_ERR("OtsOacpRdOpFail[%zd]", len);
bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
@@ -516,12 +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_ERR("L2CAP CoC error: %d while trying to execute OACP "
"Read procedure", 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;
@@ -534,14 +564,13 @@ static void oacp_read_proc_execute(struct bt_ots *ots,
struct bt_gatt_ots_oacp_read_params *params;
if (!ots->cur_obj) {
LOG_ERR("Invalid Current Object on OACP Read procedure");
LOG_ERR("OtsOacpRdInvCurObj");
return;
}
params = &ots->cur_obj->state.read_op.oacp_params;
LOG_DBG("Executing Read procedure with offset: 0x%08X and "
"length: 0x%08X", params->offset, params->len);
LOG_DBG("OtsOacpExecRd[%08x][%08x]", params->offset, params->len);
oacp_read_proc_cb(&ots->l2cap, conn);
}
@@ -560,14 +589,21 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
if (!ots->cur_obj) {
LOG_ERR("Invalid Current Object on OACP Write procedure");
LOG_ERR("OtsOacpWrInvCurObj");
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("OTS Write operation failed: "
"there is no OTS Write callback");
LOG_ERR("OtsOacpWrNoCb");
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
l2cap_ctx->rx_done = NULL;
return -ENODEV;
}
@@ -575,7 +611,7 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
offset = write_op->oacp_params.offset + write_op->recv_len;
len = buf->len;
if (write_op->recv_len + len > write_op->oacp_params.len) {
LOG_WRN("More bytes received than the client indicated");
LOG_WRN("OtsOacpRecvMoreThanInd");
len = write_op->oacp_params.len - write_op->recv_len;
}
rem = write_op->oacp_params.len - (write_op->recv_len + len);
@@ -591,24 +627,24 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
* released by the l2cap layer. This is an unsupported use case at the moment.
*/
if (rc == -EINPROGRESS) {
LOG_ERR("Unsupported error code %zd returned by object write callback", rc);
LOG_ERR("OtsOacpWrCbUnsuppRc[%zd]", rc);
}
LOG_ERR("OTS Write operation failed with error: %zd", rc);
LOG_ERR("OtsOacpWrOpFail[%zd]", rc);
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
} else {
/* Return -EIO as an error if all of data was not written */
if (rc != len) {
len = 0;
rc = -EIO;
LOG_ERR("OTS Write operation partially failed");
LOG_ERR("OtsOacpWrPartialFail");
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
}
}
write_op->recv_len += len;
if (write_op->recv_len == write_op->oacp_params.len) {
LOG_DBG("OACP Write Op over L2CAP is completed");
LOG_DBG("OtsOacpWrL2capComp");
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
}
@@ -616,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
@@ -626,10 +669,27 @@ static void oacp_ind_cb(struct bt_conn *conn,
{
struct bt_ots *ots = (struct bt_ots *) params->attr->user_data;
LOG_DBG("Received OACP Indication ACK with status: 0x%04X", err);
LOG_DBG("OtsOacpRecvIndAck[%04x]", err);
ots->oacp_ind.ind_in_flight = false;
ots->oacp_ind.conn = NULL;
if (!ots->cur_obj) {
LOG_DBG("There is no object associated with this ACK");
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;
}
@@ -644,12 +704,13 @@ static void oacp_ind_cb(struct bt_conn *conn,
/* procedure is not in progress and was already completed */
break;
default:
LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type);
LOG_ERR("OtsOacpUnsuppState[%d]", ots->cur_obj->state.type);
break;
}
}
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)
@@ -657,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;
@@ -664,7 +726,7 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
oacp_res[oacp_res_len++] = oacp_status;
if (oacp_proc.type == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC &&
oacp_status == BT_GATT_OTS_OACP_RES_SUCCESS) {
oacp_status == BT_GATT_OTS_OACP_RES_SUCCESS) {
sys_put_le32(net_buf_simple_pull_le32(resp_param), (oacp_res + oacp_res_len));
oacp_res_len += sizeof(uint32_t);
}
@@ -677,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("Sending OACP indication");
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,
@@ -693,20 +762,21 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
NET_BUF_SIMPLE_DEFINE(resp_param, sizeof(uint32_t));
LOG_DBG("Object Action Control Point GATT Write Operation");
LOG_DBG("OtsOacpGattWr");
if (!ots->oacp_ind.is_enabled) {
LOG_WRN("OACP indications not enabled");
LOG_WRN("OtsOacpIndNotEnabled");
return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF);
}
if (offset != 0) {
LOG_ERR("Invalid offset of OACP Write Request");
LOG_WRN("OtsOacpWrReqInvOft");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (k_work_is_pending(&ots->oacp_ind.work)) {
LOG_ERR("OACP Write received before indication sent");
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);
}
@@ -715,26 +785,25 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
case 0:
oacp_status = oacp_proc_validate(conn, ots, &oacp_proc, &resp_param);
if (oacp_status != BT_GATT_OTS_OACP_RES_SUCCESS) {
LOG_WRN("OACP Write error status: 0x%02X", oacp_status);
LOG_WRN("OtsOacpWrErrStatus[%02x]", oacp_status);
}
break;
case -ENOTSUP:
oacp_status = BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
LOG_WRN("OACP unsupported procedure type: 0x%02X", oacp_proc.type);
LOG_WRN("OtsOacpUnsuppProcType[%02x]", oacp_proc.type);
break;
case -EBADMSG:
LOG_ERR("Invalid length of OACP Write Request for 0x%02X "
"Op Code", oacp_proc.type);
LOG_WRN("OtsOacpWrReqInvLen[%02x]", oacp_proc.type);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
case -ENODATA:
LOG_ERR("Invalid length of OACP Write Request");
LOG_WRN("OtsOacpWrReqNoData");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
default:
LOG_ERR("Invalid return code from oacp_command_decode: %d", decode_status);
LOG_ERR("OtsOacpDecodeInvRc[%d]", decode_status);
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;
}
@@ -745,10 +814,15 @@ void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr,
CONTAINER_OF((struct bt_gatt_ccc_managed_user_data *) attr->user_data,
struct bt_gatt_ots_indicate, ccc);
LOG_DBG("Object Action Control Point CCCD value: 0x%04X", value);
LOG_DBG("OtsOacpCccd[%04x]", value);
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;
}
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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);
@@ -255,3 +256,19 @@ void *bt_gatt_ots_obj_manager_assign(void)
return cur_manager;
}
void bt_gatt_ots_obj_manager_release(void *obj_manager)
{
struct bt_gatt_ots_obj_manager *manager = obj_manager;
if (manager == NULL) {
return;
}
for (size_t i = 0; i < CONFIG_BT_OTS_MAX_OBJ_CNT; i++) {
manager->pool[i].is_allocated = false;
}
sys_dlist_init(&manager->list);
manager->is_assigned = false;
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -42,6 +43,8 @@ bool bt_gatt_ots_obj_manager_obj_contains(void *obj_manager, struct bt_gatt_ots_
void *bt_gatt_ots_obj_manager_assign(void);
void bt_gatt_ots_obj_manager_release(void *obj_manager);
#ifdef __cplusplus
}
#endif
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -119,7 +120,7 @@ static enum bt_gatt_ots_olcp_res_code olcp_goto_proc_execute(
struct bt_gatt_ots_object *id_obj;
if (!BT_OTS_VALID_OBJ_ID(id)) {
LOG_DBG("Invalid object ID 0x%016llx", id);
LOG_WRN("OtsOlcpInvObjId[%016llx]", id);
return BT_GATT_OTS_OLCP_RES_INVALID_PARAMETER;
}
@@ -139,7 +140,7 @@ static enum bt_gatt_ots_olcp_res_code olcp_goto_proc_execute(
static enum bt_gatt_ots_olcp_res_code olcp_proc_execute(
struct bt_ots *ots, struct bt_gatt_ots_olcp_proc *proc)
{
LOG_DBG("Executing OLCP procedure with 0x%02X Op Code", proc->type);
LOG_DBG("OtsOlcpExecProc[%02x]", proc->type);
switch (proc->type) {
case BT_GATT_OTS_OLCP_PROC_FIRST:
@@ -200,16 +201,23 @@ static void olcp_ind_cb(struct bt_conn *conn,
struct bt_gatt_indicate_params *params,
uint8_t err)
{
LOG_DBG("Received OLCP Indication ACK with status: 0x%04X", 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("Sending OLCP indication");
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,
@@ -243,20 +258,27 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
struct bt_gatt_ots_olcp_proc olcp_proc;
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
LOG_DBG("Object List Control Point GATT Write Operation");
LOG_DBG("OtsOlcpGattWr");
if (!ots->olcp_ind.is_enabled) {
LOG_WRN("OLCP indications not enabled");
LOG_WRN("OtsOlcpIndNotEnabled");
return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF);
}
if (offset != 0) {
LOG_ERR("Invalid offset of OLCP Write Request");
LOG_WRN("OtsOlcpWrReqInvOft");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (k_work_is_pending(&ots->olcp_ind.work)) {
LOG_ERR("OLCP Write received before indication sent");
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);
}
@@ -267,14 +289,13 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
case 0:
olcp_status = olcp_proc_execute(ots, &olcp_proc);
if (olcp_status != BT_GATT_OTS_OLCP_RES_SUCCESS) {
LOG_WRN("OLCP Write error status: 0x%02X", olcp_status);
LOG_WRN("OtsOlcpWrErrStatus[%02x]", olcp_status);
} else if (old_obj != ots->cur_obj) {
char id[BT_OTS_OBJ_ID_STR_LEN];
bt_ots_obj_id_to_str(ots->cur_obj->id, id,
sizeof(id));
LOG_DBG("Selecting a new Current Object with id: %s",
id);
LOG_DBG("OtsOlcpSelNewCurObj[%s]", id);
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
bt_ots_dir_list_selected(ots->dir_list, ots->obj_manager,
@@ -288,21 +309,20 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
break;
case -ENOTSUP:
olcp_status = BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP;
LOG_WRN("OLCP unsupported procedure type: 0x%02X", olcp_proc.type);
LOG_WRN("OtsOlcpUnsuppProcType[%02x]", olcp_proc.type);
break;
case -EBADMSG:
LOG_ERR("Invalid length of OLCP Write Request for 0x%02X "
"Op Code", olcp_proc.type);
LOG_WRN("OtsOlcpWrReqInvLen[%02x]", olcp_proc.type);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
case -ENODATA:
LOG_ERR("Invalid size of OLCP Write Request");
LOG_WRN("OtsOlcpWrReqNoData");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
default:
LOG_ERR("Invalid return code from olcp_command_decode: %d", decode_status);
LOG_ERR("OtsOlcpDecodeInvRc[%d]", decode_status);
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;
}
@@ -313,10 +333,15 @@ void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr,
CONTAINER_OF((struct bt_gatt_ccc_managed_user_data *) attr->user_data,
struct bt_gatt_ots_indicate, ccc);
LOG_DBG("Object List Control Point CCCD value: 0x%04X", value);
LOG_DBG("OtsOlcpCccd[%04x]", value);
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;
}
}
@@ -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
*/
@@ -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);
/**
@@ -1974,7 +1976,8 @@ struct bt_bap_unicast_client_cb {
* @param dir The type of remote endpoints and capabilities discovered.
* @param codec_cap Remote capabilities.
*
* If discovery procedure has complete both @p codec and @p ep are set to NULL.
* Called once per record; the end of the procedure is reported by the
* discover callback below, not by a NULL @p codec_cap.
*/
void (*pac_record)(struct bt_conn *conn, enum bt_audio_dir dir,
const struct bt_audio_codec_cap *codec_cap);
@@ -1988,21 +1991,21 @@ struct bt_bap_unicast_client_cb {
* @param dir The type of remote endpoints and capabilities discovered.
* @param ep Remote endpoint.
*
* If discovery procedure has complete both @p codec and @p ep are set to NULL.
* Called once per endpoint; the end of the procedure is reported by the
* discover callback below, not by a NULL @p ep.
*/
void (*endpoint)(struct bt_conn *conn, enum bt_audio_dir dir, struct bt_bap_ep *ep);
/**
* @brief BAP discovery callback function.
*
* If discovery procedure has completed @p ep is set to NULL and @p err is 0.
* Called once the discovery procedure has completed, for the direction it
* covered.
*
* @param conn Connection to the remote unicast server.
* @param err Error value. 0 on success, GATT error on positive value or errno on
* negative value.
* @param dir The type of remote endpoints and capabilities discovered.
*
* If discovery procedure has complete both @p codec and @p ep are set to NULL.
*/
void (*discover)(struct bt_conn *conn, int err, enum bt_audio_dir dir);
@@ -2695,6 +2698,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 +2709,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);
/**
@@ -996,19 +996,21 @@ struct bt_cap_handover_broadcast_to_unicast_param {
/** @brief Broadcast ID of the @p broadcast_source
*
* Ignored if @p reception_stop_param is not NULL.
* Always required: receive state notifications are matched against the
* {broadcast_id, adv_sid, adv_type} triple to decide that reception stopped,
* whether or not @p reception_stop_param is given.
*/
uint32_t broadcast_id;
/** @brief Advertising set ID of the @p broadcast_source
*
* Ignored if @p reception_stop_param is not NULL.
* Always required, see @p broadcast_id.
*/
uint8_t adv_sid;
/** @brief Advertising type of the advertising address of @p broadcast_source
*
* Ignored if @p reception_stop_param is not NULL.
* Always required, see @p broadcast_id.
*/
uint8_t adv_type;
@@ -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),
@@ -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);
/**
@@ -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);
/**
@@ -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);
/**
@@ -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 */
@@ -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);
/**
@@ -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);
/**
@@ -32,86 +32,128 @@ struct bt_gatt_service;
extern struct bt_aics *lib_aics_insts_get(void);
extern void *lib_aics_svc_get(struct bt_aics *aics);
extern int lib_aics_init(void);
extern int lib_aics_deinit(void);
extern int lib_aics_client_init(void);
extern int lib_aics_client_deinit(void);
extern int lib_bap_base_init(void);
extern int lib_bap_base_deinit(void);
extern int lib_bap_iso_init(void);
extern int lib_bap_iso_deinit(void);
extern int lib_bap_stream_init(void);
extern int lib_bap_stream_deinit(void);
extern struct bt_gatt_service *lib_ascs_svc_get(void);
extern int lib_ascs_init(void);
extern int lib_ascs_deinit(void);
extern struct bt_gatt_service *lib_bap_bass_svc_get(void);
extern int lib_bap_broadcast_assistant_init(void);
extern int lib_bap_broadcast_assistant_deinit(void);
extern int lib_bap_broadcast_sink_init(void);
extern int lib_bap_broadcast_sink_deinit(void);
extern int lib_bap_broadcast_source_init(void);
extern int lib_bap_broadcast_source_deinit(void);
extern int lib_bap_scan_delegator_init(void);
extern int lib_bap_scan_delegator_deinit(void);
extern int lib_bap_unicast_client_init(void);
extern int lib_bap_unicast_client_deinit(void);
extern int lib_bap_unicast_server_init(void);
extern int lib_bap_unicast_server_deinit(void);
extern struct bt_gatt_service *lib_pacs_svc_get(void);
extern int lib_pacs_init(void);
extern int lib_pacs_deinit(void);
extern struct bt_gatt_service *lib_cas_svc_get(void);
extern int lib_cap_common_init(void);
extern int lib_cap_common_deinit(void);
extern int lib_cap_stream_init(void);
extern int lib_cap_stream_deinit(void);
extern int lib_cap_initiator_init(void);
extern int lib_cap_initiator_deinit(void);
extern int lib_cap_acceptor_init(void);
extern int lib_cap_acceptor_deinit(void);
extern int lib_cap_commander_init(void);
extern int lib_cap_commander_deinit(void);
extern int lib_ccid_init(void);
extern int lib_ccid_deinit(void);
extern int lib_ccp_call_control_client_init(void);
extern int lib_ccp_call_control_client_deinit(void);
extern int lib_ccp_call_control_server_init(void);
extern int lib_ccp_call_control_server_deinit(void);
extern int lib_codec_init(void);
extern int lib_codec_deinit(void);
extern int lib_csip_set_coordinator_init(void);
extern int lib_csip_set_coordinator_deinit(void);
extern void *lib_csip_set_member_svc_get(const void *svc_inst);
extern int lib_csip_set_member_init(void);
extern int lib_csip_set_member_deinit(void);
extern int lib_gmap_client_init(void);
extern int lib_gmap_client_deinit(void);
extern struct bt_gatt_service *lib_gmas_svc_get(void);
extern int lib_gmap_server_deinit(void);
extern struct bt_gatt_service *lib_has_svc_get(void);
extern int lib_has_init(void);
extern int lib_has_deinit(void);
extern int lib_has_client_init(void);
extern int lib_has_client_deinit(void);
extern int lib_mcc_init(void);
extern int lib_mcc_deinit(void);
extern struct bt_gatt_service *lib_gmcs_svc_get(void);
extern struct bt_gatt_service *lib_mcs_server_list_get(void);
extern int lib_mcs_init(void);
extern int lib_mcs_deinit(void);
extern int lib_media_proxy_init(void);
extern int lib_media_proxy_deinit(void);
extern int lib_micp_mic_ctlr_init(void);
extern int lib_micp_mic_ctlr_deinit(void);
extern struct bt_gatt_service *lib_mics_svc_get(void);
extern int lib_micp_mic_dev_init(void);
extern int lib_micp_mic_dev_deinit(void);
extern int lib_mpl_init(void);
extern int lib_mpl_deinit(void);
extern int lib_pbp_init(void);
extern int lib_pbp_deinit(void);
extern int lib_tbs_client_init(void);
extern int lib_tbs_client_deinit(void);
extern struct bt_gatt_service *lib_gtbs_svc_get(void);
extern struct bt_gatt_service *lib_tbs_server_list_get(void);
extern int lib_tbs_init(void);
extern int lib_tbs_deinit(void);
extern struct bt_gatt_service *lib_tmas_svc_get(void);
extern int lib_tmap_init(void);
extern int lib_tmap_deinit(void);
extern int lib_vcp_vol_ctlr_init(void);
extern int lib_vcp_vol_ctlr_deinit(void);
extern struct bt_gatt_service *lib_vcs_svc_get(void);
extern int lib_vcp_vol_rend_init(void);
extern int lib_vcp_vol_rend_deinit(void);
struct bt_vocs;
struct bt_vocs_server;
extern int lib_vocs_client_init(void);
extern int lib_vocs_client_deinit(void);
extern struct bt_vocs_server *lib_vocs_insts_get(void);
extern void *lib_vocs_svc_get(struct bt_vocs *vocs);
extern int lib_vocs_init(void);
extern int lib_vocs_deinit(void);
struct bt_conn;
struct bt_gatt_attr;
+31
View File
@@ -0,0 +1,31 @@
# BLE Audio external-RAM (.bss -> PSRAM) link-time placement.
#
# The bt_extram_bss scheme and bt_bss/bt_common sections referenced below are
# defined in the component's linker_common.lf. ldgen merges all registered
# fragments into one model, so the cross-file reference resolves.
# Prebuilt archive: EXT_RAM_BSS_ATTR can't apply at its compile time, so move
# its .bss to SPIRAM at link time instead.
[mapping:ble_audio]
archive: libble_audio.a
entries:
if BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY = y:
* (bt_extram_bss)
# Move the OTS objects' .bss to SPIRAM at link time (their net_buf pools can't
# take a per-var attr). Gates mirror CMakeLists so no entry names an object
# that isn't built.
[mapping:ble_audio_ots]
archive: libbt.a
entries:
if BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY = y && (BT_OTS = y || BT_OTS_CLIENT = y):
ots_l2cap (bt_extram_bss)
if BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY = y && BT_OTS = y:
ots (bt_extram_bss)
ots_oacp (bt_extram_bss)
ots_obj_manager (bt_extram_bss)
ots_olcp (bt_extram_bss)
if BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY = y && BT_OTS = y && BT_OTS_DIR_LIST_OBJ = y:
ots_dir_list (bt_extram_bss)
if BT_AUDIO_BSS_SEG_EXTERNAL_MEMORY = y && BT_OTS_CLIENT = y:
ots_client (bt_extram_bss)
+4 -16
View File
@@ -44,36 +44,24 @@ 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"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/crypto.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/keys.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/mem.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/timer.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/utf8.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/utils.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/uuid.c"
"${CMAKE_CURRENT_LIST_DIR}/host/iso/iso.c"
"${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"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/include"
"${CMAKE_CURRENT_LIST_DIR}/include/subsys/bluetooth"
"${CMAKE_CURRENT_LIST_DIR}/include/subsys/bluetooth/host"
"${CMAKE_CURRENT_LIST_DIR}/include"
+34
View File
@@ -104,6 +104,38 @@ if BT_ISO
endif # BT_ISO_BROADCAST
config BT_ISO_BSS_SEG_EXTERNAL_MEMORY
bool "Place BLE ISO .bss segment in external SPIRAM"
depends on SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
default n
help
Place the zero-initialized (.bss) static data of the esp_ble_iso
component in external SPIRAM instead of internal DRAM, to free
internal RAM.
Safe because the esp_ble_iso sources register no IRAM ISRs: their
data is only touched in task context, never while the flash cache
is disabled. Hot ISO data-path state that must stay internal for
latency is excluded at each placement site, not by this switch.
Requires SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY (which makes SPIRAM
mandatory at boot). Only .bss is moved; initialized (.data) statistics
stay internal.
config BT_ISO_HEAP_EXTERNAL_MEMORY
bool "Place BLE ISO heap allocations in external SPIRAM"
depends on SPIRAM
default n
help
Route esp_ble_iso runtime heap allocations (control-plane only:
GATT/GAP event marshalling, CIS/CIG setup params, CCC pools) to
external SPIRAM via heap_caps_*(MALLOC_CAP_SPIRAM) instead of the
internal heap.
The real-time ISO data path (TX/RX SDU packet buffers) is excluded
at each allocation site and stays in internal DRAM. On SPIRAM
exhaustion the allocation returns NULL (no internal fallback).
config BT_ISO_DISPATCH_MONITOR
bool "Monitor ISO task callback dispatch latency"
depends on !BT_ISO_NO_LOG
@@ -157,6 +189,8 @@ if BT_ISO
choice BT_ISO_LOG_LEVEL
prompt "BLE_ISO_LOG_LEVEL"
default BT_ISO_LOG_LEVEL_INFO if BT_LOG_CRITICAL_ONLY_ISO
default BT_ISO_LOG_LEVEL_DEBUG if BLE_COMPRESSED_LOG_ENABLE
default BT_ISO_LOG_LEVEL_WARNING
depends on !BT_ISO_NO_LOG
help
@@ -6,8 +6,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/logging/log.h>
#include "esp_ble_iso_common_api.h"
LOG_MODULE_REGISTER(ISO_API, CONFIG_BT_ISO_LOG_LEVEL);
esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size,
bool (*func)(uint8_t type,
const uint8_t *data,
@@ -35,15 +41,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, &ltv[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 ? &ltv[i + 2] : NULL, data_len, user_data) == false) {
return ESP_FAIL;
}
@@ -134,6 +134,7 @@ esp_err_t esp_ble_iso_chan_connect(esp_ble_iso_connect_param_t *param,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL) {
LOG_WRN("AclUnknown[%u]", conn_handle);
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
@@ -272,6 +273,7 @@ esp_err_t esp_ble_iso_big_create(uint8_t adv_handle,
adv = bt_le_ext_adv_find(adv_handle);
if (adv == NULL) {
LOG_WRN("ExtAdvUnknown[%u]", adv_handle);
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
@@ -292,20 +294,27 @@ 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;
LOG_WRN("PaSyncUnknown[%u]", sync_handle);
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 +408,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);
}
@@ -434,6 +443,44 @@ unregister_gap:
return ESP_FAIL;
}
esp_err_t esp_ble_iso_common_deinit(const esp_ble_iso_deinit_info_t *info)
{
bool reset_ext_adv = true;
bool reset_pa_sync = true;
int err;
if (info) {
if (!info->reset_acl_conn) {
LOG_WRN("ConnKeepUnsupported");
return ESP_ERR_NOT_SUPPORTED;
}
reset_ext_adv = info->reset_ext_adv;
reset_pa_sync = info->reset_pa_sync;
}
if (bt_le_host_check_idle()) {
return ESP_ERR_INVALID_STATE;
}
err = bt_le_host_deinit();
if (err) {
return ESP_ERR_TIMEOUT;
}
if (reset_ext_adv) {
bt_le_ext_adv_state_reset();
}
if (reset_pa_sync) {
bt_le_per_adv_sync_state_reset();
}
bt_le_gap_app_cb_unregister();
return ESP_OK;
}
#if CONFIG_BT_BLUEDROID_ENABLED
uint8_t esp_ble_iso_bluedroid_get_gattc_if(void)
{

Some files were not shown because too many files have changed in this diff Show More