mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
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:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¶m.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, ¶m, &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;
|
||||
|
||||
Submodule components/bt/esp_ble_audio/lib/lib updated: b69d90c860...774d777766
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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, <v[i + 2], data_len, user_data) == false) {
|
||||
/* Zero-length values are valid (e.g. BROADCAST_IMMEDIATE flag-only LTV).
|
||||
* Match Zephyr bt_audio_data_parse: invoke callback with data=NULL. */
|
||||
if (func(type, data_len > 0 ? <v[i + 2] : NULL, data_len, user_data) == false) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user