mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
Merge branch 'idf/ble_audio_fixes' into 'master'
fix(ble_audio): Miscellaneous fixes for ISO & LE Audio (stage 2) See merge request espressif/esp-idf!51158
This commit is contained in:
@@ -161,13 +161,25 @@ endif()
|
||||
|
||||
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
|
||||
list(APPEND ble_audio_include_dirs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots"
|
||||
)
|
||||
|
||||
list(APPEND ble_audio_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_l2cap.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/l2cap.c"
|
||||
)
|
||||
|
||||
if(CONFIG_BT_NIMBLE_ENABLED)
|
||||
list(APPEND ble_audio_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/nimble/l2cap.c"
|
||||
)
|
||||
else()
|
||||
list(APPEND ble_audio_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/bluedroid/l2cap.c"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CONFIG_BT_OTS)
|
||||
list(APPEND ble_audio_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_oacp.c"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -150,6 +159,8 @@ esp_err_t esp_ble_audio_bap_unicast_group_reconfig(esp_ble_audio_bap_unicast_gro
|
||||
int err;
|
||||
|
||||
if (unicast_group == NULL || param == NULL ||
|
||||
param->params == NULL ||
|
||||
param->params_count == 0 ||
|
||||
param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
@@ -727,22 +738,29 @@ esp_err_t esp_ble_audio_bap_broadcast_sink_delete(esp_ble_audio_bap_broadcast_si
|
||||
#if CONFIG_BT_BAP_SCAN_DELEGATOR
|
||||
esp_err_t esp_ble_audio_bap_scan_delegator_register(esp_ble_audio_bap_scan_delegator_cb_t *cb)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
int err;
|
||||
|
||||
err = bt_bap_scan_delegator_register_safe(cb);
|
||||
bt_le_host_lock();
|
||||
|
||||
err = bt_bap_scan_delegator_register(cb);
|
||||
if (err) {
|
||||
return ESP_FAIL;
|
||||
ret = ESP_FAIL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
#if BLE_AUDIO_SVC_DEFERRED_ADD
|
||||
err = bt_le_bass_init();
|
||||
if (err) {
|
||||
bt_bap_scan_delegator_unregister_safe();
|
||||
return ESP_FAIL;
|
||||
bt_bap_scan_delegator_unregister();
|
||||
ret = ESP_FAIL;
|
||||
goto end;
|
||||
}
|
||||
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
|
||||
|
||||
return ESP_OK;
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_ble_audio_bap_scan_delegator_unregister(void)
|
||||
@@ -798,7 +816,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_add_src(const esp_ble_audio_bap_scan_
|
||||
param->sid > BT_GAP_SID_MAX ||
|
||||
param->pa_state > ESP_BLE_AUDIO_BAP_PA_STATE_NO_PAST ||
|
||||
param->encrypt_state > ESP_BLE_AUDIO_BAP_BIG_ENC_STATE_BAD_CODE ||
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
|
||||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -818,7 +837,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_mod_src(const esp_ble_audio_bap_scan_
|
||||
|
||||
if (param == NULL ||
|
||||
param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX ||
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
|
||||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -959,7 +979,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_add_src(uint16_t conn_handle,
|
||||
param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX ||
|
||||
param->addr.type > BT_ADDR_LE_RANDOM ||
|
||||
param->adv_sid > BT_GAP_SID_MAX ||
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
|
||||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -989,7 +1010,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_mod_src(uint16_t conn_handle,
|
||||
int err;
|
||||
|
||||
if (param == NULL ||
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
|
||||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
|
||||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -1288,7 +1310,7 @@ esp_err_t esp_ble_audio_bap_base_foreach_subgroup(const esp_ble_audio_bap_base_t
|
||||
}
|
||||
|
||||
err = bt_bap_base_foreach_subgroup(base, func, user_data);
|
||||
if (err) {
|
||||
if (err && err != -ECANCELED) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -1411,7 +1433,7 @@ esp_err_t esp_ble_audio_bap_base_subgroup_foreach_bis(const esp_ble_audio_bap_ba
|
||||
}
|
||||
|
||||
err = bt_bap_base_subgroup_foreach_bis(subgroup, func, user_data);
|
||||
if (err) {
|
||||
if (err && err != -ECANCELED) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_ble_audio_gap_app_post_event(uint8_t type, void *param)
|
||||
void esp_ble_audio_gap_app_post_event(uint16_t type, void *param)
|
||||
{
|
||||
bt_le_gap_app_post_event(type, param);
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_lock(const esp_ble_audio_csip_set_c
|
||||
{
|
||||
int err;
|
||||
|
||||
if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
|
||||
if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_release(const esp_ble_audio_csip_se
|
||||
{
|
||||
int err;
|
||||
|
||||
if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
|
||||
if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -126,7 +126,7 @@ typedef struct {
|
||||
* @param type Event type.
|
||||
* @param param Event parameters.
|
||||
*/
|
||||
void esp_ble_audio_gap_app_post_event(uint8_t type, void *param);
|
||||
void esp_ble_audio_gap_app_post_event(uint16_t type, void *param);
|
||||
|
||||
#if !CONFIG_BT_BLUEDROID_ENABLED
|
||||
/**
|
||||
|
||||
@@ -76,13 +76,10 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
inc_vocs_svc_count = vcp_included->vocs_cnt;
|
||||
inc_aics_svc_count = vcp_included->aics_cnt;
|
||||
|
||||
bt_le_bluedroid_set_svc_in_progress(VOCS_IN_PROGRESS);
|
||||
|
||||
/* VCS may include zero or more instances of VOCS */
|
||||
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
|
||||
for (size_t i = 0; i < vcp_included->vocs_cnt; i++) {
|
||||
inc_vocs_insts[i].svc_p = lib_vocs_svc_get(vcp_included->vocs[i]);
|
||||
if (!inc_vocs_insts[i].svc_p) {
|
||||
LOG_ERR("[B]VocsSvcGetFail[%u]", i);
|
||||
@@ -101,7 +98,7 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
|
||||
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
|
||||
|
||||
/* VCS may include zero or more instances of AICS */
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
for (size_t i = 0; i < vcp_included->aics_cnt; i++) {
|
||||
inc_aics_insts[i].svc_p = lib_aics_svc_get(vcp_included->aics[i]);
|
||||
if (!inc_aics_insts[i].svc_p) {
|
||||
LOG_ERR("[B]AicsSvcGetFail[%u]", i);
|
||||
@@ -116,6 +113,12 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Commit counts only after every instance initialized — mid-loop
|
||||
* failure leaves them at 0 (reset on entry) so vcs_start() never
|
||||
* iterates partially-initialized entries. */
|
||||
inc_vocs_svc_count = vcp_included->vocs_cnt;
|
||||
inc_aics_svc_count = vcp_included->aics_cnt;
|
||||
}
|
||||
|
||||
bt_le_bluedroid_set_svc_in_progress(VCS_IN_PROGRESS);
|
||||
|
||||
@@ -173,15 +173,14 @@ static void inc_svc_add_cb(uint16_t service_id, uint16_t attr_id, uint8_t status
|
||||
int result = status;
|
||||
|
||||
if (service_id != svc_handle) {
|
||||
/* Stale after timeout; take already failed — ignore without give. */
|
||||
LOG_ERR("[B]IncSvcAddMismatch[%u][%u][%u]",
|
||||
svc_in_progress, service_id, svc_handle);
|
||||
result = -1;
|
||||
goto end;
|
||||
return;
|
||||
}
|
||||
|
||||
inc_svc_handle = attr_id;
|
||||
|
||||
end:
|
||||
bt_le_bluedroid_gatts_sem_give(result);
|
||||
}
|
||||
|
||||
@@ -190,9 +189,8 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id,
|
||||
{
|
||||
int result = status;
|
||||
|
||||
if (service_id != svc_handle || ((tBT_UUID *)uuid)->len != 2) {
|
||||
/* uuid16 is only meaningful when len == 2; for 32/128-bit UUIDs the
|
||||
* union access would log the first 2 bytes of a wider value. */
|
||||
if (service_id != svc_handle) {
|
||||
/* Stale after timeout; take already failed — ignore without give. */
|
||||
if (((tBT_UUID *)uuid)->len == 2) {
|
||||
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][2][0x%04x]",
|
||||
svc_in_progress, service_id, svc_handle,
|
||||
@@ -202,6 +200,13 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id,
|
||||
svc_in_progress, service_id, svc_handle,
|
||||
((tBT_UUID *)uuid)->len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (((tBT_UUID *)uuid)->len != 2) {
|
||||
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][%u][nonU16]",
|
||||
svc_in_progress, service_id, svc_handle,
|
||||
((tBT_UUID *)uuid)->len);
|
||||
result = -1;
|
||||
goto end;
|
||||
}
|
||||
@@ -214,14 +219,13 @@ end:
|
||||
|
||||
static void svc_start_cb(uint16_t service_id, uint8_t status)
|
||||
{
|
||||
int result = status;
|
||||
|
||||
if (service_id != svc_handle) {
|
||||
/* Stale after timeout; take already failed — ignore without give. */
|
||||
LOG_ERR("[B]SvcStartMismatch[%u][%u]", service_id, svc_handle);
|
||||
result = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
bt_le_bluedroid_gatts_sem_give(result);
|
||||
bt_le_bluedroid_gatts_sem_give(status);
|
||||
}
|
||||
|
||||
static struct gatts_svc_cb svc_cb = {
|
||||
@@ -456,7 +460,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
|
||||
uint8_t inst_id;
|
||||
tBT_UUID uuid;
|
||||
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
for (size_t i = 0; i < svc->attr_count; i++) {
|
||||
curr_attr = &svc->attrs[i];
|
||||
@@ -470,7 +474,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(curr_attr->user_data);
|
||||
BT_LE_ASSERT(curr_attr->user_data);
|
||||
|
||||
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
|
||||
inst_id = get_svc_inst_id(uuid.uu.uuid16);
|
||||
@@ -496,7 +500,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(curr_attr->user_data);
|
||||
BT_LE_ASSERT(curr_attr->user_data);
|
||||
|
||||
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
|
||||
inst_id = get_svc_inst_id(uuid.uu.uuid16);
|
||||
@@ -561,7 +565,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
|
||||
next_attr = &svc->attrs[i + 1];
|
||||
perm = bt_le_bluedroid_gatt_perm_convert(next_attr->perm);
|
||||
|
||||
assert(curr_attr->user_data);
|
||||
BT_LE_ASSERT(curr_attr->user_data);
|
||||
|
||||
chrc = curr_attr->user_data;
|
||||
bt_le_bluedroid_gatt_uuid_convert(chrc->uuid, &uuid);
|
||||
@@ -612,7 +616,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
|
||||
|
||||
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc)
|
||||
{
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
svc_handle = svc->attrs[0].handle;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ static void ascs_svc_add_ase_cp_chr(struct ble_gatt_chr_def *chr)
|
||||
chr->arg = NULL;
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC;
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &ase_control_point_handle;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ static void ascs_svc_add_ase_snk_chr(struct ble_gatt_chr_def *chrs)
|
||||
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
|
||||
chr->arg = UINT_TO_POINTER(i);
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &ase_snk_handle[i];
|
||||
}
|
||||
@@ -98,7 +98,7 @@ static void ascs_svc_add_ase_src_chr(struct ble_gatt_chr_def *chrs)
|
||||
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
|
||||
chr->arg = UINT_TO_POINTER(i);
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &ase_src_handle[i];
|
||||
}
|
||||
@@ -128,7 +128,7 @@ int bt_le_nimble_ascs_attr_handle_set(void)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
assert(ase_control_point_handle >= 2);
|
||||
BT_LE_ASSERT(ase_control_point_handle >= 2);
|
||||
start_handle = ase_control_point_handle - 2; /* server attr handle & char def handle */
|
||||
#if CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0
|
||||
end_handle = ase_src_handle[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT - 1] + 1; /* cccd attr handle */
|
||||
@@ -202,6 +202,7 @@ static int ascs_svc_check(void)
|
||||
|
||||
int bt_le_nimble_ascs_init(void)
|
||||
{
|
||||
bool ascs_added = false;
|
||||
uint16_t chr_count;
|
||||
int rc;
|
||||
|
||||
@@ -211,7 +212,7 @@ int bt_le_nimble_ascs_init(void)
|
||||
LOG_DBG("[N]AscsInit[%u]", chr_count);
|
||||
|
||||
gatt_svc_ascs[0].characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
|
||||
assert(gatt_svc_ascs[0].characteristics);
|
||||
BT_LE_ASSERT(gatt_svc_ascs[0].characteristics);
|
||||
|
||||
ascs_svc_add_ase_cp_chr((void *)(gatt_svc_ascs[0].characteristics + 0));
|
||||
|
||||
@@ -234,6 +235,7 @@ int bt_le_nimble_ascs_init(void)
|
||||
LOG_ERR("[N]AscsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
ascs_added = true;
|
||||
|
||||
rc = ascs_svc_check();
|
||||
if (rc) {
|
||||
@@ -243,7 +245,12 @@ int bt_le_nimble_ascs_init(void)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
free((void *)gatt_svc_ascs[0].characteristics);
|
||||
gatt_svc_ascs[0].characteristics = NULL;
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (!ascs_added) {
|
||||
free((void *)gatt_svc_ascs[0].characteristics);
|
||||
gatt_svc_ascs[0].characteristics = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ static void bass_svc_add_recv_state_chr(struct ble_gatt_chr_def *chrs)
|
||||
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
|
||||
chr->arg = UINT_TO_POINTER(i);
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &bass_recv_state_handle[i];
|
||||
}
|
||||
@@ -104,7 +104,7 @@ int bt_le_nimble_bass_attr_handle_set(void)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
assert(bass_control_point_handle >= 2);
|
||||
BT_LE_ASSERT(bass_control_point_handle >= 2);
|
||||
start_handle = bass_control_point_handle - 2; /* server attr handle & char def handle */
|
||||
end_handle = bass_recv_state_handle[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT - 1] + 1; /* cccd attr handle */
|
||||
|
||||
@@ -154,7 +154,7 @@ static int bass_svc_check(void)
|
||||
for (size_t i = 0; i < bass_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(bass_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -172,6 +172,7 @@ static int bass_svc_check(void)
|
||||
|
||||
int bt_le_nimble_bass_init(void)
|
||||
{
|
||||
bool bass_added = false;
|
||||
uint16_t chr_count;
|
||||
int rc;
|
||||
|
||||
@@ -181,7 +182,7 @@ int bt_le_nimble_bass_init(void)
|
||||
LOG_DBG("[N]BassInit[%u]", chr_count);
|
||||
|
||||
gatt_svc_bass->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
|
||||
assert(gatt_svc_bass->characteristics);
|
||||
BT_LE_ASSERT(gatt_svc_bass->characteristics);
|
||||
|
||||
bass_svc_add_control_point_chr((void *)(gatt_svc_bass->characteristics + 0));
|
||||
|
||||
@@ -198,6 +199,7 @@ int bt_le_nimble_bass_init(void)
|
||||
LOG_ERR("[N]BassAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
bass_added = true;
|
||||
|
||||
rc = bass_svc_check();
|
||||
if (rc) {
|
||||
@@ -207,7 +209,12 @@ int bt_le_nimble_bass_init(void)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
free((void *)gatt_svc_bass->characteristics);
|
||||
gatt_svc_bass->characteristics = NULL;
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (!bass_added) {
|
||||
free((void *)gatt_svc_bass->characteristics);
|
||||
gatt_svc_bass->characteristics = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
|
||||
#if CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE
|
||||
#define CSIS_CHR_FLAGS_SIRK \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
#else /* CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE */
|
||||
#define CSIS_CHR_FLAGS_SIRK \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
@@ -48,14 +48,14 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
|
||||
#if CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE
|
||||
#define CSIS_CHR_FLAGS_SET_SIZE \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
#else /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */
|
||||
#define CSIS_CHR_FLAGS_SET_SIZE \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */
|
||||
|
||||
#define CSIS_CHR_FLAGS_SET_LOCK \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
#define CSIS_CHR_FLAGS_RANK \
|
||||
@@ -63,7 +63,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
|
||||
#if CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE
|
||||
#define CSIS_CHR_FLAGS_SET_NAME \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
#else /* CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE */
|
||||
#define CSIS_CHR_FLAGS_SET_NAME \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
@@ -124,7 +124,7 @@ static int csis_svc_check(void)
|
||||
struct ble_gatt_svc_def *csis = &gatt_svc_csis[i];
|
||||
struct bt_gatt_service *svc = csis_insts[i].svc_p;
|
||||
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
for (const struct ble_gatt_chr_def *chr = csis->characteristics;
|
||||
chr && chr->uuid; chr++) {
|
||||
@@ -135,7 +135,7 @@ static int csis_svc_check(void)
|
||||
for (size_t j = 0; j < svc->attr_count; j++) {
|
||||
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -161,8 +161,8 @@ int bt_le_nimble_csis_attr_handle_set(void)
|
||||
for (size_t i = 0; i < csis_svc_count; i++) {
|
||||
struct bt_gatt_service *zsvc = csis_insts[i].svc_p;
|
||||
|
||||
assert(zsvc);
|
||||
assert(csis_insts[i].sirk_handle >= 2);
|
||||
BT_LE_ASSERT(zsvc);
|
||||
BT_LE_ASSERT(csis_insts[i].sirk_handle >= 2);
|
||||
|
||||
/* SIRK is always the first characteristic, so its value handle anchors the range. */
|
||||
start_handle = csis_insts[i].sirk_handle - 2; /* server attr handle & char def handle */
|
||||
@@ -181,7 +181,7 @@ int bt_le_nimble_csis_attr_handle_set(void)
|
||||
const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)(zsvc->attrs + j)->uuid;
|
||||
uint16_t chr_handle = 0;
|
||||
|
||||
if (uuid->uuid.type != BT_UUID_TYPE_16) {
|
||||
if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ static void csis_svc_init(struct csis_inst *inst,
|
||||
svc->includes = NULL;
|
||||
|
||||
svc->characteristics = bt_le_ext_calloc(CSIS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
|
||||
assert(svc->characteristics);
|
||||
BT_LE_ASSERT(svc->characteristics);
|
||||
|
||||
/* Build the NimBLE characteristics from the ones actually present in the Zephyr
|
||||
* service. Optional characteristics (set size, lock, rank) may be absent depending
|
||||
@@ -255,7 +255,7 @@ static void csis_svc_init(struct csis_inst *inst,
|
||||
for (size_t i = 0; i < zsvc->attr_count; i++) {
|
||||
const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)zsvc->attrs[i].uuid;
|
||||
|
||||
if (uuid->uuid.type != BT_UUID_TYPE_16) {
|
||||
if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -292,11 +292,12 @@ static void csis_svc_init(struct csis_inst *inst,
|
||||
* the terminator slot. Trips if the switch matches a 6th char: a new CSIS case added
|
||||
* without bumping the (5 + 1), or a duplicate UUID in the Zephyr service table.
|
||||
*/
|
||||
assert(chr_cnt < CSIS_CHR_COUNT);
|
||||
BT_LE_ASSERT(chr_cnt < CSIS_CHR_COUNT);
|
||||
}
|
||||
|
||||
int bt_le_nimble_csis_init(void *svc, uint8_t count)
|
||||
{
|
||||
bool csis_added = false;
|
||||
int rc;
|
||||
|
||||
LOG_DBG("[N]CsisInit[%u]", count);
|
||||
@@ -325,6 +326,7 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count)
|
||||
LOG_ERR("[N]CsisAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
csis_added = true;
|
||||
|
||||
rc = csis_svc_check();
|
||||
if (rc) {
|
||||
@@ -334,9 +336,14 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
for (size_t i = 0; i < csis_svc_count; i++) {
|
||||
free((void *)gatt_svc_csis[i].characteristics);
|
||||
gatt_svc_csis[i].characteristics = NULL;
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (!csis_added) {
|
||||
for (size_t i = 0; i < csis_svc_count; i++) {
|
||||
free((void *)gatt_svc_csis[i].characteristics);
|
||||
gatt_svc_csis[i].characteristics = NULL;
|
||||
}
|
||||
}
|
||||
csis_svc_count = 0;
|
||||
return rc;
|
||||
|
||||
@@ -50,7 +50,7 @@ static int gmas_build_svc(struct bt_gatt_service *gmas_svc)
|
||||
for (size_t i = 0; i < gmas_svc->attr_count; i++) {
|
||||
const struct bt_uuid_16 *u = (const struct bt_uuid_16 *)gmas_svc->attrs[i].uuid;
|
||||
|
||||
if (u->uuid.type != BT_UUID_TYPE_16) {
|
||||
if (!u || u->uuid.type != BT_UUID_TYPE_16) {
|
||||
prev_decl = false;
|
||||
continue;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ static int gmas_svc_check(void)
|
||||
for (size_t i = 0; i < gmas_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(gmas_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
|
||||
@@ -60,7 +60,7 @@ static void has_svc_add_features_chr(struct ble_gatt_chr_def *chr)
|
||||
chr->arg = NULL;
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_HAS_FEATURES_NOTIFIABLE
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
#else /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC;
|
||||
#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
||||
@@ -78,9 +78,9 @@ static void has_svc_add_control_point_chr(struct ble_gatt_chr_def *chr)
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE
|
||||
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE |
|
||||
BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY;
|
||||
BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
#else /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
|
||||
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &has_control_point_handle;
|
||||
@@ -97,7 +97,7 @@ static void has_svc_add_preset_index_chr(struct ble_gatt_chr_def *chr)
|
||||
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
|
||||
chr->arg = NULL;
|
||||
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
|
||||
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
|
||||
chr->min_key_size = 16;
|
||||
chr->val_handle = &has_preset_index_handle;
|
||||
}
|
||||
@@ -125,9 +125,15 @@ int bt_le_nimble_has_attr_handle_set(void)
|
||||
LOG_ERR("[N]HasSvcGetFail");
|
||||
return -ENODEV;
|
||||
}
|
||||
assert(has_svc->attr_count > 0);
|
||||
BT_LE_ASSERT(has_svc->attr_count > 0);
|
||||
|
||||
end_handle = start_handle + has_svc->attr_count - 1;
|
||||
#if CONFIG_BT_HAS_ACTIVE_PRESET_INDEX
|
||||
BT_LE_ASSERT(has_preset_index_handle >= 2);
|
||||
end_handle = has_preset_index_handle + 1; /* cccd attr handle */
|
||||
#else
|
||||
BT_LE_ASSERT(has_control_point_handle >= 2);
|
||||
end_handle = has_control_point_handle + 1; /* cccd attr handle */
|
||||
#endif
|
||||
|
||||
LOG_DBG("[N]HasAttrHdlSet[%u][%u][%u]",
|
||||
start_handle, end_handle, has_svc->attr_count);
|
||||
@@ -175,7 +181,7 @@ static int has_svc_check(void)
|
||||
for (size_t i = 0; i < has_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(has_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -193,6 +199,7 @@ static int has_svc_check(void)
|
||||
|
||||
int bt_le_nimble_has_init(void)
|
||||
{
|
||||
bool has_added = false;
|
||||
uint8_t chr_count;
|
||||
int rc;
|
||||
|
||||
@@ -206,7 +213,7 @@ int bt_le_nimble_has_init(void)
|
||||
LOG_DBG("[N]HasInit[%u]", chr_count);
|
||||
|
||||
gatt_svc_has->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
|
||||
assert(gatt_svc_has->characteristics);
|
||||
BT_LE_ASSERT(gatt_svc_has->characteristics);
|
||||
|
||||
has_svc_add_features_chr((void *)(gatt_svc_has->characteristics + 0));
|
||||
|
||||
@@ -227,6 +234,7 @@ int bt_le_nimble_has_init(void)
|
||||
LOG_ERR("[N]HasAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
has_added = true;
|
||||
|
||||
rc = has_svc_check();
|
||||
if (rc) {
|
||||
@@ -236,7 +244,12 @@ int bt_le_nimble_has_init(void)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
free((void *)gatt_svc_has->characteristics);
|
||||
gatt_svc_has->characteristics = NULL;
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (!has_added) {
|
||||
free((void *)gatt_svc_has->characteristics);
|
||||
gatt_svc_has->characteristics = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -72,10 +72,10 @@ LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
|
||||
#define INC_OTS_CHR_FLAGS_ACTION_CP \
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_OTS_CHR_FLAGS_LIST_CP \
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_ots_svc_count;
|
||||
|
||||
@@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_player_name_handle,
|
||||
},
|
||||
@@ -174,7 +174,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_track_changed_handle,
|
||||
}, {
|
||||
@@ -182,7 +182,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_track_title_handle,
|
||||
}, {
|
||||
@@ -190,7 +190,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_track_duration_handle,
|
||||
}, {
|
||||
@@ -199,7 +199,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_track_position_handle,
|
||||
@@ -209,7 +209,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_playback_speed_handle,
|
||||
@@ -218,7 +218,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_seeking_speed_handle,
|
||||
},
|
||||
@@ -237,7 +237,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_current_track_obj_id_handle,
|
||||
@@ -247,7 +247,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_next_track_obj_id_handle,
|
||||
@@ -256,7 +256,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_parent_group_obj_id_handle,
|
||||
}, {
|
||||
@@ -265,7 +265,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_current_group_obj_id_handle,
|
||||
@@ -277,7 +277,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
|
||||
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_playing_order_handle,
|
||||
@@ -294,7 +294,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_media_state_handle,
|
||||
}, {
|
||||
@@ -303,7 +303,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_media_control_point_handle,
|
||||
}, {
|
||||
@@ -311,7 +311,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_media_control_opcodes_handle,
|
||||
},
|
||||
@@ -322,7 +322,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_search_control_point_handle,
|
||||
}, {
|
||||
@@ -330,7 +330,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &mcs_search_results_obj_id_handle,
|
||||
},
|
||||
@@ -366,9 +366,9 @@ static int inc_ots_attr_handle_set(void)
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
|
||||
assert(ots && ots->service);
|
||||
BT_LE_ASSERT(ots && ots->service);
|
||||
|
||||
assert(inc_ots_chr_feature_handle >= 2);
|
||||
BT_LE_ASSERT(inc_ots_chr_feature_handle >= 2);
|
||||
start_handle = inc_ots_chr_feature_handle - 2; /* server attr handle & char def handle */
|
||||
end_handle = inc_ots_chr_list_cp_handle + 1; /* cccd for chr Object List Control Point */
|
||||
|
||||
@@ -472,7 +472,7 @@ static int gmcs_svc_check(void)
|
||||
for (size_t i = 0; i < gmcs_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(gmcs_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -498,8 +498,8 @@ static int inc_ots_svc_check(void)
|
||||
* the service exist in the service defined by Zephyr.
|
||||
*/
|
||||
|
||||
assert(gatt_svc_inc_ots);
|
||||
assert(ots && ots->service);
|
||||
BT_LE_ASSERT(gatt_svc_inc_ots);
|
||||
BT_LE_ASSERT(ots && ots->service);
|
||||
|
||||
LOG_DBG("[N]IncOtsSvcCheck");
|
||||
|
||||
@@ -512,7 +512,7 @@ static int inc_ots_svc_check(void)
|
||||
for (size_t i = 0; i < ots->service->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(ots->service->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -551,7 +551,7 @@ static int inc_ots_svc_init(void)
|
||||
uint8_t chr_count;
|
||||
|
||||
attrs = bt_ots_svc_decl_get(ots);
|
||||
assert(attrs);
|
||||
BT_LE_ASSERT(attrs);
|
||||
|
||||
chr_count = 0;
|
||||
|
||||
@@ -576,7 +576,7 @@ static int inc_ots_svc_init(void)
|
||||
|
||||
/* An additional characteristic consist of all 0s indicating end of characteristics */
|
||||
svc->characteristics = bt_le_ext_calloc(INC_OTS_CHR_COUNT + 1, sizeof(struct ble_gatt_chr_def));
|
||||
assert(svc->characteristics);
|
||||
BT_LE_ASSERT(svc->characteristics);
|
||||
|
||||
/* Characteristic - OTS Feature */
|
||||
inc_ots_chr_init((void *)&svc->characteristics[0],
|
||||
@@ -632,6 +632,7 @@ static int inc_ots_svc_init(void)
|
||||
|
||||
int bt_le_nimble_gmcs_init(bool ots_included)
|
||||
{
|
||||
bool inc_ots_added = false;
|
||||
int rc;
|
||||
|
||||
LOG_DBG("[N]GmcsInit[%u]", ots_included);
|
||||
@@ -642,14 +643,14 @@ int bt_le_nimble_gmcs_init(bool ots_included)
|
||||
|
||||
/* Extra one for terminating the included service array with NULL */
|
||||
gmcs_inc_svcs = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def *));
|
||||
assert(gmcs_inc_svcs);
|
||||
BT_LE_ASSERT(gmcs_inc_svcs);
|
||||
|
||||
/* Extra one for terminating the OTS service array */
|
||||
gatt_svc_inc_ots = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def));
|
||||
assert(gatt_svc_inc_ots);
|
||||
BT_LE_ASSERT(gatt_svc_inc_ots);
|
||||
|
||||
ots = lib_mcs_get_ots();
|
||||
assert(ots && ots->service);
|
||||
BT_LE_ASSERT(ots && ots->service);
|
||||
|
||||
rc = inc_ots_svc_init();
|
||||
if (rc) {
|
||||
@@ -669,6 +670,7 @@ int bt_le_nimble_gmcs_init(bool ots_included)
|
||||
LOG_ERR("[N]IncOtsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
inc_ots_added = true;
|
||||
|
||||
rc = inc_ots_svc_check();
|
||||
if (rc) {
|
||||
@@ -703,18 +705,23 @@ int bt_le_nimble_gmcs_init(bool ots_included)
|
||||
|
||||
free:
|
||||
#if CONFIG_BT_OTS
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (ots_included) {
|
||||
inc_ots_svc_count = 0;
|
||||
|
||||
free(gmcs_inc_svcs);
|
||||
gmcs_inc_svcs = NULL;
|
||||
|
||||
if (gatt_svc_inc_ots[0].characteristics) {
|
||||
free((void *)gatt_svc_inc_ots[0].characteristics);
|
||||
gatt_svc_inc_ots[0].characteristics = NULL;
|
||||
if (!inc_ots_added) {
|
||||
if (gatt_svc_inc_ots[0].characteristics) {
|
||||
free((void *)gatt_svc_inc_ots[0].characteristics);
|
||||
gatt_svc_inc_ots[0].characteristics = NULL;
|
||||
}
|
||||
free(gatt_svc_inc_ots);
|
||||
gatt_svc_inc_ots = NULL;
|
||||
}
|
||||
free(gatt_svc_inc_ots);
|
||||
gatt_svc_inc_ots = NULL;
|
||||
}
|
||||
gatt_svc_gmcs[0].includes = NULL;
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
@@ -779,7 +786,7 @@ static int mcs_svc_check(void)
|
||||
for (size_t i = 0; i < mcs_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(mcs_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -798,6 +805,7 @@ static int mcs_svc_check(void)
|
||||
int bt_le_nimble_mcs_init(void)
|
||||
{
|
||||
uint8_t count = CONFIG_BT_MCS_INSTANCE_COUNT;
|
||||
bool mcs_added = false;
|
||||
int rc;
|
||||
|
||||
/* NULL when CONFIG_BT_MCS_INSTANCE_COUNT == 0: nothing discrete to add. */
|
||||
@@ -811,7 +819,7 @@ int bt_le_nimble_mcs_init(void)
|
||||
/* One ble_gatt_svc_def per instance + a zeroed terminator. Persists for the
|
||||
* GATT DB lifetime (NimBLE references these defs until ble_gatts_start). */
|
||||
gatt_svc_mcs = bt_le_ext_calloc(count + 1, sizeof(struct ble_gatt_svc_def));
|
||||
assert(gatt_svc_mcs);
|
||||
BT_LE_ASSERT(gatt_svc_mcs);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
gatt_svc_mcs[i].type = BLE_GATT_SVC_TYPE_PRIMARY;
|
||||
@@ -837,6 +845,7 @@ int bt_le_nimble_mcs_init(void)
|
||||
LOG_ERR("[N]McsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
mcs_added = true;
|
||||
|
||||
rc = mcs_svc_check();
|
||||
if (rc) {
|
||||
@@ -846,7 +855,12 @@ int bt_le_nimble_mcs_init(void)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
free(gatt_svc_mcs);
|
||||
gatt_svc_mcs = NULL;
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (!mcs_added) {
|
||||
free(gatt_svc_mcs);
|
||||
gatt_svc_mcs = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
#define INC_AICS_CHR_COUNT (6 + 1)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_STATE \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_GAIN \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
@@ -44,13 +44,13 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_STATUS \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_CONTROL \
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_DESCRIPTION \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
|
||||
@@ -92,7 +92,7 @@ static struct ble_gatt_svc_def gatt_svc_mics[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | \
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ_ENC | \
|
||||
BLE_GATT_CHR_F_WRITE_ENC,
|
||||
.min_key_size = 16,
|
||||
@@ -116,8 +116,8 @@ static int inc_aics_svc_check(void)
|
||||
* the service exist in the service defined by Zephyr.
|
||||
*/
|
||||
|
||||
assert(gatt_svc_inc_aics);
|
||||
assert(inc_aics_insts);
|
||||
BT_LE_ASSERT(gatt_svc_inc_aics);
|
||||
BT_LE_ASSERT(inc_aics_insts);
|
||||
|
||||
LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count);
|
||||
|
||||
@@ -125,7 +125,7 @@ static int inc_aics_svc_check(void)
|
||||
struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i];
|
||||
struct bt_gatt_service *svc = inc_aics_insts[i].svc_p;
|
||||
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
for (const struct ble_gatt_chr_def *chr = aics->characteristics;
|
||||
chr && chr->uuid; chr++) {
|
||||
@@ -136,7 +136,7 @@ static int inc_aics_svc_check(void)
|
||||
for (size_t j = 0; j < svc->attr_count; j++) {
|
||||
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -176,7 +176,7 @@ static int mics_svc_check(void)
|
||||
for (size_t i = 0; i < mics_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(mics_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -198,14 +198,14 @@ static int inc_aics_attr_handle_set(void)
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
|
||||
assert(inc_aics_insts);
|
||||
BT_LE_ASSERT(inc_aics_insts);
|
||||
|
||||
LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count);
|
||||
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
assert(inc_aics_insts[i].svc_p);
|
||||
BT_LE_ASSERT(inc_aics_insts[i].svc_p);
|
||||
|
||||
assert(inc_aics_insts[i].state_handle >= 2);
|
||||
BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2);
|
||||
start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */
|
||||
end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */
|
||||
|
||||
@@ -307,7 +307,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
|
||||
svc->includes = NULL;
|
||||
|
||||
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
|
||||
assert(svc->characteristics);
|
||||
BT_LE_ASSERT(svc->characteristics);
|
||||
|
||||
/* Characteristic - Audio Input State */
|
||||
inc_aics_chr_init((void *)&svc->characteristics[0],
|
||||
@@ -349,6 +349,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
|
||||
int bt_le_nimble_mics_init(void *micp_inc)
|
||||
{
|
||||
struct bt_micp_included *micp_included;
|
||||
bool inc_aics_added = false;
|
||||
uint8_t inc_count;
|
||||
int rc;
|
||||
|
||||
@@ -371,16 +372,16 @@ int bt_le_nimble_mics_init(void *micp_inc)
|
||||
inc_count = inc_aics_svc_count + 1;
|
||||
|
||||
mics_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
|
||||
assert(mics_inc_svcs);
|
||||
BT_LE_ASSERT(mics_inc_svcs);
|
||||
|
||||
/* MICS may include zero or more instances of AICS */
|
||||
if (inc_aics_svc_count) {
|
||||
inc_aics_insts = bt_le_ext_calloc(inc_aics_svc_count, sizeof(struct inc_aics_inst));
|
||||
assert(inc_aics_insts);
|
||||
BT_LE_ASSERT(inc_aics_insts);
|
||||
|
||||
/* Extra one for terminating the AICS service array */
|
||||
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
|
||||
assert(gatt_svc_inc_aics);
|
||||
BT_LE_ASSERT(gatt_svc_inc_aics);
|
||||
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
|
||||
@@ -406,6 +407,7 @@ int bt_le_nimble_mics_init(void *micp_inc)
|
||||
LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
inc_aics_added = true;
|
||||
|
||||
rc = inc_aics_svc_check();
|
||||
if (rc) {
|
||||
@@ -439,22 +441,28 @@ int bt_le_nimble_mics_init(void *micp_inc)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. That covers
|
||||
* inc_aics_insts too: the registered chr_defs point at its handle fields. */
|
||||
if (micp_included) {
|
||||
free(mics_inc_svcs);
|
||||
mics_inc_svcs = NULL;
|
||||
|
||||
if (inc_aics_svc_count) {
|
||||
free(inc_aics_insts);
|
||||
inc_aics_insts = NULL;
|
||||
if (!inc_aics_added) {
|
||||
free(inc_aics_insts);
|
||||
inc_aics_insts = NULL;
|
||||
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
free((void *)gatt_svc_inc_aics[i].characteristics);
|
||||
gatt_svc_inc_aics[i].characteristics = NULL;
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
free((void *)gatt_svc_inc_aics[i].characteristics);
|
||||
gatt_svc_inc_aics[i].characteristics = NULL;
|
||||
}
|
||||
|
||||
free(gatt_svc_inc_aics);
|
||||
gatt_svc_inc_aics = NULL;
|
||||
}
|
||||
|
||||
free(gatt_svc_inc_aics);
|
||||
gatt_svc_inc_aics = NULL;
|
||||
|
||||
inc_aics_svc_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_PAC_SNK_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
|
||||
@@ -77,13 +77,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_WRITE_ENC,
|
||||
#elif CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
#elif !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
|
||||
@@ -99,7 +99,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_PAC_SRC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
|
||||
@@ -114,13 +114,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_WRITE_ENC,
|
||||
#elif CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
#elif !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
|
||||
@@ -134,7 +134,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
.val_handle = &pacs_ava_ctx_handle,
|
||||
}, {
|
||||
@@ -143,7 +143,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
|
||||
@@ -173,13 +173,13 @@ int bt_le_nimble_pacs_attr_handle_set(void)
|
||||
}
|
||||
|
||||
#if CONFIG_BT_PAC_SNK
|
||||
assert(pacs_snk_handle >= 2);
|
||||
BT_LE_ASSERT(pacs_snk_handle >= 2);
|
||||
start_handle = pacs_snk_handle - 2; /* server attr handle & char def handle */
|
||||
#elif CONFIG_BT_PAC_SRC
|
||||
assert(pacs_src_handle >= 2);
|
||||
BT_LE_ASSERT(pacs_src_handle >= 2);
|
||||
start_handle = pacs_src_handle - 2; /* server attr handle & char def handle */
|
||||
#else
|
||||
assert(pacs_ava_ctx_handle >= 2);
|
||||
BT_LE_ASSERT(pacs_ava_ctx_handle >= 2);
|
||||
start_handle = pacs_ava_ctx_handle - 2; /* server attr handle & char def handle */
|
||||
#endif
|
||||
#if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE
|
||||
@@ -234,7 +234,7 @@ static int pacs_svc_check(void)
|
||||
for (size_t i = 0; i < pacs_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(pacs_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
|
||||
@@ -42,7 +42,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_UCI_VAL),
|
||||
@@ -56,21 +56,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_URI_LIST_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_STRENGTH_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_INTERVAL_VAL),
|
||||
@@ -86,7 +86,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_CCID_VAL),
|
||||
@@ -100,21 +100,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_URI_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_STATE_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_CONTROL_POINT_VAL),
|
||||
@@ -122,7 +122,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE | \
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_OPTIONAL_OPCODES_VAL),
|
||||
@@ -136,21 +136,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_CALL_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_FRIENDLY_NAME_VAL),
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
0, /* No more characteristics in this service. */
|
||||
@@ -233,7 +233,7 @@ static int gtbs_svc_check(void)
|
||||
for (size_t i = 0; i < gtbs_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(gtbs_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -328,7 +328,7 @@ static int tbs_svc_check(void)
|
||||
for (size_t i = 0; i < tbs_list[0].attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(tbs_list[0].attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,17 +37,17 @@ LOG_MODULE_REGISTER(LEA_VCS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
#define INC_VOCS_CHR_COUNT (4 + 1)
|
||||
|
||||
#define INC_VOCS_CHR_FLAGS_STATE \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_VOCS_CHR_FLAGS_LOCATION \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
#define INC_VOCS_CHR_FLAGS_CONTROL \
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
#define INC_VOCS_CHR_FLAGS_DESCRIPTION \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_vocs_svc_count;
|
||||
@@ -73,7 +73,7 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO
|
||||
#define INC_AICS_CHR_COUNT (6 + 1)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_STATE \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_GAIN \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
@@ -82,13 +82,13 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_STATUS \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_CONTROL \
|
||||
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
#define INC_AICS_CHR_FLAGS_DESCRIPTION \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
|
||||
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
|
||||
|
||||
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
|
||||
@@ -129,7 +129,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = {
|
||||
.access_cb = bt_le_nimble_gatts_access_cb_safe,
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
.min_key_size = 16,
|
||||
}, {
|
||||
/* Volume Control Service -- Volume COntrol Point characteristic */
|
||||
@@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = {
|
||||
.arg = NULL,
|
||||
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
|
||||
#if CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
|
||||
#else /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
|
||||
#endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
|
||||
@@ -170,7 +170,7 @@ static int inc_vocs_svc_check(void)
|
||||
* the service exist in the service defined by Zephyr.
|
||||
*/
|
||||
|
||||
assert(gatt_svc_inc_vocs);
|
||||
BT_LE_ASSERT(gatt_svc_inc_vocs);
|
||||
|
||||
LOG_DBG("[N]IncVocsSvcCheck[%u]", inc_vocs_svc_count);
|
||||
|
||||
@@ -178,7 +178,7 @@ static int inc_vocs_svc_check(void)
|
||||
struct ble_gatt_svc_def *vocs = &gatt_svc_inc_vocs[i];
|
||||
struct bt_gatt_service *svc = inc_vocs_insts[i].svc_p;
|
||||
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
for (const struct ble_gatt_chr_def *chr = vocs->characteristics;
|
||||
chr && chr->uuid; chr++) {
|
||||
@@ -189,7 +189,7 @@ static int inc_vocs_svc_check(void)
|
||||
for (size_t j = 0; j < svc->attr_count; j++) {
|
||||
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -215,7 +215,7 @@ static int inc_aics_svc_check(void)
|
||||
* the service exist in the service defined by Zephyr.
|
||||
*/
|
||||
|
||||
assert(gatt_svc_inc_aics);
|
||||
BT_LE_ASSERT(gatt_svc_inc_aics);
|
||||
|
||||
LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count);
|
||||
|
||||
@@ -223,7 +223,7 @@ static int inc_aics_svc_check(void)
|
||||
struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i];
|
||||
struct bt_gatt_service *svc = inc_aics_insts[i].svc_p;
|
||||
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
for (const struct ble_gatt_chr_def *chr = aics->characteristics;
|
||||
chr && chr->uuid; chr++) {
|
||||
@@ -234,7 +234,7 @@ static int inc_aics_svc_check(void)
|
||||
for (size_t j = 0; j < svc->attr_count; j++) {
|
||||
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -278,7 +278,7 @@ static int vcs_svc_check(void)
|
||||
for (size_t i = 0; i < vcs_svc->attr_count; i++) {
|
||||
uuid = (const struct bt_uuid_16 *)(vcs_svc->attrs + i)->uuid;
|
||||
|
||||
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
|
||||
uuid->val == check->value) {
|
||||
chr_found = true;
|
||||
break;
|
||||
@@ -303,9 +303,9 @@ static int inc_vocs_attr_handle_set(void)
|
||||
LOG_DBG("[N]IncVocsAttrHdlSet[%u]", inc_vocs_svc_count);
|
||||
|
||||
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
|
||||
assert(inc_vocs_insts[i].svc_p);
|
||||
BT_LE_ASSERT(inc_vocs_insts[i].svc_p);
|
||||
|
||||
assert(inc_vocs_insts[i].state_handle >= 2);
|
||||
BT_LE_ASSERT(inc_vocs_insts[i].state_handle >= 2);
|
||||
start_handle = inc_vocs_insts[i].state_handle - 2; /* server attr handle & char def handle */
|
||||
end_handle = inc_vocs_insts[i].description_handle + 1; /* cccd for chr Audio Output Description */
|
||||
|
||||
@@ -339,9 +339,9 @@ static int inc_aics_attr_handle_set(void)
|
||||
LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count);
|
||||
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
assert(inc_aics_insts[i].svc_p);
|
||||
BT_LE_ASSERT(inc_aics_insts[i].svc_p);
|
||||
|
||||
assert(inc_aics_insts[i].state_handle >= 2);
|
||||
BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2);
|
||||
start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */
|
||||
end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */
|
||||
|
||||
@@ -450,7 +450,7 @@ static void inc_vocs_svc_init(struct inc_vocs_inst *inst,
|
||||
svc->includes = NULL;
|
||||
|
||||
svc->characteristics = bt_le_ext_calloc(INC_VOCS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
|
||||
assert(svc->characteristics);
|
||||
BT_LE_ASSERT(svc->characteristics);
|
||||
|
||||
/* Characteristic - Volume Offset State */
|
||||
inc_vocs_chr_init((void *)&svc->characteristics[0],
|
||||
@@ -503,7 +503,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
|
||||
svc->includes = NULL;
|
||||
|
||||
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
|
||||
assert(svc->characteristics);
|
||||
BT_LE_ASSERT(svc->characteristics);
|
||||
|
||||
/* Characteristic - Audio Input State */
|
||||
inc_aics_chr_init((void *)&svc->characteristics[0],
|
||||
@@ -545,6 +545,8 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
|
||||
int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
{
|
||||
struct bt_vcp_included *vcp_included;
|
||||
bool inc_vocs_added = false;
|
||||
bool inc_aics_added = false;
|
||||
uint8_t inc_count;
|
||||
int rc;
|
||||
|
||||
@@ -571,13 +573,13 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
inc_count = inc_vocs_svc_count + inc_aics_svc_count + 1;
|
||||
|
||||
vcs_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
|
||||
assert(vcs_inc_svcs);
|
||||
BT_LE_ASSERT(vcs_inc_svcs);
|
||||
|
||||
/* VCS may include zero or more instances of VOCS */
|
||||
if (inc_vocs_svc_count) {
|
||||
/* Extra one for terminating the VOCS service array */
|
||||
gatt_svc_inc_vocs = bt_le_ext_calloc(inc_vocs_svc_count + 1, sizeof(struct ble_gatt_svc_def));
|
||||
assert(gatt_svc_inc_vocs);
|
||||
BT_LE_ASSERT(gatt_svc_inc_vocs);
|
||||
|
||||
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
|
||||
inc_vocs_svc_init(&inc_vocs_insts[i], &gatt_svc_inc_vocs[i]);
|
||||
@@ -603,6 +605,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
LOG_ERR("[N]IncVocsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
inc_vocs_added = true;
|
||||
|
||||
rc = inc_vocs_svc_check();
|
||||
if (rc) {
|
||||
@@ -614,7 +617,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
if (inc_aics_svc_count) {
|
||||
/* Extra one for terminating the AICS service array */
|
||||
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
|
||||
assert(gatt_svc_inc_aics);
|
||||
BT_LE_ASSERT(gatt_svc_inc_aics);
|
||||
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
|
||||
@@ -640,6 +643,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc);
|
||||
goto free;
|
||||
}
|
||||
inc_aics_added = true;
|
||||
|
||||
rc = inc_aics_svc_check();
|
||||
if (rc) {
|
||||
@@ -673,15 +677,20 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
|
||||
return 0;
|
||||
|
||||
free:
|
||||
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
|
||||
* offers no per-service unregister, so an added service must be leaked
|
||||
* rather than freed into a dangling entry of its global list. */
|
||||
if (vcp_included) {
|
||||
if (inc_vocs_svc_count) {
|
||||
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
|
||||
free((void *)gatt_svc_inc_vocs[i].characteristics);
|
||||
gatt_svc_inc_vocs[i].characteristics = NULL;
|
||||
}
|
||||
if (!inc_vocs_added) {
|
||||
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
|
||||
free((void *)gatt_svc_inc_vocs[i].characteristics);
|
||||
gatt_svc_inc_vocs[i].characteristics = NULL;
|
||||
}
|
||||
|
||||
free(gatt_svc_inc_vocs);
|
||||
gatt_svc_inc_vocs = NULL;
|
||||
free(gatt_svc_inc_vocs);
|
||||
gatt_svc_inc_vocs = NULL;
|
||||
}
|
||||
|
||||
inc_vocs_svc_count = 0;
|
||||
}
|
||||
@@ -689,7 +698,7 @@ free:
|
||||
if (inc_aics_svc_count) {
|
||||
/* A VOCS-phase failure reaches here with the count already set
|
||||
* but gatt_svc_inc_aics not yet allocated (still NULL). */
|
||||
if (gatt_svc_inc_aics) {
|
||||
if (!inc_aics_added && gatt_svc_inc_aics) {
|
||||
for (size_t i = 0; i < inc_aics_svc_count; i++) {
|
||||
free((void *)gatt_svc_inc_aics[i].characteristics);
|
||||
gatt_svc_inc_aics[i].characteristics = NULL;
|
||||
|
||||
@@ -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;
|
||||
@@ -89,7 +107,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
||||
rc = attr->read(conn, attr, (void *)&cb, UINT16_MAX, 0);
|
||||
if (rc < 0) {
|
||||
LOG_WRN("[N]RdGattErr[%u][%d]", attr_handle, rc);
|
||||
return BT_GATT_ERR(rc);
|
||||
return to_nimble_att_err(rc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -98,12 +116,12 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
||||
attr = bt_gatts_find_attr_by_handle(attr_handle);
|
||||
if (attr == NULL) {
|
||||
LOG_WRN("[N]WrAttrNotFound[%u]", attr_handle);
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE);
|
||||
return BLE_ATT_ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (attr->write == NULL) {
|
||||
LOG_WRN("[N]WrNotPermit");
|
||||
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
||||
return BLE_ATT_ERR_WRITE_NOT_PERMITTED;
|
||||
}
|
||||
|
||||
if (BT_UUID_16(attr->uuid)->val == BT_UUID_BASS_CONTROL_POINT_VAL) {
|
||||
@@ -120,14 +138,14 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
||||
if (OS_MBUF_PKTLEN(ctx->om) > alloc_len) {
|
||||
LOG_WRN("[N]WrBassCtrlPtTooLong[%u > %u]",
|
||||
OS_MBUF_PKTLEN(ctx->om), alloc_len);
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
data = bt_le_ext_calloc(1, alloc_len);
|
||||
assert(data);
|
||||
BT_LE_ASSERT(data);
|
||||
|
||||
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
|
||||
assert(rc == 0);
|
||||
BT_LE_ASSERT(rc == 0);
|
||||
|
||||
rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0);
|
||||
|
||||
@@ -136,20 +154,26 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
||||
} else {
|
||||
LOG_DBG("[N]Wr[%u]", OS_MBUF_PKTLEN(ctx->om));
|
||||
|
||||
data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om));
|
||||
assert(data);
|
||||
/* A zero-length write is a legal PDU, but calloc(0) returns NULL
|
||||
* on IDF and would trip the assert; pass it on unallocated. */
|
||||
if (OS_MBUF_PKTLEN(ctx->om) > 0) {
|
||||
data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om));
|
||||
BT_LE_ASSERT(data);
|
||||
|
||||
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
|
||||
assert(rc == 0);
|
||||
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
|
||||
BT_LE_ASSERT(rc == 0);
|
||||
}
|
||||
|
||||
rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0);
|
||||
|
||||
free(data);
|
||||
data = NULL;
|
||||
if (data != NULL) {
|
||||
free(data);
|
||||
data = NULL;
|
||||
}
|
||||
}
|
||||
if (rc < 0) {
|
||||
LOG_WRN("[N]WrGattErr[%u][%d]", attr_handle, rc);
|
||||
return BT_GATT_ERR(rc);
|
||||
return to_nimble_att_err(rc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -45,6 +45,8 @@ struct bt_le_audio_start_info {
|
||||
|
||||
int bt_le_audio_init(void);
|
||||
|
||||
void bt_le_audio_deinit(void);
|
||||
|
||||
int bt_le_ascs_init(void);
|
||||
|
||||
int bt_le_bass_init(void);
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <../host/conn_internal.h>
|
||||
#include <../host/hci_core.h>
|
||||
|
||||
#include "utils/assert.h"
|
||||
#include "utils/mem.h"
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
@@ -52,6 +53,10 @@
|
||||
#include "nimble/init.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
#include "ots/adapter/l2cap.h"
|
||||
#endif
|
||||
|
||||
#include "../../../lib/include/audio.h"
|
||||
|
||||
#include "esp_ble_audio_common_api.h"
|
||||
@@ -180,7 +185,7 @@ static const uint16_t ext_structs[] = {
|
||||
sizeof(struct bt_bond_info),
|
||||
};
|
||||
|
||||
#define LEA_VERSION (0x20260724)
|
||||
#define LEA_VERSION (0x20260802)
|
||||
|
||||
struct lib_ext_cfgs {
|
||||
/* BLE */
|
||||
@@ -1205,29 +1210,13 @@ static void log_error(const char *format, ...)
|
||||
#endif /* (CONFIG_BT_AUDIO_LOG_LEVEL >= BT_ISO_LOG_ERROR) */
|
||||
}
|
||||
|
||||
/* Fatal assert handler registered into lib_ext_funcs._assert.
|
||||
* Always logged (no LOG_LEVEL gate) — this is the last message before
|
||||
* abort, and the user needs the context to diagnose.
|
||||
*/
|
||||
static void assert_fatal(const char *tag, size_t info,
|
||||
const char *file, int line, const char *func)
|
||||
{
|
||||
esp_log_write(ESP_LOG_ERROR, LEA_TAG,
|
||||
BT_ISO_LOG_COLOR_E
|
||||
"E (%lu) %s: LibAssert[%s][info=%u][%s:%d][%s]"
|
||||
BT_ISO_LOG_RESET_COLOR "\n",
|
||||
esp_log_timestamp(), LEA_TAG,
|
||||
tag, (unsigned)info, file, line, func);
|
||||
abort();
|
||||
}
|
||||
|
||||
static const struct lib_ext_funcs ext_funcs = {
|
||||
._log_dbg = (void *)log_debug,
|
||||
._log_inf = (void *)log_info,
|
||||
._log_wrn = (void *)log_warn,
|
||||
._log_err = (void *)log_error,
|
||||
|
||||
._assert = (void *)assert_fatal,
|
||||
._assert = (void *)bt_le_assert,
|
||||
|
||||
#if CONFIG_BT_AUDIO_HEAP_EXTERNAL_MEMORY
|
||||
._malloc = (void *)bt_le_ext_malloc,
|
||||
@@ -1319,8 +1308,6 @@ static const struct lib_ext_funcs ext_funcs = {
|
||||
._conn_index = (void *)bt_conn_index,
|
||||
._conn_lookup_index = (void *)bt_conn_lookup_index,
|
||||
._conn_get_dst = (void *)bt_conn_get_dst,
|
||||
._conn_ref = (void *)bt_conn_ref,
|
||||
._conn_unref = (void *)bt_conn_unref,
|
||||
|
||||
._gatt_svc_register = (void *)bt_gatt_service_register,
|
||||
._gatt_svc_unregister = (void *)bt_gatt_service_unregister,
|
||||
@@ -2128,10 +2115,32 @@ int bt_le_audio_init(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
err = bt_le_l2cap_ots_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
return bt_le_bluedroid_audio_init();
|
||||
err = bt_le_bluedroid_audio_init();
|
||||
#else
|
||||
return bt_le_nimble_audio_init();
|
||||
err = bt_le_nimble_audio_init();
|
||||
#endif
|
||||
if (err) {
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
bt_le_l2cap_ots_deinit();
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_le_audio_deinit(void)
|
||||
{
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
bt_le_l2cap_ots_deinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -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,829 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
|
||||
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <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);
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
#if CONFIG_BT_OTS_CLIENT
|
||||
extern int bt_gatt_ots_client_conn_cb_register(void);
|
||||
extern void bt_gatt_ots_client_conn_cb_unregister(void);
|
||||
#endif /* CONFIG_BT_OTS_CLIENT */
|
||||
extern int bt_gatt_ots_l2cap_init(void);
|
||||
|
||||
static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->psm == psm) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
|
||||
{
|
||||
LOG_DBG("L2capChanAdd[%04x]", psm);
|
||||
|
||||
/* Attach channel to the connection */
|
||||
if (sys_slist_find(&conn->channels, &chan->node, NULL)) {
|
||||
LOG_WRN("L2capChanExists[%04x]", psm);
|
||||
return false;
|
||||
}
|
||||
|
||||
sys_slist_append(&conn->channels, &chan->node);
|
||||
chan->conn = conn;
|
||||
|
||||
/* Set channel PSM */
|
||||
BT_L2CAP_LE_CHAN(chan)->psm = psm;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_server *server = NULL;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) {
|
||||
if (server->psm == psm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
static inline uint16_t err_to_result(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case -ENOMEM:
|
||||
return L2CAP_LE_ERR_NO_RESOURCES;
|
||||
case -EACCES:
|
||||
return L2CAP_LE_ERR_AUTHORIZATION;
|
||||
case -EPERM:
|
||||
return L2CAP_LE_ERR_KEY_SIZE;
|
||||
case -ENOTSUP:
|
||||
/* This handle the cases where a fixed channel is registered but
|
||||
* for some reason (e.g. controller not supporting a feature)
|
||||
* cannot be used.
|
||||
*/
|
||||
return L2CAP_LE_ERR_PSM_NOT_SUPP;
|
||||
default:
|
||||
return L2CAP_LE_ERR_UNACCEPT_PARAMS;
|
||||
}
|
||||
}
|
||||
|
||||
static int l2cap_accept(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t scid, uint16_t mtu,
|
||||
uint16_t mps, uint16_t credits,
|
||||
uint16_t *result)
|
||||
{
|
||||
struct bt_l2cap_server *server;
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
int err;
|
||||
|
||||
ARG_UNUSED(credits);
|
||||
|
||||
LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_PARAMS;
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
/* Check if there is a server registered */
|
||||
server = l2cap_server_lookup_psm(psm);
|
||||
if (server == NULL) {
|
||||
LOG_ERR("L2capSrvNotReg[%04x]", psm);
|
||||
|
||||
*result = L2CAP_LE_ERR_PSM_NOT_SUPP;
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (!L2CAP_LE_CID_IS_DYN(scid)) {
|
||||
LOG_ERR("L2capNotDynScid[%04x]", scid);
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_SCID;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_tx_cid(conn, scid);
|
||||
if (chan) {
|
||||
LOG_WRN("L2capScidUsed[%04x]", scid);
|
||||
|
||||
*result = L2CAP_LE_ERR_SCID_IN_USE;
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
/* Every event is routed back by (conn, psm), so a second channel for the
|
||||
* same pair would deliver to whichever chan l2cap_lookup_psm() hits first. */
|
||||
if (l2cap_lookup_psm(conn, psm) != NULL) {
|
||||
LOG_WRN("L2capPsmChanExists[%u][%04x]", conn_handle, psm);
|
||||
|
||||
*result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
if (server->accept == NULL) {
|
||||
LOG_ERR("L2capSrvAcceptNull");
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_PARAMS;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
err = server->accept(conn, server, &chan);
|
||||
if (err) {
|
||||
LOG_ERR("L2capSrvAcceptFail[%d]", err);
|
||||
|
||||
*result = err_to_result(err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capSrvAcceptNullChan");
|
||||
*result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (l2cap_chan_add(conn, chan, psm) == false) {
|
||||
*result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*result = L2CAP_LE_SUCCESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
static void l2cap_handle_accept(const struct bt_le_l2cap_event *qev)
|
||||
{
|
||||
uint16_t result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
|
||||
/* chan_handle is our local CID; the peer's is unknown until the channel is
|
||||
* up, so it stands in for scid (dynamic-CID check and duplicate detection).
|
||||
* mtu/mps are logging-only in l2cap_accept(). */
|
||||
(void)l2cap_accept(qev->conn_handle, qev->psm, qev->accept.chan_handle,
|
||||
0, 0, 0, &result);
|
||||
|
||||
/* Forward result, not a bool: l2cap_accept() distinguishes authentication,
|
||||
* key size and SCID-in-use, and the peer is entitled to the exact reason. */
|
||||
bt_le_bluedroid_l2cap_accept_result(qev->conn_handle, qev->accept.l2cap_id,
|
||||
qev->accept.chan_handle, result);
|
||||
}
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
static void l2cap_connected(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t tx_cid, uint16_t tx_mtu,
|
||||
uint16_t rx_cid, uint16_t rx_mtu)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]",
|
||||
conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capPsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid;
|
||||
BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu;
|
||||
BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid;
|
||||
BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu;
|
||||
|
||||
if (chan->ops->connected) {
|
||||
chan->ops->connected(chan);
|
||||
}
|
||||
}
|
||||
|
||||
static void l2cap_disconnected(uint16_t conn_handle, uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capPsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unlink first so no later event rediscovers it by PSM. The callback must not
|
||||
* free chan: it reads chan->conn, and clearing conn after returns the pool ctx. */
|
||||
sys_slist_find_and_remove(&conn->channels, &chan->node);
|
||||
|
||||
if (chan->ops->disconnected) {
|
||||
chan->ops->disconnected(chan);
|
||||
}
|
||||
|
||||
chan->conn = NULL;
|
||||
}
|
||||
|
||||
static void l2cap_received(uint16_t conn_handle, uint16_t psm,
|
||||
uint8_t *data, uint16_t len)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct net_buf buf = {0};
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capPsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.data = data;
|
||||
buf.len = len;
|
||||
|
||||
if (chan->ops->recv) {
|
||||
chan->ops->recv(chan, &buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void l2cap_sent(uint16_t conn_handle, uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capSent[%u][%04x]", conn_handle, psm);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capSentNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capPsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chan->ops->sent) {
|
||||
chan->ops->sent(chan);
|
||||
}
|
||||
}
|
||||
|
||||
static int l2cap_post_event(struct bt_le_l2cap_event *qev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT, qev, sizeof(*qev));
|
||||
if (err) {
|
||||
LOG_ERR("L2capPostFail[%d][%u]", err, qev->type);
|
||||
if (qev->type == BT_LE_L2CAP_EVENT_RECEIVED) {
|
||||
free(qev->received.data);
|
||||
}
|
||||
free(qev);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Alloc + stamp the common header. NULL on OOM rather than abort: L2CAP is off
|
||||
* the ISO data path, so losing one event beats taking the whole stack down. */
|
||||
static struct bt_le_l2cap_event *l2cap_event_alloc(uint8_t type, uint16_t conn_handle,
|
||||
uint16_t psm)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
if (qev == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qev->type = type;
|
||||
qev->conn_handle = conn_handle;
|
||||
qev->psm = psm;
|
||||
|
||||
return qev;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_post_connected(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t tx_cid, uint16_t tx_mtu,
|
||||
uint16_t rx_cid, uint16_t rx_mtu)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_CONNECTED, conn_handle, psm);
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capConnectedNoMem[%u][%04x]", conn_handle, psm);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
qev->connected.tx_cid = tx_cid;
|
||||
qev->connected.tx_mtu = tx_mtu;
|
||||
qev->connected.rx_cid = rx_cid;
|
||||
qev->connected.rx_mtu = rx_mtu;
|
||||
|
||||
return l2cap_post_event(qev);
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_post_disconnected(uint16_t conn_handle, uint16_t psm)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_DISCONNECTED, conn_handle, psm);
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capDisconnectedNoMem[%u][%04x]", conn_handle, psm);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return l2cap_post_event(qev);
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_post_received(uint16_t conn_handle, uint16_t psm,
|
||||
const uint8_t *data, uint16_t len)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_RECEIVED, conn_handle, psm);
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capReceivedNoMem[%u][%04x][%u]", conn_handle, psm, len);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
qev->received.len = len;
|
||||
|
||||
if (len) {
|
||||
qev->received.data = bt_le_ext_calloc(1, len);
|
||||
if (qev->received.data == NULL) {
|
||||
LOG_ERR("L2capReceivedNoDataMem[%u][%04x][%u]", conn_handle, psm, len);
|
||||
free(qev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(qev->received.data, data, len);
|
||||
}
|
||||
|
||||
return l2cap_post_event(qev);
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_post_sent(uint16_t conn_handle, uint16_t psm)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_SENT, conn_handle, psm);
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capSentNoMem[%u][%04x]", conn_handle, psm);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return l2cap_post_event(qev);
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_post_accept(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t chan_handle, uint8_t l2cap_id)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev;
|
||||
|
||||
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_ACCEPT, conn_handle, psm);
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capAcceptNoMem[%u][%04x][%04x]", conn_handle, psm, chan_handle);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
qev->accept.chan_handle = chan_handle;
|
||||
qev->accept.l2cap_id = l2cap_id;
|
||||
|
||||
return l2cap_post_event(qev);
|
||||
}
|
||||
#else /* !CONFIG_BT_BLUEDROID_ENABLED */
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_accept_safe(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t scid, uint16_t mtu,
|
||||
uint16_t mps, uint16_t credits,
|
||||
uint16_t *result)
|
||||
{
|
||||
int err;
|
||||
bt_le_host_lock();
|
||||
err = l2cap_accept(conn_handle, psm, scid, mtu, mps, credits, result);
|
||||
bt_le_host_unlock();
|
||||
return err;
|
||||
}
|
||||
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
_IDF_ONLY
|
||||
void bt_le_l2cap_handle_event(void *data, size_t data_len)
|
||||
{
|
||||
struct bt_le_l2cap_event *qev = data;
|
||||
|
||||
if (qev == NULL) {
|
||||
LOG_ERR("L2capEvtNull");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_len != sizeof(*qev)) {
|
||||
LOG_ERR("L2capEvtBadLen[%u]", (unsigned)data_len);
|
||||
free(qev);
|
||||
return;
|
||||
}
|
||||
|
||||
bt_le_host_lock();
|
||||
|
||||
switch (qev->type) {
|
||||
case BT_LE_L2CAP_EVENT_CONNECTED:
|
||||
l2cap_connected(qev->conn_handle, qev->psm,
|
||||
qev->connected.tx_cid, qev->connected.tx_mtu,
|
||||
qev->connected.rx_cid, qev->connected.rx_mtu);
|
||||
break;
|
||||
case BT_LE_L2CAP_EVENT_DISCONNECTED:
|
||||
l2cap_disconnected(qev->conn_handle, qev->psm);
|
||||
break;
|
||||
case BT_LE_L2CAP_EVENT_RECEIVED:
|
||||
l2cap_received(qev->conn_handle, qev->psm,
|
||||
qev->received.data, qev->received.len);
|
||||
free(qev->received.data);
|
||||
break;
|
||||
case BT_LE_L2CAP_EVENT_SENT:
|
||||
l2cap_sent(qev->conn_handle, qev->psm);
|
||||
break;
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
case BT_LE_L2CAP_EVENT_ACCEPT:
|
||||
l2cap_handle_accept(qev);
|
||||
break;
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
default:
|
||||
LOG_ERR("L2capEvtUnknown[%u]", qev->type);
|
||||
break;
|
||||
}
|
||||
|
||||
bt_le_host_unlock();
|
||||
|
||||
free(qev);
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
|
||||
{
|
||||
int err;
|
||||
|
||||
LOG_DBG("L2capChanConnect[%04x]", psm);
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capChanNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("L2capInvPsm[%04x]", psm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Caller holds bt_le_host_lock. chan_add is done BEFORE the adapter call
|
||||
* (optimistic): the Bluedroid adapter posts fire-and-forget, so the
|
||||
* CONNECTED/DISCONNECTED event needs the chan already in conn->channels.
|
||||
* On failure the adapter posts DISCONNECTED, matching the Zephyr
|
||||
* connect-fail contract. */
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
/* Same (conn, psm) uniqueness as l2cap_accept(): event routing keys on it.
|
||||
* l2cap_chan_add only rejects re-adding this very chan object. */
|
||||
if (l2cap_lookup_psm(conn, psm) != NULL) {
|
||||
LOG_WRN("L2capPsmChanExists[%u][%04x]", conn->handle, psm);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
if (!l2cap_chan_add(conn, chan, psm)) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
err = bt_le_bluedroid_l2cap_chan_connect(conn->handle);
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
|
||||
#endif
|
||||
if (err) {
|
||||
/* Roll back the optimistic chan_add. */
|
||||
sys_slist_find_and_remove(&conn->channels, &chan->node);
|
||||
chan->conn = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
int err;
|
||||
|
||||
LOG_DBG("L2capChanDisconnect");
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capChanNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
err = bt_le_bluedroid_l2cap_chan_disconnect(chan);
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_chan_disconnect(chan);
|
||||
#endif
|
||||
if (err) {
|
||||
/* If the disconnect failed, remove the channel from the connection.
|
||||
* Otherwise the removal will be handled by the disconnect callback. */
|
||||
if (chan->conn != NULL) {
|
||||
sys_slist_find_and_remove(&chan->conn->channels, &chan->node);
|
||||
chan->conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (chan == NULL || buf == NULL) {
|
||||
LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_DBG("L2capChanSend[%u]", buf->len);
|
||||
|
||||
/* Caller holds bt_le_host_lock. The Bluedroid adapter posts fire-and-forget;
|
||||
* returns 0 (buf ownership transferred) or error (buf stays with caller).
|
||||
* TX completion via ops->sent. */
|
||||
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (buf->len > BT_L2CAP_LE_CHAN(chan)->tx.mtu) {
|
||||
LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len,
|
||||
BT_L2CAP_LE_CHAN(chan)->tx.mtu);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
err = bt_le_bluedroid_l2cap_chan_send(chan, buf);
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_chan_send(chan, buf);
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_server_register(struct bt_l2cap_server *server)
|
||||
{
|
||||
LOG_DBG("L2capSrvReg");
|
||||
|
||||
if (server == NULL) {
|
||||
LOG_ERR("L2capSrvNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (server->accept == NULL) {
|
||||
LOG_ERR("L2capSrvAcceptNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (server->sec_level > BT_SECURITY_L4) {
|
||||
LOG_ERR("L2capInvSecLevel[%u]", server->sec_level);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Init path (no concurrency). l2cap_servers is shared with the accept
|
||||
* lookup in the event handler (iso_task). */
|
||||
if (server->psm) {
|
||||
if (server->psm < L2CAP_LE_PSM_FIXED_START ||
|
||||
server->psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("L2capInvPsm[%04x]", server->psm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check if given PSM is already in use */
|
||||
if (l2cap_server_lookup_psm(server->psm)) {
|
||||
LOG_WRN("L2capPsmReg");
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
LOG_DBG("L2capSrvPsm[%04x]", server->psm);
|
||||
} else {
|
||||
uint16_t psm;
|
||||
|
||||
for (psm = L2CAP_LE_PSM_DYN_START;
|
||||
psm <= L2CAP_LE_PSM_DYN_END; psm++) {
|
||||
if (l2cap_server_lookup_psm(psm) == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("L2capNoFreeDynPsm");
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
|
||||
LOG_DBG("L2capPsmNew[%04x]", psm);
|
||||
|
||||
server->psm = psm;
|
||||
}
|
||||
|
||||
if (server->sec_level < BT_SECURITY_L1) {
|
||||
server->sec_level = BT_SECURITY_L1;
|
||||
}
|
||||
|
||||
sys_slist_append(&l2cap_servers, &server->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_le_l2cap_ots_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
LOG_DBG("L2capOtsInit");
|
||||
|
||||
/* No unregister API, so a re-init would otherwise find the stale entry and
|
||||
* fail bt_l2cap_server_register() with -EADDRINUSE. */
|
||||
sys_slist_init(&l2cap_servers);
|
||||
|
||||
#if CONFIG_BT_OTS
|
||||
err = bt_gatt_ots_conn_cb_register();
|
||||
if (err) {
|
||||
LOG_ERR("L2capOtsConnCbRegFail[%d]", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bt_gatt_ots_instances_prepare();
|
||||
if (err) {
|
||||
LOG_ERR("L2capPrepOtsInstsFail[%d]", err);
|
||||
goto unreg_conn_cb;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
|
||||
#if CONFIG_BT_OTS_CLIENT
|
||||
err = bt_gatt_ots_client_conn_cb_register();
|
||||
if (err) {
|
||||
LOG_ERR("L2capOtsCliConnCbRegFail[%d]", err);
|
||||
goto unreg_conn_cb;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS_CLIENT */
|
||||
|
||||
err = bt_gatt_ots_l2cap_init();
|
||||
if (err) {
|
||||
LOG_ERR("L2capOtsInitFail[%d]", err);
|
||||
goto unreg_client_conn_cb;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
err = bt_le_bluedroid_l2cap_init();
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_init();
|
||||
#endif
|
||||
if (err) {
|
||||
LOG_ERR("L2capOtsAdapterInitFail[%d]", err);
|
||||
/* bt_l2cap_server_register() has no unregister, so drop the whole list
|
||||
* rather than leave the OTS PSM claimed by a half-initialised service. */
|
||||
sys_slist_init(&l2cap_servers);
|
||||
goto unreg_client_conn_cb;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unreg_client_conn_cb:
|
||||
#if CONFIG_BT_OTS_CLIENT
|
||||
bt_gatt_ots_client_conn_cb_unregister();
|
||||
#endif /* CONFIG_BT_OTS_CLIENT */
|
||||
unreg_conn_cb:
|
||||
#if CONFIG_BT_OTS
|
||||
/* Leaving it registered would fail every later retry with -EEXIST. */
|
||||
bt_gatt_ots_conn_cb_unregister();
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
return err;
|
||||
}
|
||||
|
||||
void bt_le_l2cap_ots_deinit(void)
|
||||
{
|
||||
LOG_DBG("L2capOtsDeinit");
|
||||
|
||||
/* TODO(deinit): unassign OTS obj managers / clear instance->obj_manager. */
|
||||
|
||||
/* Symmetric with the registers in _init: conn_cbs is never reset elsewhere. */
|
||||
#if CONFIG_BT_OTS
|
||||
bt_gatt_ots_conn_cb_unregister();
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
#if CONFIG_BT_OTS_CLIENT
|
||||
bt_gatt_ots_client_conn_cb_unregister();
|
||||
#endif /* CONFIG_BT_OTS_CLIENT */
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
bt_le_bluedroid_l2cap_deinit();
|
||||
#else
|
||||
bt_le_nimble_l2cap_deinit();
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
|
||||
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef OTS_ADAPTER_L2CAP_H_
|
||||
#define OTS_ADAPTER_L2CAP_H_
|
||||
|
||||
#include <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);
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -310,6 +311,16 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
|
||||
struct bt_gatt_ots_object *new_obj;
|
||||
struct bt_ots_obj_created_desc created_desc;
|
||||
|
||||
/* Directory listing Object Type is 16- or 128-bit only (same as OACP Create). */
|
||||
switch (param->type.uuid.type) {
|
||||
case BT_UUID_TYPE_16:
|
||||
case BT_UUID_TYPE_128:
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("OtsObjAddInvType[%u]", param->type.uuid.type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
|
||||
!bt_ots_dir_list_is_idle(ots->dir_list)) {
|
||||
LOG_DBG("OtsDirListBusy");
|
||||
@@ -330,6 +341,9 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
|
||||
if (err) {
|
||||
(void)bt_gatt_ots_obj_manager_obj_delete(new_obj);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
|
||||
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -359,11 +373,19 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
|
||||
LOG_ERR("OtsObjCreatedCbNotSet");
|
||||
|
||||
(void)bt_gatt_ots_obj_manager_obj_delete(new_obj);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
|
||||
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
new_obj->metadata.type = param->type;
|
||||
new_obj->metadata.name = created_desc.name;
|
||||
/* Own the name: a client name write copies into metadata.name, and the
|
||||
* application's buffer may be read-only or shorter than the max name. */
|
||||
strncpy(new_obj->metadata.name_c, created_desc.name, CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
|
||||
new_obj->metadata.name_c[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN] = '\0';
|
||||
new_obj->metadata.name = new_obj->metadata.name_c;
|
||||
new_obj->metadata.size = created_desc.size;
|
||||
new_obj->metadata.props = created_desc.props;
|
||||
|
||||
@@ -448,6 +470,11 @@ int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
|
||||
/* Object removed from the list: drop stale anchor and refresh size. */
|
||||
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
|
||||
}
|
||||
|
||||
if (ots->cur_obj == obj) {
|
||||
ots->cur_obj = NULL;
|
||||
}
|
||||
@@ -464,18 +491,48 @@ void *bt_ots_svc_decl_get(struct bt_ots *ots)
|
||||
|
||||
static void oacp_indicate_work_handler(struct k_work *work)
|
||||
{
|
||||
struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work);
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct bt_gatt_ots_indicate *ind =
|
||||
CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work);
|
||||
struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, oacp_ind);
|
||||
int err;
|
||||
|
||||
bt_gatt_indicate(NULL, &ots->oacp_ind.params);
|
||||
if (!ind->conn) {
|
||||
LOG_WRN("OtsOacpIndNoConn");
|
||||
ots->oacp_ind.ind_in_flight = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INF("OtsOacpInd[%04x]", ind->conn->handle);
|
||||
|
||||
err = bt_gatt_indicate(ind->conn, &ots->oacp_ind.params);
|
||||
if (err) {
|
||||
LOG_ERR("OtsOacpIndFail[%04x][%d]", ind->conn->handle, err);
|
||||
ots->oacp_ind.ind_in_flight = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void olcp_indicate_work_handler(struct k_work *work)
|
||||
{
|
||||
struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work);
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct bt_gatt_ots_indicate *ind =
|
||||
CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work);
|
||||
struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, olcp_ind);
|
||||
int err;
|
||||
|
||||
bt_gatt_indicate(NULL, &ots->olcp_ind.params);
|
||||
if (!ind->conn) {
|
||||
LOG_WRN("OtsOlcpIndNoConn");
|
||||
ots->olcp_ind.ind_in_flight = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INF("OtsOlcpInd[%04x]", ind->conn->handle);
|
||||
|
||||
err = bt_gatt_indicate(ind->conn, &ots->olcp_ind.params);
|
||||
if (err) {
|
||||
LOG_ERR("OtsOlcpIndFail[%04x][%d]", ind->conn->handle, err);
|
||||
ots->olcp_ind.ind_in_flight = false;
|
||||
}
|
||||
}
|
||||
|
||||
int bt_ots_init(struct bt_ots *ots,
|
||||
@@ -531,7 +588,11 @@ int bt_ots_init(struct bt_ots *ots,
|
||||
|
||||
err = bt_gatt_service_register(ots->service);
|
||||
if (err) {
|
||||
bt_gatt_ots_l2cap_unregister(&ots->l2cap);
|
||||
int unreg_err = bt_gatt_ots_l2cap_unregister(&ots->l2cap);
|
||||
|
||||
if (unreg_err) {
|
||||
LOG_ERR("OtsL2capUnregFail[%d]", unreg_err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
@@ -540,8 +601,8 @@ int bt_ots_init(struct bt_ots *ots,
|
||||
bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager);
|
||||
}
|
||||
|
||||
k_work_init(&ots->oacp_ind.work, oacp_indicate_work_handler);
|
||||
k_work_init(&ots->olcp_ind.work, olcp_indicate_work_handler);
|
||||
k_work_init_delayable(&ots->oacp_ind.work, oacp_indicate_work_handler);
|
||||
k_work_init_delayable(&ots->olcp_ind.work, olcp_indicate_work_handler);
|
||||
|
||||
LOG_DBG("OtsInit");
|
||||
|
||||
@@ -616,6 +677,7 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co
|
||||
char id_str[BT_OTS_OBJ_ID_STR_LEN];
|
||||
struct bt_gatt_ots_object *obj;
|
||||
struct bt_gatt_ots_object *next_obj;
|
||||
bool deleted = false;
|
||||
int err;
|
||||
|
||||
err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &next_obj);
|
||||
@@ -638,9 +700,34 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co
|
||||
if (bt_gatt_ots_obj_manager_obj_delete(obj)) {
|
||||
LOG_ERR("OtsObjMgrDelFail[%s]",
|
||||
id_str);
|
||||
} else {
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Refresh once after the loop: while empty-name objects are being deleted
|
||||
* the manager still holds not-yet-removed ones, and dir_list_update_size
|
||||
* would assert (name_len > 0) on them.
|
||||
*/
|
||||
if (deleted && IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
|
||||
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void ots_ind_on_disconnect(struct bt_gatt_ots_indicate *ind, struct bt_conn *conn)
|
||||
{
|
||||
if (ind->conn != conn) {
|
||||
LOG_INF("OtsIndSkipDisc[%04x][%04x]",
|
||||
ind->conn ? ind->conn->handle : 0xFFFF, conn->handle);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INF("OtsIndClrOnDisc[%04x]", conn->handle);
|
||||
|
||||
(void)k_work_cancel_delayable(&ind->work);
|
||||
ind->ind_in_flight = false;
|
||||
ind->conn = NULL;
|
||||
}
|
||||
|
||||
static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
@@ -654,6 +741,9 @@ static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
|
||||
LOG_DBG("OtsInstDisconnect[%u]", index);
|
||||
|
||||
ots_ind_on_disconnect(&instance->oacp_ind, conn);
|
||||
ots_ind_on_disconnect(&instance->olcp_ind, conn);
|
||||
|
||||
if (instance->cur_obj != NULL) {
|
||||
__ASSERT(instance->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE,
|
||||
"The current object is expected to be in idle state as part "
|
||||
@@ -678,7 +768,7 @@ struct bt_ots *bt_ots_free_instance_get(void)
|
||||
return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++];
|
||||
}
|
||||
|
||||
static int bt_gatt_ots_instances_prepare(void)
|
||||
int bt_gatt_ots_instances_prepare(void)
|
||||
{
|
||||
uint32_t index;
|
||||
struct bt_ots *instance;
|
||||
@@ -709,10 +799,12 @@ static int bt_gatt_ots_instances_prepare(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION,
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
int bt_gatt_ots_conn_cb_register(void)
|
||||
{
|
||||
return bt_conn_cb_register_safe((void *)&bt_conn_cb_conn_callbacks);
|
||||
}
|
||||
|
||||
void bt_gatt_ots_conn_cb_unregister(void)
|
||||
{
|
||||
(void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_conn_callbacks);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @brief Bluetooth Object Transfer Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -131,6 +132,7 @@ static void read_next_metadata(struct bt_conn *conn,
|
||||
static int read_attr(struct bt_conn *conn,
|
||||
struct bt_otc_internal_instance_t *inst,
|
||||
uint16_t handle, bt_gatt_read_func_t cb);
|
||||
static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst);
|
||||
|
||||
/* L2CAP callbacks */
|
||||
static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
@@ -177,6 +179,7 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
}
|
||||
|
||||
const uint32_t offset = cur_inst->rcvd_size;
|
||||
uint32_t len = buf->len;
|
||||
bool is_complete = false;
|
||||
const struct bt_ots_obj_metadata *cur_object =
|
||||
&cur_inst->otc_inst->cur_object;
|
||||
@@ -184,19 +187,24 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
|
||||
LOG_DBG("OtsCliL2capRecv[%u][%u]", buf->len, offset);
|
||||
|
||||
cur_inst->rcvd_size += buf->len;
|
||||
|
||||
if (cur_inst->rcvd_size >= cur_object->size.cur) {
|
||||
is_complete = true;
|
||||
if (offset >= cur_object->size.cur) {
|
||||
LOG_WRN("OtsCliRecvPastEnd[%u][%u]", offset, cur_object->size.cur);
|
||||
(void)bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
|
||||
cur_inst = NULL;
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if (cur_inst->rcvd_size > cur_object->size.cur) {
|
||||
LOG_WRN("OtsCliRecvExceedMax[%u][%u]", cur_inst->rcvd_size, cur_object->size.cur);
|
||||
if (offset + len > cur_object->size.cur) {
|
||||
LOG_WRN("OtsCliRecvExceedMax[%u][%u]", offset + len, cur_object->size.cur);
|
||||
len = cur_object->size.cur - offset;
|
||||
}
|
||||
|
||||
cur_inst->rcvd_size = offset + len;
|
||||
is_complete = (cur_inst->rcvd_size >= cur_object->size.cur);
|
||||
|
||||
if (cur_inst->otc_inst->cb != NULL && cur_inst->otc_inst->cb->obj_data_read != NULL) {
|
||||
cb_ret = cur_inst->otc_inst->cb->obj_data_read(cur_inst->otc_inst, conn, offset,
|
||||
buf->len, buf->data,
|
||||
len, buf->data,
|
||||
is_complete);
|
||||
} else {
|
||||
LOG_ERR("OtsCliObjDataRdCbNull");
|
||||
@@ -234,10 +242,15 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
struct bt_otc_internal_instance_t *inst =
|
||||
CONTAINER_OF(l2cap_ctx, struct bt_otc_internal_instance_t, l2cap_ctx);
|
||||
|
||||
ARG_UNUSED(conn);
|
||||
|
||||
LOG_DBG("OtsCliL2capClosed");
|
||||
if (cur_inst) {
|
||||
cur_inst = NULL;
|
||||
}
|
||||
|
||||
/* Only release if this channel belonged to the active transfer. */
|
||||
oacp_clr_cur_inst(inst);
|
||||
}
|
||||
/* End L2CAP callbacks */
|
||||
|
||||
@@ -318,7 +331,9 @@ static void olcp_ind_handler(struct bt_conn *conn,
|
||||
enum bt_gatt_ots_olcp_proc_type op_code;
|
||||
struct net_buf_simple net_buf;
|
||||
|
||||
if (length < sizeof(op_code)) {
|
||||
/* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject
|
||||
* every valid 3-byte response. */
|
||||
if (length < sizeof(uint8_t)) {
|
||||
LOG_WRN("OtsCliInvIndLen[%u]", length);
|
||||
return;
|
||||
}
|
||||
@@ -387,18 +402,29 @@ static void olcp_ind_handler(struct bt_conn *conn,
|
||||
}
|
||||
}
|
||||
|
||||
static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst)
|
||||
{
|
||||
if (cur_inst == inst) {
|
||||
cur_inst = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void oacp_ind_handler(struct bt_conn *conn,
|
||||
struct bt_ots_client *otc_inst,
|
||||
struct bt_otc_internal_instance_t *inst,
|
||||
const void *data, uint16_t length)
|
||||
{
|
||||
struct bt_ots_client *otc_inst = inst->otc_inst;
|
||||
enum bt_gatt_ots_oacp_proc_type op_code;
|
||||
enum bt_gatt_ots_oacp_proc_type req_opcode;
|
||||
enum bt_gatt_ots_oacp_res_code result_code;
|
||||
uint32_t checksum;
|
||||
struct net_buf_simple net_buf;
|
||||
|
||||
if (length < sizeof(op_code)) {
|
||||
/* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject
|
||||
* every valid 3-byte response. */
|
||||
if (length < sizeof(uint8_t)) {
|
||||
LOG_WRN("OtsCliInvIndLen[%u]", length);
|
||||
oacp_clr_cur_inst(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,11 +435,12 @@ static void oacp_ind_handler(struct bt_conn *conn,
|
||||
LOG_DBG("OtsCliOacpInd");
|
||||
|
||||
if (op_code == BT_GATT_OTS_OACP_PROC_RESP) {
|
||||
if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) {
|
||||
if (net_buf.len >= (sizeof(uint8_t) + sizeof(uint8_t))) {
|
||||
req_opcode = net_buf_simple_pull_u8(&net_buf);
|
||||
result_code = net_buf_simple_pull_u8(&net_buf);
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvIndDataLen[%u]", net_buf.len);
|
||||
oacp_clr_cur_inst(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -427,13 +454,22 @@ static void oacp_ind_handler(struct bt_conn *conn,
|
||||
}
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvChecksumLen[%u]", net_buf.len);
|
||||
return;
|
||||
}
|
||||
/* Checksum never uses L2CAP; always release cur_inst. */
|
||||
oacp_clr_cur_inst(inst);
|
||||
} else if ((req_opcode == BT_GATT_OTS_OACP_PROC_READ ||
|
||||
req_opcode == BT_GATT_OTS_OACP_PROC_WRITE) &&
|
||||
result_code != BT_GATT_OTS_OACP_RES_SUCCESS) {
|
||||
/* Read/Write SUCCESS still waits on L2CAP (rx/tx_done/closed). */
|
||||
LOG_DBG("OtsCliOacpFailClr[%02x][%02x]", req_opcode, result_code);
|
||||
oacp_clr_cur_inst(inst);
|
||||
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
|
||||
}
|
||||
|
||||
print_oacp_response(req_opcode, result_code);
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvIndOpcode[%u]", op_code);
|
||||
oacp_clr_cur_inst(inst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +501,7 @@ uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn,
|
||||
if (handle == inst->otc_inst->olcp_handle) {
|
||||
olcp_ind_handler(conn, inst->otc_inst, data, length);
|
||||
} else if (handle == inst->otc_inst->oacp_handle) {
|
||||
oacp_ind_handler(conn, inst->otc_inst, data, length);
|
||||
oacp_ind_handler(conn, inst, data, length);
|
||||
}
|
||||
}
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
@@ -511,8 +547,50 @@ static uint8_t read_feature_cb(struct bt_conn *conn, uint8_t err,
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
/* Disconnect fires no subscription callback, so the OLCP/OACP indication that
|
||||
* releases busy never arrives — an in-flight procedure would strand -EBUSY. */
|
||||
static void ots_client_conn_disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
ARG_UNUSED(conn);
|
||||
ARG_UNUSED(reason);
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
|
||||
struct bt_otc_internal_instance_t *inst = &otc_insts[i];
|
||||
|
||||
if (inst->otc_inst == NULL || !inst->busy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_WRN("OtsCliDisconnBusy[%d]", i);
|
||||
|
||||
inst->busy = false;
|
||||
oacp_clr_cur_inst(inst);
|
||||
}
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(client_conn_callbacks) = {
|
||||
.disconnected = ots_client_conn_disconnected,
|
||||
};
|
||||
|
||||
int bt_gatt_ots_client_conn_cb_register(void)
|
||||
{
|
||||
return bt_conn_cb_register_safe((void *)&bt_conn_cb_client_conn_callbacks);
|
||||
}
|
||||
|
||||
void bt_gatt_ots_client_conn_cb_unregister(void)
|
||||
{
|
||||
(void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_client_conn_callbacks);
|
||||
}
|
||||
|
||||
int bt_ots_client_register(struct bt_ots_client *otc_inst)
|
||||
{
|
||||
/* A NULL inst would leave the slot marked free after registering its
|
||||
* L2CAP node, so the next call re-appends the same node and self-links it. */
|
||||
if (otc_inst == NULL) {
|
||||
LOG_ERR("OtsCliInvInst");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
|
||||
int err;
|
||||
|
||||
@@ -534,18 +612,32 @@ int bt_ots_client_register(struct bt_ots_client *otc_inst)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_unregister(uint8_t index)
|
||||
{
|
||||
if (index < ARRAY_SIZE(otc_insts)) {
|
||||
bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx);
|
||||
memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
|
||||
} else {
|
||||
int err;
|
||||
|
||||
if (index >= ARRAY_SIZE(otc_insts)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx);
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliL2capUnregFail[%d]", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (cur_inst == &otc_insts[index]) {
|
||||
LOG_DBG("OtsCliUnregClrCurInst[%u]", index);
|
||||
cur_inst = NULL;
|
||||
}
|
||||
|
||||
memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_read_feature(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
@@ -602,7 +694,15 @@ static void write_olcp_cb(struct bt_conn *conn, uint8_t err,
|
||||
return;
|
||||
}
|
||||
|
||||
inst->busy = false;
|
||||
/* Keep busy until OLCP indication; only release on write failure.
|
||||
* Notify the app — without an indication, obj_selected would never fire.
|
||||
*/
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliOlcpWrFail[%02x]", err);
|
||||
inst->busy = false;
|
||||
on_object_selected(conn, BT_GATT_OTS_OLCP_RES_OPERATION_FAILED,
|
||||
inst->otc_inst);
|
||||
}
|
||||
}
|
||||
|
||||
static int write_olcp(struct bt_otc_internal_instance_t *inst,
|
||||
@@ -633,6 +733,7 @@ static int write_olcp(struct bt_otc_internal_instance_t *inst,
|
||||
return err;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn,
|
||||
uint64_t obj_id)
|
||||
@@ -679,6 +780,7 @@ int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
@@ -713,6 +815,7 @@ int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
@@ -748,6 +851,7 @@ int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
@@ -782,6 +886,7 @@ int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_select_prev(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
@@ -1040,6 +1145,7 @@ static uint8_t read_obj_created_cb(struct bt_conn *conn, uint8_t err,
|
||||
date_time_decode(
|
||||
&net_buf,
|
||||
&inst->otc_inst->cur_object.first_created);
|
||||
BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read);
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE);
|
||||
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
@@ -1079,6 +1185,7 @@ static uint8_t read_obj_modified_cb(struct bt_conn *conn, uint8_t err,
|
||||
if (length == BT_OTS_DATE_TIME_FIELD_SIZE) {
|
||||
date_time_decode(&net_buf,
|
||||
&inst->otc_inst->cur_object.modified);
|
||||
BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read);
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE);
|
||||
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
@@ -1102,8 +1209,9 @@ static int read_attr(struct bt_conn *conn,
|
||||
uint16_t handle, bt_gatt_read_func_t cb)
|
||||
{
|
||||
if (!handle) {
|
||||
/* Characteristic not discovered — skip without poisoning metadata_err. */
|
||||
LOG_DBG("OtsCliHdlNotSet");
|
||||
return -EINVAL;
|
||||
return -ENOENT;
|
||||
} else if (cb == NULL) {
|
||||
LOG_ERR("OtsCliCbNull");
|
||||
return -EINVAL;
|
||||
@@ -1138,22 +1246,24 @@ static uint8_t read_obj_properties_cb(struct bt_conn *conn, uint8_t err,
|
||||
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliMetaRdErr[%02x]", err);
|
||||
} else if (data && length == OTS_PROPERTIES_LEN) {
|
||||
struct bt_ots_obj_metadata *cur_object =
|
||||
&inst->otc_inst->cur_object;
|
||||
} else if (data) {
|
||||
if (length != OTS_PROPERTIES_LEN) {
|
||||
LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN);
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
} else {
|
||||
struct bt_ots_obj_metadata *cur_object =
|
||||
&inst->otc_inst->cur_object;
|
||||
|
||||
cur_object->props = net_buf_simple_pull_le32(&net_buf);
|
||||
cur_object->props = net_buf_simple_pull_le32(&net_buf);
|
||||
|
||||
LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props);
|
||||
LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props);
|
||||
|
||||
if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
|
||||
LOG_WRN("OtsCliObjRdNotSupp");
|
||||
if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
|
||||
LOG_WRN("OtsCliObjRdNotSupp");
|
||||
}
|
||||
|
||||
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
|
||||
}
|
||||
|
||||
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
|
||||
} else {
|
||||
LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN);
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
}
|
||||
|
||||
if (cb_err) {
|
||||
@@ -1181,7 +1291,14 @@ static void write_oacp_cp_cb(struct bt_conn *conn, uint8_t err,
|
||||
return;
|
||||
}
|
||||
|
||||
inst->busy = false;
|
||||
/* Keep busy until OACP indication; only release on write failure. */
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliOacpWrFail[%02x]", err);
|
||||
inst->busy = false;
|
||||
oacp_clr_cur_inst(inst);
|
||||
/* oacp_read may already have connected L2CAP before the GATT write. */
|
||||
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
|
||||
@@ -1201,6 +1318,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
|
||||
LOG_WRN("OtsCliOacpWrReqFail[%02x]", err);
|
||||
inst->busy = false;
|
||||
cur_inst = NULL;
|
||||
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1210,6 +1328,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliL2capSendErr[%d]", err);
|
||||
cur_inst = NULL;
|
||||
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
|
||||
}
|
||||
|
||||
inst->busy = false;
|
||||
@@ -1236,6 +1355,7 @@ static int oacp_read(struct bt_conn *conn,
|
||||
* transfer?
|
||||
*/
|
||||
|
||||
l2cap = &inst->l2cap_ctx;
|
||||
err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliL2capConnectFail[%d]", err);
|
||||
@@ -1269,6 +1389,8 @@ static int oacp_read(struct bt_conn *conn,
|
||||
if (!err) {
|
||||
inst->busy = true;
|
||||
cur_inst = inst;
|
||||
} else {
|
||||
(void)bt_gatt_ots_l2cap_disconnect(l2cap);
|
||||
}
|
||||
|
||||
inst->rcvd_size = 0;
|
||||
@@ -1292,6 +1414,7 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
l2cap = &inst->l2cap_ctx;
|
||||
err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
|
||||
if (err) {
|
||||
LOG_WRN("OtsCliL2capConnectFail[%d]", err);
|
||||
@@ -1328,6 +1451,8 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i
|
||||
if (!err) {
|
||||
inst->busy = true;
|
||||
cur_inst = inst;
|
||||
} else {
|
||||
(void)bt_gatt_ots_l2cap_disconnect(l2cap);
|
||||
}
|
||||
|
||||
inst->rcvd_size = 0;
|
||||
@@ -1405,6 +1530,7 @@ int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst,
|
||||
return oacp_read(conn, inst);
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst,
|
||||
struct bt_conn *conn, const void *buf, size_t len,
|
||||
off_t offset, enum bt_ots_oacp_write_op_mode mode)
|
||||
@@ -1472,6 +1598,7 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst,
|
||||
return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode);
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn,
|
||||
off_t offset, size_t len)
|
||||
{
|
||||
@@ -1569,6 +1696,10 @@ static void read_next_metadata(struct bt_conn *conn,
|
||||
|
||||
if (err) {
|
||||
LOG_INF("OtsCliMetaRdFailTryNext[%d]", err);
|
||||
/* -ENOENT: handle not discovered, skip. Other errors: keep first. */
|
||||
if (err != -ENOENT && !inst->metadata_err) {
|
||||
inst->metadata_err = err;
|
||||
}
|
||||
read_next_metadata(conn, inst);
|
||||
}
|
||||
}
|
||||
@@ -1756,6 +1887,7 @@ static int decode_record(struct net_buf_simple *buf,
|
||||
return rec->len;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
|
||||
bt_ots_client_dirlisting_cb cb)
|
||||
{
|
||||
@@ -1772,6 +1904,9 @@ int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
|
||||
while (net_buf.len) {
|
||||
int ret;
|
||||
|
||||
/* Optional fields written only when flagged; clear between records. */
|
||||
memset(&record, 0, sizeof(record));
|
||||
|
||||
count++;
|
||||
|
||||
if (net_buf.len < sizeof(uint16_t)) {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -263,6 +264,12 @@ static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_man
|
||||
dir_list->dir_list_obj->metadata.size.cur = len;
|
||||
}
|
||||
|
||||
void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager)
|
||||
{
|
||||
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
|
||||
dir_list_update_size(dir_list, obj_manager);
|
||||
}
|
||||
|
||||
void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
|
||||
struct bt_gatt_ots_object *cur_obj)
|
||||
{
|
||||
@@ -273,8 +280,7 @@ void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manage
|
||||
return;
|
||||
}
|
||||
|
||||
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
|
||||
dir_list_update_size(dir_list, obj_manager);
|
||||
bt_ots_dir_list_content_changed(dir_list, obj_manager);
|
||||
}
|
||||
|
||||
void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 - 2022 Nordic Semiconductor ASA
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -158,6 +159,7 @@ enum {
|
||||
|
||||
void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
|
||||
struct bt_gatt_ots_object *cur_obj);
|
||||
void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager);
|
||||
void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager);
|
||||
ssize_t bt_ots_dir_list_content_get(struct bt_ots_dir_list *dir_list, void *obj_manager,
|
||||
void **data, size_t len, off_t offset);
|
||||
|
||||
@@ -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,8 +47,9 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU);
|
||||
len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent);
|
||||
|
||||
/* Prepare buffer for sending. */
|
||||
buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER);
|
||||
/* Single-buffer pool, and iso_task is both the only allocator and the only
|
||||
* path that releases it, so waiting here would self-deadlock. */
|
||||
buf = net_buf_alloc(&ot_chan_tx_pool, K_NO_WAIT);
|
||||
if (buf == NULL) {
|
||||
LOG_ERR("OtsL2capTxBufAllocFail");
|
||||
return -ENOMEM;
|
||||
@@ -77,7 +79,7 @@ static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("OtsL2capAllocBuf");
|
||||
|
||||
return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER);
|
||||
return net_buf_alloc(&ot_chan_rx_pool, K_NO_WAIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -93,12 +95,11 @@ static void l2cap_sent(struct bt_l2cap_chan *chan)
|
||||
/* Ongoing TX - sending next chunk. */
|
||||
if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) {
|
||||
if (ots_l2cap_send(l2cap_ctx)) {
|
||||
/* Send failed - clean up TX state to unblock channel. */
|
||||
/* Do not call tx_done: it means success to upper layers
|
||||
* (oacp_read would skip unread bytes; write_obj_tx_done
|
||||
* would report a full write). Abort via disconnect → closed. */
|
||||
LOG_WRN("OtsL2capTxAbort");
|
||||
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
|
||||
if (l2cap_ctx->tx_done) {
|
||||
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
|
||||
}
|
||||
(void)bt_l2cap_chan_disconnect(chan);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -155,6 +156,12 @@ static void l2cap_disconnected(struct bt_l2cap_chan *chan)
|
||||
if (l2cap_ctx->closed) {
|
||||
l2cap_ctx->closed(l2cap_ctx, chan->conn);
|
||||
}
|
||||
|
||||
/* Contexts are reused via find_free_l2cap_ctx; drop procedure hooks
|
||||
* so a later accept/connect cannot invoke stale callbacks. */
|
||||
l2cap_ctx->closed = NULL;
|
||||
l2cap_ctx->rx_done = NULL;
|
||||
l2cap_ctx->tx_done = NULL;
|
||||
}
|
||||
|
||||
static const struct bt_l2cap_chan_ops l2cap_ops = {
|
||||
@@ -216,7 +223,7 @@ static struct bt_l2cap_server l2cap_server = {
|
||||
.accept = l2cap_accept,
|
||||
};
|
||||
|
||||
static int bt_gatt_ots_l2cap_init(void)
|
||||
int bt_gatt_ots_l2cap_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -268,6 +275,10 @@ int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
|
||||
int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
if (sys_slist_find(&channels, &l2cap_ctx->node, NULL)) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
sys_slist_append(&channels, &l2cap_ctx->node);
|
||||
|
||||
return 0;
|
||||
@@ -275,6 +286,11 @@ int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
|
||||
int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
if (l2cap_ctx->ot_chan.chan.conn) {
|
||||
LOG_WRN("OtsL2capUnregBusy");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
sys_slist_find_and_remove(&channels, &l2cap_ctx->node);
|
||||
|
||||
return 0;
|
||||
@@ -297,11 +313,21 @@ int bt_gatt_ots_l2cap_connect(struct bt_conn *conn,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Callers owning a context pass it in, so the context they later use in
|
||||
* their callbacks is the one actually connected. The global free-list
|
||||
* lookup would otherwise hand out another instance's (or the server's)
|
||||
* context whenever more than one is registered. */
|
||||
ctx = *l2cap_ctx;
|
||||
*l2cap_ctx = NULL;
|
||||
|
||||
ctx = find_free_l2cap_ctx();
|
||||
if (!ctx) {
|
||||
return -ENOMEM;
|
||||
ctx = find_free_l2cap_ctx();
|
||||
if (!ctx) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
} else if (ctx->ot_chan.chan.conn) {
|
||||
LOG_WRN("OtsL2capCtxAlreadyConnected");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
l2cap_chan_init(&ctx->ot_chan);
|
||||
@@ -323,6 +349,3 @@ int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan);
|
||||
}
|
||||
|
||||
SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION,
|
||||
CONFIG_APPLICATION_INIT_PRIORITY);
|
||||
|
||||
@@ -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)
|
||||
@@ -68,6 +72,12 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate(
|
||||
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
|
||||
}
|
||||
|
||||
if (ots->cur_obj &&
|
||||
ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
|
||||
LOG_DBG("OtsOacpCreateObjLocked");
|
||||
return BT_GATT_OTS_OACP_RES_OBJ_LOCKED;
|
||||
}
|
||||
|
||||
err = bt_ots_obj_add_internal(ots, conn, ¶m, &obj);
|
||||
if (err) {
|
||||
goto exit;
|
||||
@@ -216,6 +226,11 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
|
||||
|
||||
LOG_DBG("OtsOacpValRd[%08x][%08x]", params->offset, params->len);
|
||||
|
||||
if (!BT_OTS_OACP_GET_FEAT_READ(ots->features.oacp)) {
|
||||
LOG_DBG("OtsOacpRdNotSupp");
|
||||
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
|
||||
}
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
return BT_GATT_OTS_OACP_RES_INV_OBJ;
|
||||
}
|
||||
@@ -297,8 +312,9 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate(
|
||||
return BT_GATT_OTS_OACP_RES_INV_PARAM;
|
||||
}
|
||||
|
||||
/* append is not supported */
|
||||
if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.cur) {
|
||||
/* Growing the object is allowed (see the size.cur update in
|
||||
* oacp_write_proc_cb), but never past what the application allocated. */
|
||||
if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) {
|
||||
return BT_GATT_OTS_OACP_RES_INV_PARAM;
|
||||
}
|
||||
|
||||
@@ -511,11 +527,31 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
return;
|
||||
}
|
||||
|
||||
/* Early EOF while bytes remain: do not L2CAP-send len 0 (tx_done would
|
||||
* re-enter with sent_len unchanged → busy loop).
|
||||
*/
|
||||
if (len == 0) {
|
||||
LOG_WRN("OtsOacpRdEofEarly[%u][%u]", read_op->sent_len,
|
||||
read_op->oacp_params.len);
|
||||
|
||||
bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
|
||||
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
|
||||
ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
|
||||
return;
|
||||
}
|
||||
|
||||
ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
ots->l2cap.tx_done = oacp_read_proc_cb;
|
||||
ots->l2cap.closed = oacp_l2cap_closed;
|
||||
err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len);
|
||||
if (err) {
|
||||
LOG_WRN("OtsOacpL2capErr[%d]", err);
|
||||
bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
|
||||
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
|
||||
} else {
|
||||
read_op->sent_len += len;
|
||||
@@ -557,9 +593,17 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Reading state.write_op while another procedure owns the union would
|
||||
* confuse recv_len with the read op's fields and underflow len below. */
|
||||
if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_WRITE_OP_STATE) {
|
||||
LOG_ERR("OtsOacpWrInvState[%d]", ots->cur_obj->state.type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ots->cb->obj_write) {
|
||||
LOG_ERR("OtsOacpWrNoCb");
|
||||
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
|
||||
l2cap_ctx->rx_done = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@@ -608,6 +652,13 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
ots->cur_obj->metadata.size.cur = offset + len;
|
||||
}
|
||||
|
||||
/* Back to idle means this write is over (completed or failed above). The
|
||||
* CoC stays open, so drop the sink or a later procedure's inbound data
|
||||
* would still land here. */
|
||||
if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE) {
|
||||
l2cap_ctx->rx_done = NULL;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
@@ -620,11 +671,28 @@ static void oacp_ind_cb(struct bt_conn *conn,
|
||||
|
||||
LOG_DBG("OtsOacpRecvIndAck[%04x]", err);
|
||||
|
||||
ots->oacp_ind.ind_in_flight = false;
|
||||
ots->oacp_ind.conn = NULL;
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("OtsOacpNoObjForAck");
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
/* Client did not ACK the OACP response — do not start L2CAP I/O.
|
||||
* Leave READ/WRITE would permanently OBJ_LOCKED. */
|
||||
LOG_WRN("OtsOacpIndFail[%02x][%d]", err, ots->cur_obj->state.type);
|
||||
if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_READ_OP_STATE ||
|
||||
ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_WRITE_OP_STATE) {
|
||||
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
|
||||
ots->l2cap.rx_done = NULL;
|
||||
ots->l2cap.tx_done = NULL;
|
||||
(void)bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ots->cur_obj->state.type) {
|
||||
case BT_GATT_OTS_OBJECT_READ_OP_STATE:
|
||||
oacp_read_proc_execute(ots, conn);
|
||||
@@ -641,7 +709,8 @@ static void oacp_ind_cb(struct bt_conn *conn,
|
||||
}
|
||||
}
|
||||
|
||||
static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
|
||||
static void oacp_ind_send(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *oacp_attr,
|
||||
struct bt_gatt_ots_oacp_proc oacp_proc,
|
||||
enum bt_gatt_ots_oacp_res_code oacp_status,
|
||||
struct net_buf_simple *resp_param)
|
||||
@@ -649,6 +718,7 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
|
||||
struct bt_ots *ots = (struct bt_ots *) oacp_attr->user_data;
|
||||
uint8_t *oacp_res = ots->oacp_ind.res;
|
||||
uint16_t oacp_res_len = 0;
|
||||
int err;
|
||||
|
||||
/* Encode OACP Response */
|
||||
oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP;
|
||||
@@ -669,9 +739,16 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
|
||||
ots->oacp_ind.params.data = oacp_res;
|
||||
ots->oacp_ind.params.len = oacp_res_len;
|
||||
|
||||
LOG_DBG("OtsOacpSendInd");
|
||||
LOG_DBG("OtsOacpSendInd[%u]", conn->handle);
|
||||
|
||||
k_work_submit(&ots->oacp_ind.work);
|
||||
ots->oacp_ind.conn = conn;
|
||||
ots->oacp_ind.ind_in_flight = true;
|
||||
err = k_work_schedule(&ots->oacp_ind.work, K_NO_WAIT_ASYNC);
|
||||
if (err < 0) {
|
||||
LOG_ERR("OtsOacpSchIndFail[%u][%d]", conn->handle, err);
|
||||
ots->oacp_ind.ind_in_flight = false;
|
||||
ots->oacp_ind.conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
|
||||
@@ -697,7 +774,8 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
if (k_work_is_pending(&ots->oacp_ind.work)) {
|
||||
if (ots->oacp_ind.ind_in_flight ||
|
||||
k_work_is_pending(&ots->oacp_ind.work.work)) {
|
||||
LOG_WRN("OtsOacpWrBeforeIndSent");
|
||||
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
|
||||
}
|
||||
@@ -725,7 +803,7 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
|
||||
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
|
||||
oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param);
|
||||
oacp_ind_send(conn, attr, oacp_proc, oacp_status, &resp_param);
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -741,5 +819,10 @@ void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
oacp_ind->is_enabled = false;
|
||||
if (value == BT_GATT_CCC_INDICATE) {
|
||||
oacp_ind->is_enabled = true;
|
||||
} else {
|
||||
LOG_DBG("OtsOacpIndClrOnCcc");
|
||||
(void)k_work_cancel_delayable(&oacp_ind->work);
|
||||
oacp_ind->ind_in_flight = false;
|
||||
oacp_ind->conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -200,16 +201,23 @@ static void olcp_ind_cb(struct bt_conn *conn,
|
||||
struct bt_gatt_indicate_params *params,
|
||||
uint8_t err)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) params->attr->user_data;
|
||||
|
||||
LOG_DBG("OtsOlcpRecvIndAck[%04x]", err);
|
||||
|
||||
ots->olcp_ind.ind_in_flight = false;
|
||||
ots->olcp_ind.conn = NULL;
|
||||
}
|
||||
|
||||
static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
|
||||
static void olcp_ind_send(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *olcp_attr,
|
||||
enum bt_gatt_ots_olcp_proc_type req_op_code,
|
||||
enum bt_gatt_ots_olcp_res_code olcp_status)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) olcp_attr->user_data;
|
||||
uint8_t *olcp_res = ots->olcp_ind.res;
|
||||
uint16_t olcp_res_len = 0;
|
||||
int err;
|
||||
|
||||
/* Encode OLCP Response */
|
||||
olcp_res[olcp_res_len++] = BT_GATT_OTS_OLCP_PROC_RESP;
|
||||
@@ -219,7 +227,7 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
|
||||
/* Prepare indication parameters */
|
||||
memset(&ots->olcp_ind.params, 0, sizeof(ots->olcp_ind.params));
|
||||
memcpy(&ots->olcp_ind.attr, olcp_attr, sizeof(ots->olcp_ind.attr));
|
||||
ots->olcp_ind.params.attr = olcp_attr;
|
||||
ots->olcp_ind.params.attr = &ots->olcp_ind.attr;
|
||||
ots->olcp_ind.params.func = olcp_ind_cb;
|
||||
ots->olcp_ind.params.data = olcp_res;
|
||||
ots->olcp_ind.params.len = olcp_res_len;
|
||||
@@ -227,9 +235,16 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
|
||||
ots->olcp_ind.params.chan_opt = BT_ATT_CHAN_OPT_NONE;
|
||||
#endif /* CONFIG_BT_EATT */
|
||||
|
||||
LOG_DBG("OtsOlcpSendInd");
|
||||
LOG_DBG("OtsOlcpSendInd[%u]", conn->handle);
|
||||
|
||||
k_work_submit(&ots->olcp_ind.work);
|
||||
ots->olcp_ind.conn = conn;
|
||||
ots->olcp_ind.ind_in_flight = true;
|
||||
err = k_work_schedule(&ots->olcp_ind.work, K_NO_WAIT_ASYNC);
|
||||
if (err < 0) {
|
||||
LOG_ERR("OtsOlcpSchIndFail[%u][%d]", conn->handle, err);
|
||||
ots->olcp_ind.ind_in_flight = false;
|
||||
ots->olcp_ind.conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
|
||||
@@ -255,11 +270,18 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
if (k_work_is_pending(&ots->olcp_ind.work)) {
|
||||
if (ots->olcp_ind.ind_in_flight ||
|
||||
k_work_is_pending(&ots->olcp_ind.work.work)) {
|
||||
LOG_WRN("OtsOlcpWrBeforeIndSent");
|
||||
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
|
||||
}
|
||||
|
||||
if (ots->cur_obj &&
|
||||
ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
|
||||
LOG_WRN("OtsOlcpWrObjBusy[%d]", ots->cur_obj->state.type);
|
||||
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
|
||||
}
|
||||
|
||||
old_obj = ots->cur_obj;
|
||||
|
||||
decode_status = olcp_command_decode(buf, len, &olcp_proc);
|
||||
@@ -300,7 +322,7 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
|
||||
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
|
||||
olcp_ind_send(attr, olcp_proc.type, olcp_status);
|
||||
olcp_ind_send(conn, attr, olcp_proc.type, olcp_status);
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -316,5 +338,10 @@ void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
olcp_ind->is_enabled = false;
|
||||
if (value == BT_GATT_CCC_INDICATE) {
|
||||
olcp_ind->is_enabled = true;
|
||||
} else {
|
||||
LOG_DBG("OtsOlcpIndClrOnCcc");
|
||||
(void)k_work_cancel_delayable(&olcp_ind->work);
|
||||
olcp_ind->ind_in_flight = false;
|
||||
olcp_ind->conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
@@ -2695,6 +2697,7 @@ int bt_bap_broadcast_sink_delete_safe(struct bt_bap_broadcast_sink *sink);
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb);
|
||||
int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb);
|
||||
|
||||
/**
|
||||
@@ -2705,6 +2708,7 @@ int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb);
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_bap_scan_delegator_unregister(void);
|
||||
int bt_bap_scan_delegator_unregister_safe(void);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
Submodule components/bt/esp_ble_audio/lib/lib updated: db6ea94a1e...11b8159e74
@@ -44,6 +44,7 @@ list(APPEND ble_iso_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/common/app/gap.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/common/app/gatt.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/utils/crc/crc32_sw.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/utils/assert.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/utils/addr.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/utils/bt_str.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/utils/buf.c"
|
||||
@@ -57,21 +58,6 @@ list(APPEND ble_iso_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_iso_common_api.c"
|
||||
)
|
||||
|
||||
# L2CAP host shim wiring is currently only consumed by the OTS service
|
||||
# (which lives in esp_ble_audio). common/l2cap.c has both host branches
|
||||
# (Bluedroid returns -ENOTSUP), so compile it under either host whenever
|
||||
# OTS is enabled; the NimBLE-only adapter glue stays NimBLE-gated.
|
||||
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
|
||||
list(APPEND ble_iso_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/common/l2cap.c"
|
||||
)
|
||||
if(CONFIG_BT_NIMBLE_ENABLED)
|
||||
list(APPEND ble_iso_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/l2cap.c"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
list(APPEND ble_iso_include_dirs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/api/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/host/common/include"
|
||||
|
||||
@@ -35,15 +35,9 @@ esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size,
|
||||
type = ltv[i + 1];
|
||||
data_len = len - sizeof(uint8_t);
|
||||
|
||||
/* Skip empty value entries in strict parsing mode. */
|
||||
if (data_len == 0) {
|
||||
i += (size_t)len + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* A callback returning false aborts parsing; report it as an error so
|
||||
* callers detect it (bt_audio_data_parse() returns -ECANCELED here). */
|
||||
if (func(type, <v[i + 2], data_len, user_data) == false) {
|
||||
/* Zero-length values are valid (e.g. BROADCAST_IMMEDIATE flag-only LTV).
|
||||
* Match Zephyr bt_audio_data_parse: invoke callback with data=NULL. */
|
||||
if (func(type, data_len > 0 ? <v[i + 2] : NULL, data_len, user_data) == false) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -292,20 +286,26 @@ esp_err_t esp_ble_iso_big_sync(uint16_t sync_handle,
|
||||
esp_ble_iso_big_sync_param_t *param,
|
||||
esp_ble_iso_big_t **out_big)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
void *per_adv_sync;
|
||||
int err;
|
||||
|
||||
per_adv_sync = bt_le_per_adv_sync_find_safe(sync_handle);
|
||||
bt_le_host_lock();
|
||||
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
ret = ESP_ERR_NOT_FOUND;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
err = bt_iso_big_sync_safe(per_adv_sync, param, out_big);
|
||||
err = bt_iso_big_sync(per_adv_sync, param, out_big);
|
||||
if (err) {
|
||||
return ESP_FAIL;
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
unlock:
|
||||
bt_le_host_unlock();
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
|
||||
|
||||
@@ -399,7 +399,7 @@ esp_err_t esp_ble_iso_chan_send_ts(esp_ble_iso_chan_t *chan,
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_TX */
|
||||
|
||||
void esp_ble_iso_gap_app_post_event(uint8_t type, void *param)
|
||||
void esp_ble_iso_gap_app_post_event(uint16_t type, void *param)
|
||||
{
|
||||
bt_le_gap_app_post_event(type, param);
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ typedef struct {
|
||||
* @param type Event type.
|
||||
* @param param Event parameters.
|
||||
*/
|
||||
void esp_ble_iso_gap_app_post_event(uint8_t type, void *param);
|
||||
void esp_ble_iso_gap_app_post_event(uint16_t type, void *param);
|
||||
|
||||
/**
|
||||
* @brief Initialize ISO common functionality.
|
||||
|
||||
@@ -154,7 +154,7 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
/* Only AUTH_CMPL reaches here from the application's
|
||||
* esp_ble_gap_register_callback path. EXT_ADV_REPORT / PA_SYNC_ESTAB /
|
||||
@@ -164,26 +164,17 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
const esp_ble_auth_cmpl_t *a = &p->ble_security.auth_cmpl;
|
||||
struct gatt_conn *gatt_conn;
|
||||
uint8_t sec_level;
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(a->addr_type, a->bd_addr, false);
|
||||
if (gatt_conn == NULL) {
|
||||
LOG_ERR("[B]UnknownDevForEnc");
|
||||
free(qev);
|
||||
return;
|
||||
}
|
||||
|
||||
qev->type = BT_LE_GAP_APP_PARAM_SECURITY_CHANGE;
|
||||
|
||||
qev->security_change.status = (a->success ? 0x00 : 0xFF);
|
||||
|
||||
/* Populate connection identity unconditionally so failure events
|
||||
* carry valid conn_handle / role / dst to the application. */
|
||||
qev->security_change.conn_handle = gatt_conn->conn_handle;
|
||||
qev->security_change.role = gatt_conn->role;
|
||||
qev->security_change.dst.type = gatt_conn->peer.type;
|
||||
memcpy(qev->security_change.dst.val, gatt_conn->peer.val, BT_ADDR_SIZE);
|
||||
/* dst only: conn_handle/role are resolved from it by the iso_task handler.
|
||||
* Looking them up here would mean reading gatt_conns[] from BTC, which
|
||||
* iso_task mutates under host_lock - and BTC must not take that lock. */
|
||||
qev->security_change.dst.type = a->addr_type;
|
||||
memcpy(qev->security_change.dst.val, a->bd_addr, BT_ADDR_SIZE);
|
||||
|
||||
if (qev->security_change.status == 0) {
|
||||
/* Derive level from auth_mode bits (mirrors NimBLE's sec_state
|
||||
@@ -204,9 +195,9 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
|
||||
/* Attach the bonded LTK (CSIS SIRK-encryption key K). */
|
||||
if (bd_read_bonded_ltk(a->bd_addr, qev->security_change.ltk)) {
|
||||
qev->security_change.ltk_present = 1;
|
||||
LOG_INF("[B]LtkFromStore[%u]", qev->security_change.conn_handle);
|
||||
LOG_INF("[B]LtkFromStore");
|
||||
} else {
|
||||
LOG_WRN("[B]NoLtkForEnc[%u]", qev->security_change.conn_handle);
|
||||
LOG_WRN("[B]NoLtkForEnc");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +225,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
switch (event) {
|
||||
#if (BLE_50_EXTEND_SCAN_EN == TRUE)
|
||||
@@ -258,7 +249,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
|
||||
|
||||
if (qev->ext_scan_recv.data_len) {
|
||||
qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len);
|
||||
assert(qev->ext_scan_recv.data);
|
||||
BT_LE_ASSERT(qev->ext_scan_recv.data);
|
||||
memcpy(qev->ext_scan_recv.data, r->adv_data, qev->ext_scan_recv.data_len);
|
||||
}
|
||||
break;
|
||||
@@ -347,7 +338,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
|
||||
|
||||
if (qev->pa_sync_recv.data_len) {
|
||||
qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len);
|
||||
assert(qev->pa_sync_recv.data);
|
||||
BT_LE_ASSERT(qev->pa_sync_recv.data);
|
||||
memcpy(qev->pa_sync_recv.data, r->data, qev->pa_sync_recv.data_len);
|
||||
}
|
||||
break;
|
||||
@@ -357,16 +348,6 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
|
||||
#if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE)
|
||||
case BTM_BLE_GAP_PERIODIC_ADV_SYNC_TRANS_RECV_EVT: {
|
||||
const tBTM_BLE_PERIOD_ADV_SYNC_TRANS_RECV *r = ¶ms->past_recv;
|
||||
struct gatt_conn *gatt_conn;
|
||||
|
||||
/* Look up the PAST-delivering ACL handle by peer BDA — past_recv
|
||||
* doesn't carry addr type. */
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, r->addr, true);
|
||||
if (gatt_conn == NULL) {
|
||||
LOG_ERR("[B]UnknownPastSrc");
|
||||
free(qev);
|
||||
return;
|
||||
}
|
||||
|
||||
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_PAST;
|
||||
|
||||
@@ -378,7 +359,13 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
|
||||
qev->pa_sync_past.adv_phy = r->adv_phy;
|
||||
qev->pa_sync_past.per_adv_itvl = r->adv_interval;
|
||||
qev->pa_sync_past.adv_ca = r->adv_clk_accuracy;
|
||||
qev->pa_sync_past.conn_handle = gatt_conn->conn_handle;
|
||||
|
||||
/* Sender identity: HCI reports it as a Connection_Handle and carries no
|
||||
* address for it (adv_addr_type/adv_addr above are the advertiser's).
|
||||
* btu_hcif copies the BDA out of the LCB but drops both the handle and
|
||||
* the address type, so .type stays unset and the handler matches on the
|
||||
* value alone. */
|
||||
memcpy(qev->pa_sync_past.src_addr.val, r->addr, BT_ADDR_SIZE);
|
||||
|
||||
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
|
||||
/* PAST-established syncs need tracker too: a later app-initiated
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "bta/bta_gatt_common.h"
|
||||
#include "bta_gattc_int.h"
|
||||
#include "btc_gatt_util.h"
|
||||
#include "osi/allocator.h"
|
||||
#include "stack/btm_ble_api.h"
|
||||
|
||||
#include "common/host.h"
|
||||
@@ -148,7 +149,7 @@ static int gatts_indicate_resolve(const struct bt_gatt_attr *attr_in,
|
||||
handle = data.handle;
|
||||
|
||||
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
|
||||
assert(data.attr->user_data);
|
||||
BT_LE_ASSERT(data.attr->user_data);
|
||||
chrc = data.attr->user_data;
|
||||
|
||||
if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) {
|
||||
@@ -181,7 +182,7 @@ static struct gatts_list_node *gatts_list_node_alloc(struct bt_gatt_indicate_par
|
||||
n->data_copy = NULL;
|
||||
|
||||
if (ip->len > 0) {
|
||||
assert(ip->data);
|
||||
BT_LE_ASSERT(ip->data);
|
||||
|
||||
n->data_copy = bt_le_ext_malloc(ip->len);
|
||||
if (n->data_copy == NULL) {
|
||||
@@ -226,6 +227,27 @@ static void gatts_notify_list_drain(struct gatt_conn *gatt_conn)
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the first notify marker whose value_handle matches. Used by
|
||||
* handle_gatts_notify_tx_event so an indication CONF cannot be mistaken for
|
||||
* a notify CONF when a notify marker is already queued (BTU/ISO race). */
|
||||
static struct gatts_notify_node *
|
||||
gatts_notify_take_by_handle(struct gatt_conn *gatt_conn, uint16_t handle)
|
||||
{
|
||||
struct gatts_notify_node *notify;
|
||||
struct gatts_notify_node *tmp;
|
||||
sys_snode_t *prev = NULL;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&gatt_conn->gatts_notify_list, notify, tmp, node) {
|
||||
if (notify->value_handle == handle) {
|
||||
sys_slist_remove(&gatt_conn->gatts_notify_list, prev, ¬ify->node);
|
||||
return notify;
|
||||
}
|
||||
prev = ¬ify->node;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Rewrite a slot's conn_id to the GATTS/GATTC gatt_if (low byte); the BTA index
|
||||
* (high byte) is shared on one ACL. Lets a dual-role link op on the right conn_id. */
|
||||
static inline uint16_t to_gatts_conn_id(uint16_t conn_id)
|
||||
@@ -280,6 +302,13 @@ uint8_t bt_le_bluedroid_gattc_get_if(void)
|
||||
|
||||
uint8_t bt_le_bluedroid_gatts_get_if(void)
|
||||
{
|
||||
/* Callers pass this straight to BTA without checking, so the value matters
|
||||
* less than the log: a silent 0 surfaces only as an unexplained sem timeout. */
|
||||
if (gatts_if == 0) {
|
||||
LOG_WRN("[B]GetGattsIfBeforeInit");
|
||||
return 0xFF; /* ESP_GATT_IF_NONE */
|
||||
}
|
||||
|
||||
return gatts_if;
|
||||
}
|
||||
|
||||
@@ -435,7 +464,7 @@ static void gattc_connect_event_handler(tBTA_GATTC_CONNECT *connect)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_CONNECT_EVENT;
|
||||
|
||||
@@ -458,7 +487,7 @@ static void gattc_disconnect_event_handler(tBTA_GATTC_DISCONNECT *disconnect)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_DISCONNECT_EVENT;
|
||||
|
||||
@@ -478,7 +507,7 @@ static void gattc_open_event_handler(tBTA_GATTC_OPEN *open)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_OPEN_EVENT;
|
||||
|
||||
@@ -498,7 +527,7 @@ static void gattc_mtu_event_handler(tBTA_GATTC_CFG_MTU *cfg_mtu)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_MTU_EVENT;
|
||||
|
||||
@@ -519,7 +548,7 @@ static void gattc_disc_cmpl_event_handler(tBTA_GATTC_DIS_CMPL *disc_cmpl)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_DISC_CMPL_EVENT;
|
||||
|
||||
@@ -539,7 +568,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_READ_CHRC_EVENT;
|
||||
|
||||
@@ -553,7 +582,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
|
||||
qev->gattc_read_chrc.len = read->p_value->len;
|
||||
|
||||
qev->gattc_read_chrc.value = bt_le_ext_calloc(1, read->p_value->len);
|
||||
assert(qev->gattc_read_chrc.value);
|
||||
BT_LE_ASSERT(qev->gattc_read_chrc.value);
|
||||
|
||||
memcpy(qev->gattc_read_chrc.value, read->p_value->p_value, read->p_value->len);
|
||||
}
|
||||
@@ -574,7 +603,7 @@ static void gattc_write_chrc_event_handler(tBTA_GATTC_WRITE *write)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_WRITE_CHRC_EVENT;
|
||||
|
||||
@@ -596,15 +625,15 @@ static void gatts_notify_tx_event_handler(tBTA_GATTS_REQ *req)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_NOTIFY_TX_EVENT;
|
||||
|
||||
/* BTA fires CONF_EVT for both indication acks and immediate notify
|
||||
* completions; tBTA_GATTS_REQ carries no flag to tell them apart. The
|
||||
* bluedroid handler disambiguates by popping a parallel notify marker
|
||||
* list first (see gatts_notify_enqueue / handle_gatts_notify_tx_event),
|
||||
* so is_notify here is left unused on this path. */
|
||||
* bluedroid handler matches by attr_handle against gatts_list head /
|
||||
* gatts_notify_list markers (see gatts_notify_take_by_handle), so
|
||||
* is_notify here is left unused on this path. */
|
||||
qev->gatts_notify_tx.is_notify = false;
|
||||
qev->gatts_notify_tx.conn_id = req->conn_id;
|
||||
qev->gatts_notify_tx.attr_handle = req->handle;
|
||||
@@ -623,7 +652,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_NOTIFY_RX_EVENT;
|
||||
|
||||
@@ -635,7 +664,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
|
||||
qev->gattc_notify_rx.len = notify->len;
|
||||
|
||||
qev->gattc_notify_rx.value = bt_le_ext_calloc(1, notify->len);
|
||||
assert(qev->gattc_notify_rx.value);
|
||||
BT_LE_ASSERT(qev->gattc_notify_rx.value);
|
||||
|
||||
memcpy(qev->gattc_notify_rx.value, notify->value, notify->len);
|
||||
}
|
||||
@@ -656,7 +685,7 @@ static void gatts_connect_event_handler(tBTA_GATTS_CONN *connect)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_CONNECT_EVENT;
|
||||
|
||||
@@ -679,7 +708,7 @@ static void gatts_disconnect_event_handler(tBTA_GATTS_CONN *disconnect)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_DISCONNECT_EVENT;
|
||||
|
||||
@@ -700,7 +729,7 @@ static void gatts_mtu_event_handler(tBTA_GATTS_REQ *req)
|
||||
|
||||
/* req->p_data is non-NULL here: BTA always passes a stack object. */
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_MTU_EVENT;
|
||||
|
||||
@@ -720,7 +749,7 @@ static void gatts_read_req_handler(tBTA_GATTS_REQ *req)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_READ_EVENT;
|
||||
|
||||
@@ -746,7 +775,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
|
||||
|
||||
/* req->p_data is non-NULL here: BTA always passes a stack object. */
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_WRITE_EVENT;
|
||||
|
||||
@@ -762,7 +791,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
|
||||
qev->gatts_write.len = req->p_data->write_req.len;
|
||||
|
||||
qev->gatts_write.value = bt_le_ext_calloc(1, req->p_data->write_req.len);
|
||||
assert(qev->gatts_write.value);
|
||||
BT_LE_ASSERT(qev->gatts_write.value);
|
||||
|
||||
memcpy(qev->gatts_write.value, req->p_data->write_req.value, req->p_data->write_req.len);
|
||||
}
|
||||
@@ -783,7 +812,7 @@ static void gatts_exec_write_req_handler(tBTA_GATTS_REQ *req)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTS_EXEC_WRITE_EVENT;
|
||||
qev->gatts_exec_write.conn_id = req->conn_id;
|
||||
@@ -1369,7 +1398,7 @@ static void post_acl_connect_app_event(struct gatt_conn *gatt_conn)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GAP_APP_PARAM_ACL_CONNECT;
|
||||
|
||||
@@ -1397,7 +1426,7 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GAP_APP_PARAM_ACL_DISCONNECT;
|
||||
|
||||
@@ -1411,6 +1440,24 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason)
|
||||
}
|
||||
}
|
||||
|
||||
/* Fire func(err) for every pending GATTS indication and free the nodes, so lib
|
||||
* state machines waiting on a confirm unblock. conn may be NULL (already gone)
|
||||
* — func(NULL, ...) still releases the waiter. */
|
||||
static void gatts_list_fire_and_drain(struct gatt_conn *gatt_conn,
|
||||
struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
struct gatts_list_node *n;
|
||||
sys_snode_t *snode;
|
||||
|
||||
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
|
||||
n = CONTAINER_OF(snode, struct gatts_list_node, node);
|
||||
if (n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, err);
|
||||
}
|
||||
gatts_list_node_free(n);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
@@ -1423,8 +1470,22 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false);
|
||||
if (gatt_conn) {
|
||||
LOG_ERR("[B]DevAlreadyExists");
|
||||
return;
|
||||
if (gatt_conn->conn_handle == event->conn_handle) {
|
||||
LOG_ERR("[B]DevAlreadyExists");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Same peer, different handle: prior disconnect was missed/delayed.
|
||||
* Drop the stale slot so reconnect can proceed. */
|
||||
LOG_WRN("[B]StaleGattcConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle);
|
||||
|
||||
post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED);
|
||||
|
||||
/* Stale slot never gets a disconnect event: fire pending indication
|
||||
* funcs so lib state machines unblock before the drain in reset. */
|
||||
gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY);
|
||||
|
||||
reset_gatt_conn(gatt_conn);
|
||||
}
|
||||
|
||||
/* App initiated via esp_ble_gattc_aux_open(engine_gattc_if, ...). BTA
|
||||
@@ -1450,9 +1511,7 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
|
||||
static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *event)
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
struct gatts_list_node *n;
|
||||
struct bt_conn *conn;
|
||||
sys_snode_t *snode;
|
||||
|
||||
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
|
||||
if (gatt_conn == NULL) {
|
||||
@@ -1463,22 +1522,15 @@ static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *e
|
||||
event->conn_handle = gatt_conn->conn_handle;
|
||||
|
||||
if (gatt_conn->role == BTM_ROLE_MASTER) {
|
||||
/* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS).
|
||||
* Mirror handle_gatts_disconnect_event: fire func(err) for every
|
||||
* pending indication so lib state machines don't stall. */
|
||||
conn = bt_le_acl_conn_find(event->conn_handle);
|
||||
|
||||
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
|
||||
n = CONTAINER_OF(snode, struct gatts_list_node, node);
|
||||
if (conn && n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
gatts_list_node_free(n);
|
||||
}
|
||||
/* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS):
|
||||
* fire func(err) for pending indications so lib state machines don't
|
||||
* stall. conn may be gone — func(NULL, ...) still unblocks waiters. */
|
||||
gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY);
|
||||
|
||||
post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason);
|
||||
|
||||
/* Reset the corresponding gatt_conn */
|
||||
reset_gatt_conn(gatt_conn);
|
||||
}
|
||||
}
|
||||
@@ -1502,13 +1554,11 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
|
||||
post_acl_connect_app_event(gatt_conn);
|
||||
|
||||
if (gatt_conn->status) {
|
||||
/* If failed to create connection, reset the corresponding gatt_conn */
|
||||
reset_gatt_conn(gatt_conn);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (gatt_conn->gattc_open && gatt_conn->status != BTA_GATT_OK) {
|
||||
/* Failed to open gatt client, post an event to notify the app layer */
|
||||
bt_le_gattc_app_open_event(event, gattc_if);
|
||||
return;
|
||||
}
|
||||
@@ -1526,15 +1576,13 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
|
||||
gatt_conn->cfg_mtu = 1;
|
||||
}
|
||||
|
||||
/* Post an event to the app layer, we need this event to discover
|
||||
* services and characteristics, etc.
|
||||
*/
|
||||
bt_le_gattc_app_open_event(event, gattc_if);
|
||||
}
|
||||
|
||||
static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
uint16_t prev_mtu;
|
||||
|
||||
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
|
||||
if (gatt_conn == NULL) {
|
||||
@@ -1548,6 +1596,8 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
|
||||
* related to the Link Layer role, hence we don't check the Link layer
|
||||
* role here and only check if the MTU has already been exchanged here.
|
||||
*/
|
||||
prev_mtu = gatt_conn->mtu;
|
||||
|
||||
if (gatt_conn->mtu != 0) {
|
||||
LOG_INF("[B]GattcMtuExchanged[%u][%u][%u]",
|
||||
gatt_conn->conn_handle, gatt_conn->mtu, event->mtu);
|
||||
@@ -1558,13 +1608,17 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
|
||||
}
|
||||
|
||||
if (gatt_conn->cfg_mtu && gatt_conn->mtu_posted == 0) {
|
||||
/* Post an event to the app layer, we need this event to discover
|
||||
* services and characteristics, etc. Report the clamped value so
|
||||
* the app stays consistent with gatt_conn state (mirrors NimBLE's
|
||||
* BLE_GAP_EVENT_MTU which already carries the negotiated value). */
|
||||
/* Report the clamped value so the app stays consistent with
|
||||
* gatt_conn state (mirrors NimBLE's BLE_GAP_EVENT_MTU). */
|
||||
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
|
||||
|
||||
gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */
|
||||
gatt_conn->mtu_posted = 1;
|
||||
} else if (gatt_conn->mtu_posted && gatt_conn->mtu < prev_mtu) {
|
||||
/* Dual-role: peer/side may clamp after the first post; app must
|
||||
* learn the smaller value or it will oversize ATT PDUs. */
|
||||
LOG_INF("[B]GattcMtuClamped[%u][%u][%u]",
|
||||
event->conn_handle, prev_mtu, gatt_conn->mtu);
|
||||
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1617,7 +1671,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
|
||||
const uint8_t *val;
|
||||
sys_snode_t *snode;
|
||||
uint16_t vlen;
|
||||
uint16_t off;
|
||||
uint8_t ret;
|
||||
|
||||
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
|
||||
@@ -1660,11 +1713,14 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((read_copy.handle_count == 0 &&
|
||||
(event->attr_handle < read_copy.by_uuid.start_handle ||
|
||||
event->attr_handle > read_copy.by_uuid.end_handle)) ||
|
||||
(read_copy.handle_count == 1 &&
|
||||
event->attr_handle != read_copy.single.handle)) {
|
||||
/* On error BTA often returns attr_handle 0; only validate the handle when
|
||||
* the read succeeded, otherwise pass the real ATT status to the caller. */
|
||||
if (event->status == 0 &&
|
||||
((read_copy.handle_count == 0 &&
|
||||
(event->attr_handle < read_copy.by_uuid.start_handle ||
|
||||
event->attr_handle > read_copy.by_uuid.end_handle)) ||
|
||||
(read_copy.handle_count == 1 &&
|
||||
event->attr_handle != read_copy.single.handle))) {
|
||||
LOG_ERR("[B]GattcRdCharInvRsp[%u][%u][%u][%u][%u]",
|
||||
read_copy.handle_count, event->attr_handle,
|
||||
read_copy.by_uuid.start_handle,
|
||||
@@ -1676,13 +1732,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
|
||||
val = event->value;
|
||||
vlen = event->len;
|
||||
|
||||
if (read_copy.handle_count == 1 && read_copy.single.offset != 0 &&
|
||||
event->status == 0) {
|
||||
off = read_copy.single.offset;
|
||||
val = (off < event->len) ? event->value + off : NULL;
|
||||
vlen = (off < event->len) ? (event->len - off) : 0;
|
||||
}
|
||||
|
||||
/* By-UUID read: report the matched handle in params->by_uuid.start_handle (like
|
||||
* Zephyr); callers (has_client active-index read) use it as the value handle. */
|
||||
if (read_copy.handle_count == 0 && event->status == 0) {
|
||||
@@ -1745,7 +1794,7 @@ static void handle_gattc_write_chrc_event(struct bt_le_gattc_write_chrc_event *e
|
||||
params = op->write_params;
|
||||
free(op);
|
||||
|
||||
assert(params && params->func);
|
||||
BT_LE_ASSERT(params && params->func);
|
||||
|
||||
if (event->attr_handle != params->handle) {
|
||||
LOG_ERR("[B]GattcWrCharInvRsp[%u][%u]", event->attr_handle, params->handle);
|
||||
@@ -1826,8 +1875,22 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event)
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false);
|
||||
if (gatt_conn) {
|
||||
LOG_ERR("[B]DevAlreadyExists");
|
||||
return;
|
||||
if (gatt_conn->conn_handle == event->conn_handle) {
|
||||
LOG_ERR("[B]DevAlreadyExists");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Same peer, different handle: prior disconnect was missed/delayed.
|
||||
* Drop the stale slot so reconnect can proceed. */
|
||||
LOG_WRN("[B]StaleGattsConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle);
|
||||
|
||||
post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED);
|
||||
|
||||
/* Stale slot never gets a disconnect event: fire pending indication
|
||||
* funcs so lib state machines unblock before the drain in reset. */
|
||||
gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY);
|
||||
|
||||
reset_gatt_conn(gatt_conn);
|
||||
}
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_free_gatt_conn();
|
||||
@@ -1860,10 +1923,8 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event)
|
||||
|
||||
static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *event)
|
||||
{
|
||||
struct gatts_list_node *n;
|
||||
struct gatt_conn *gatt_conn;
|
||||
struct bt_conn *conn;
|
||||
sys_snode_t *snode;
|
||||
|
||||
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
|
||||
if (gatt_conn == NULL) {
|
||||
@@ -1879,32 +1940,22 @@ static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *e
|
||||
return;
|
||||
}
|
||||
|
||||
/* conn may already have been torn down by the ACL layer before this event
|
||||
* arrives — best-effort lookup, NULL is tolerated and the drain still runs
|
||||
* (params->func is just skipped for nodes we can't address). */
|
||||
conn = bt_le_acl_conn_find(event->conn_handle);
|
||||
|
||||
/* Fire func with err for every pending indication. Mirrors NimBLE stack's
|
||||
* ble_gatts_indicate_fail_notconn on disconn — BTA never delivers CONF_EVT
|
||||
* for in-flight indications when the conn drops, so the adapter has to
|
||||
* simulate that fail path to keep lib state machines from stalling. */
|
||||
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
|
||||
n = CONTAINER_OF(snode, struct gatts_list_node, node);
|
||||
if (conn && n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
gatts_list_node_free(n);
|
||||
}
|
||||
/* conn may be gone — fire func(NULL, err) for pending indications so upper
|
||||
* layers don't stall waiting for a CONF_EVT BTA never delivers after the
|
||||
* link drops. */
|
||||
gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY);
|
||||
|
||||
post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason);
|
||||
|
||||
/* Reset the corresponding gatt_conn */
|
||||
reset_gatt_conn(gatt_conn);
|
||||
}
|
||||
|
||||
static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
uint16_t prev_mtu;
|
||||
|
||||
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
|
||||
if (gatt_conn == NULL) {
|
||||
@@ -1918,6 +1969,8 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
|
||||
* related to the Link Layer role, hence we don't check the Link layer
|
||||
* role here and only check if the MTU has already been exchanged here.
|
||||
*/
|
||||
prev_mtu = gatt_conn->mtu;
|
||||
|
||||
if (gatt_conn->mtu != 0) {
|
||||
LOG_INF("[B]GattsMtuExchanged[%u][%u][%u]",
|
||||
gatt_conn->conn_handle, gatt_conn->mtu, event->mtu);
|
||||
@@ -1931,7 +1984,12 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
|
||||
/* Report clamped value — see handle_gattc_mtu_event. */
|
||||
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
|
||||
|
||||
gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */
|
||||
gatt_conn->mtu_posted = 1;
|
||||
} else if (gatt_conn->mtu < prev_mtu) {
|
||||
/* Dual-role clamp after first post — see handle_gattc_mtu_event. */
|
||||
LOG_INF("[B]GattsMtuClamped[%u][%u][%u]",
|
||||
event->conn_handle, prev_mtu, gatt_conn->mtu);
|
||||
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1969,11 +2027,11 @@ static void handle_gatts_read_event(struct bt_le_gatts_read_event *event)
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* The tBTA_GATT_STATUS structure is too large, hence use
|
||||
/* The tBTA_GATTS_RSP structure is too large, hence use
|
||||
* dynamic memory here to avoid stack overflow.
|
||||
*/
|
||||
rsp = bt_le_ext_calloc(1, sizeof(*(rsp)));
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
rsp->attr_value.handle = event->attr_handle;
|
||||
|
||||
@@ -2069,7 +2127,7 @@ static void handle_gatts_prepare_write(struct bt_le_gatts_write_event *event)
|
||||
if (status == BTA_GATT_OK) {
|
||||
/* The prepare-write response must echo handle/offset/value. */
|
||||
rsp = bt_le_ext_calloc(1, sizeof(*rsp));
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
rsp->attr_value.handle = event->attr_handle;
|
||||
rsp->attr_value.offset = event->offset;
|
||||
@@ -2133,7 +2191,6 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Check if the attribute UUID is CCCD */
|
||||
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) == 0) {
|
||||
if (event->len != 2) {
|
||||
LOG_ERR("[B]GattsWrInvPdu[%u]", event->len);
|
||||
@@ -2256,10 +2313,12 @@ static void handle_gatts_exec_write_event(struct bt_le_gatts_exec_write_event *e
|
||||
|
||||
static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *event)
|
||||
{
|
||||
struct gatts_list_node *n;
|
||||
struct gatts_notify_node *notify;
|
||||
struct gatt_conn *gatt_conn;
|
||||
struct gatts_list_node *n;
|
||||
struct bt_conn *conn;
|
||||
sys_snode_t *snode;
|
||||
bool ind_match;
|
||||
|
||||
/* Find conn once up front. If the acl_conn was already torn down (race
|
||||
* with a queued CLOSE_EVT behind this CONF_EVT), leave the head in the
|
||||
@@ -2278,28 +2337,51 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
|
||||
return;
|
||||
}
|
||||
|
||||
/* Notify CONF_EVT first: BTA fires CONF_EVT immediately after send for
|
||||
* notifications; pop the matching marker so we don't mistake it for an
|
||||
* indication ack and corrupt the indication queue. */
|
||||
snode = sys_slist_get(&gatt_conn->gatts_notify_list);
|
||||
/* BTA fires CONF_EVT for both notify completions and indication acks,
|
||||
* with no type flag. Match by attr_handle — do NOT assume "any notify
|
||||
* marker means this CONF is a notify": a notify may be enqueued on the
|
||||
* ISO path while an earlier indication's peer ACK is already queued,
|
||||
* which would swallow the indication CONF and stall gatts_list. When
|
||||
* both heads share the same handle, prefer indication (peer ACK); the
|
||||
* notify CONF arrives shortly and claims the leftover marker. */
|
||||
snode = sys_slist_peek_head(&gatt_conn->gatts_list);
|
||||
ind_match = false;
|
||||
n = NULL;
|
||||
if (snode != NULL) {
|
||||
struct gatts_notify_node *nn = CONTAINER_OF(snode, struct gatts_notify_node, node);
|
||||
n = CONTAINER_OF(snode, struct gatts_list_node, node);
|
||||
ind_match = (n->value_handle == event->attr_handle);
|
||||
}
|
||||
|
||||
if (nn->value_handle != event->attr_handle) {
|
||||
LOG_DBG("[B]NotifyTxHdlSkew[%u][%u]", event->attr_handle, nn->value_handle);
|
||||
if (!ind_match) {
|
||||
notify = gatts_notify_take_by_handle(gatt_conn, event->attr_handle);
|
||||
if (notify != NULL) {
|
||||
free(notify);
|
||||
return;
|
||||
}
|
||||
free(nn);
|
||||
|
||||
if (n == NULL) {
|
||||
LOG_WRN("[B]NotifyTxNoPending[%u][%u]",
|
||||
event->conn_handle, event->attr_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Indication head exists but handle disagrees, and no notify marker
|
||||
* matched either — list is out of sync. Drop the head to keep
|
||||
* advancing (alternative is freezing the queue). */
|
||||
LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle);
|
||||
|
||||
(void)sys_slist_get(&gatt_conn->gatts_list);
|
||||
|
||||
if (n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
|
||||
gatts_list_node_free(n);
|
||||
return;
|
||||
}
|
||||
|
||||
/* No notify pending — this CONF_EVT is an indication ack. */
|
||||
snode = sys_slist_get(&gatt_conn->gatts_list);
|
||||
if (snode == NULL) {
|
||||
LOG_WRN("[B]NotifyTxNoPending[%u]", event->conn_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
n = CONTAINER_OF(snode, struct gatts_list_node, node);
|
||||
/* Indication ack for the current head. */
|
||||
(void)sys_slist_get(&gatt_conn->gatts_list);
|
||||
|
||||
/* Submit next pending BEFORE firing the completion cb. host_mutex is
|
||||
* recursive, so a cb that re-enters bt_gatt_indicate isn't blocked;
|
||||
@@ -2319,20 +2401,6 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
|
||||
}
|
||||
}
|
||||
|
||||
if (n->value_handle != event->attr_handle) {
|
||||
LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle);
|
||||
/* Mismatch is fatal here: BTA delivers CONF_EVT in FIFO order, so a
|
||||
* head-vs-event handle disagreement means our list is out of sync.
|
||||
* Drop the head to keep advancing (alternative is freezing the queue).
|
||||
* Notify lib with err so its state machine doesn't stall waiting
|
||||
* for a callback that will never match. */
|
||||
if (n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
gatts_list_node_free(n);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n->params_copy.func) {
|
||||
n->params_copy.func(conn, &n->params_copy, event->status);
|
||||
}
|
||||
@@ -2410,7 +2478,7 @@ void bt_le_bluedroid_gatt_handle_event(uint8_t *data, size_t data_len)
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2428,7 +2496,7 @@ void bt_le_bluedroid_gatts_svc_cb_register(struct gatts_svc_cb *cb)
|
||||
|
||||
void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid_out)
|
||||
{
|
||||
assert(uuid_out && uuid_in);
|
||||
BT_LE_ASSERT(uuid_out && uuid_in);
|
||||
|
||||
if (uuid_in->type == BT_UUID_TYPE_16) {
|
||||
((tBT_UUID *)uuid_out)->len = LEN_UUID_16;
|
||||
@@ -2440,7 +2508,7 @@ void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid
|
||||
((tBT_UUID *)uuid_out)->len = LEN_UUID_128;
|
||||
memcpy(((tBT_UUID *)uuid_out)->uu.uuid128, BT_UUID_128(uuid_in)->val, LEN_UUID_128);
|
||||
} else {
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2526,8 +2594,8 @@ ssize_t bt_le_bluedroid_gatts_attr_read(struct bt_conn *conn, const struct bt_ga
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Append a notify marker so handle_gatts_notify_tx_event can pop it off the
|
||||
* notify list ahead of any pending indication head. Returns -ENOTCONN when
|
||||
/* Append a notify marker so handle_gatts_notify_tx_event can match CONF_EVT
|
||||
* by attr_handle against pending notifications. Returns -ENOTCONN when
|
||||
* the gatt_conn is already torn down (caller should skip the BTA send to
|
||||
* avoid a "Unknown connection ID" log from BTA), -ENOMEM on alloc fail. */
|
||||
static int gatts_notify_enqueue(struct bt_conn *conn, uint16_t value_handle)
|
||||
@@ -2582,7 +2650,6 @@ static int gatts_notify(struct bt_conn *conn,
|
||||
data.attr = *attr;
|
||||
data.handle = bt_gatt_attr_get_handle(data.attr);
|
||||
|
||||
/* Lookup UUID if it was given */
|
||||
if (uuid) {
|
||||
if (bt_gatts_find_attr_by_uuid(&data, uuid) == false) {
|
||||
return -ENOENT;
|
||||
@@ -2595,12 +2662,11 @@ static int gatts_notify(struct bt_conn *conn,
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if attribute is a characteristic then adjust the handle */
|
||||
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
|
||||
struct bt_gatt_chrc *chrc;
|
||||
uint16_t required;
|
||||
|
||||
assert(data.attr->user_data);
|
||||
BT_LE_ASSERT(data.attr->user_data);
|
||||
chrc = data.attr->user_data;
|
||||
|
||||
required = need_cfm ? BT_GATT_CHRC_INDICATE : BT_GATT_CHRC_NOTIFY;
|
||||
@@ -2775,7 +2841,6 @@ int bt_le_bluedroid_gattc_disc_start(uint16_t conn_handle)
|
||||
true, BTA_GATT_TRANSPORT_LE, false, 0,
|
||||
false, 0xff, 0xff, 0, NULL, NULL, NULL);
|
||||
|
||||
/* Mark the gattc open as in progress */
|
||||
gatt_conn->gattc_open = 1;
|
||||
|
||||
return 0;
|
||||
@@ -2859,11 +2924,32 @@ static int gattc_disc_primary_svc(struct bt_conn *conn, uint16_t conn_id,
|
||||
|
||||
end:
|
||||
if (db) {
|
||||
free(db);
|
||||
osi_free(db);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* BTA expands every DB UUID to 128-bit. Return true and write the 16-bit
|
||||
* value only when it matches the Bluetooth Base UUID; otherwise unsupported. */
|
||||
static bool bluedroid_db_uuid_as_16(const bt_uuid_t *src, uint16_t *out_val)
|
||||
{
|
||||
static const uint8_t base[16] = {
|
||||
BT_UUID_128_ENCODE(0x00000000, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB)
|
||||
};
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i == 12 || i == 13) {
|
||||
continue;
|
||||
}
|
||||
if (src->uu[i] != base[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*out_val = sys_get_le16(&src->uu[12]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
|
||||
struct bt_gatt_discover_params *params)
|
||||
{
|
||||
@@ -2907,11 +2993,14 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
|
||||
i, db[i].type, db[i].start_handle, db[i].end_handle,
|
||||
db[i].attribute_handle, db[i].properties);
|
||||
|
||||
/* Only 16-bit Bluetooth Base UUIDs are supported; skip 32/128-bit
|
||||
* rather than silently truncating them to a bogus 16-bit value. */
|
||||
if (!bluedroid_db_uuid_as_16(&db[i].uuid, &svc_uuid.val)) {
|
||||
LOG_WRN("[B]InclSvcNon16BitUuidSkip[%u]", db[i].attribute_handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
svc_uuid.uuid.type = BT_UUID_TYPE_16;
|
||||
/* The LSB 12-octets is Bluetooth_Base_UUID, and the remaining
|
||||
* 2-octets or 4-octets is used by 16-bit or 32-bit UUID.
|
||||
*/
|
||||
svc_uuid.val = sys_get_le16(db[i].uuid.uu + 12);
|
||||
|
||||
inc_svc.uuid = &svc_uuid.uuid;
|
||||
inc_svc.start_handle = db[i].start_handle;
|
||||
@@ -2946,7 +3035,7 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
|
||||
|
||||
end:
|
||||
if (db) {
|
||||
free(db);
|
||||
osi_free(db);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -3041,7 +3130,7 @@ static int gattc_disc_chrc(struct bt_conn *conn, uint16_t conn_id,
|
||||
|
||||
end:
|
||||
if (db) {
|
||||
free(db);
|
||||
osi_free(db);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -3058,10 +3147,10 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
|
||||
uint16_t count = 0;
|
||||
int err = 0;
|
||||
|
||||
assert(params);
|
||||
assert(params->uuid);
|
||||
assert(params->func);
|
||||
assert(params->sub_params);
|
||||
BT_LE_ASSERT(params);
|
||||
BT_LE_ASSERT(params->uuid);
|
||||
BT_LE_ASSERT(params->func);
|
||||
BT_LE_ASSERT(params->sub_params);
|
||||
|
||||
/* Only descriptors can be filtered */
|
||||
if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 ||
|
||||
@@ -3100,6 +3189,9 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
|
||||
db[0].type, db[0].start_handle, db[0].end_handle,
|
||||
db[0].attribute_handle, db[0].properties);
|
||||
|
||||
/* Zephyr bt_gatt_attr contract: attr->uuid must be non-NULL. For
|
||||
* filtered descriptor discovery this is the requested descriptor UUID. */
|
||||
attr.uuid = params->uuid;
|
||||
attr.handle = db[0].attribute_handle;
|
||||
|
||||
status = BTA_GATTC_RegisterForNotifications(gattc_if, conn->le.dst.a.val, chrc_handle);
|
||||
@@ -3124,7 +3216,7 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
|
||||
|
||||
end:
|
||||
if (db) {
|
||||
free(db);
|
||||
osi_free(db);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -3186,6 +3278,17 @@ int bt_le_bluedroid_gattc_read(struct bt_conn *conn, struct bt_gatt_read_params
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* BTA cannot emit ATT Read Blob: bta_gattc_read hardcodes GATT_READ_BY_HANDLE
|
||||
* and tBTA_GATTC_API_READ has no offset. Rejecting beats a Read Request from
|
||||
* byte 0, which makes a continuation read stall silently. Do not hoist this
|
||||
* into bt_gatt_read() - NimBLE has ble_gattc_read_long.
|
||||
* TODO: drop once Bluedroid exposes a read-with-offset API. */
|
||||
if (params->handle_count == 1 && params->single.offset != 0) {
|
||||
LOG_ERR("[B]GattcRdNoBlob[%u][%u]",
|
||||
params->single.handle, params->single.offset);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* BTA serializes via p_cmd_list (FIFO); we mirror with gattc_list so the
|
||||
* EVT can recover the caller's params. Append before submit so the head
|
||||
* is set when BTA delivers the cmpl synchronously on error paths. */
|
||||
@@ -3230,6 +3333,13 @@ int bt_le_bluedroid_gattc_write(struct bt_conn *conn, struct bt_gatt_write_param
|
||||
|
||||
LOG_DBG("[B]GattcWr[%u]", conn->handle);
|
||||
|
||||
/* bta_gattc_write memcpy's into a stack tGATT_CL_COMPLETE sized
|
||||
* GATT_MAX_ATTR_LEN, with no length check of its own */
|
||||
if (params->length > BTA_GATT_MAX_ATTR_LEN) {
|
||||
LOG_ERR("[B]GattcWrValTooBig[%u]", params->length);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn->handle);
|
||||
if (gatt_conn == NULL) {
|
||||
LOG_ERR("[B]NoConnInfo[%u]", conn->handle);
|
||||
@@ -3261,6 +3371,12 @@ int bt_le_bluedroid_gattc_write_without_rsp(struct bt_conn *conn, uint16_t handl
|
||||
|
||||
LOG_DBG("[B]GattcWrCmd[%u][%u][%u]", conn->handle, handle, length);
|
||||
|
||||
/* Same BTU stack-overflow hazard as gattc_write */
|
||||
if (length > BTA_GATT_MAX_ATTR_LEN) {
|
||||
LOG_ERR("[B]GattcWrCmdValTooBig[%u]", length);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* Mirror sibling GATT ops: refuse to forward doomed writes after
|
||||
* disconnect cleared the gatt_conn slot (bt_conn->state may briefly
|
||||
* still look CONNECTED during the teardown race). */
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct bt_l2cap_chan;
|
||||
struct net_buf;
|
||||
|
||||
int bt_le_bluedroid_l2cap_chan_connect(uint16_t conn_handle);
|
||||
|
||||
int bt_le_bluedroid_l2cap_chan_disconnect(struct bt_l2cap_chan *chan);
|
||||
|
||||
int bt_le_bluedroid_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf);
|
||||
|
||||
void bt_le_bluedroid_l2cap_accept_result(uint16_t conn_handle, uint8_t l2cap_id,
|
||||
uint16_t chan_handle, uint16_t result);
|
||||
|
||||
int bt_le_bluedroid_l2cap_init(void);
|
||||
|
||||
void bt_le_bluedroid_l2cap_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -69,7 +69,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint16_t conn_handle;
|
||||
tBTM_STATUS status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
|
||||
@@ -89,7 +89,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
tx_sync.packet_seq_num = sys_get_le16(rsp_buf + 2);
|
||||
tx_sync.tx_time_stamp = sys_get_le32(rsp_buf + 4);
|
||||
tx_sync.time_offset = sys_get_le24(rsp_buf + 8) & 0xFFFFFF;
|
||||
assert(tx_sync.conn_handle == conn_handle);
|
||||
BT_LE_ASSERT(tx_sync.conn_handle == conn_handle);
|
||||
}
|
||||
#else /* USE_DIRECT_HCI */
|
||||
bt_le_host_lock();
|
||||
@@ -100,7 +100,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
LOG_ERR("[B]RdIsoTxSyncRspTimeout[0x%03x]", conn_handle);
|
||||
status = BTM_ERR_PROCESSING;
|
||||
} else {
|
||||
assert(tx_sync.conn_handle == conn_handle);
|
||||
BT_LE_ASSERT(tx_sync.conn_handle == conn_handle);
|
||||
status = tx_sync.status;
|
||||
}
|
||||
bt_le_host_unlock();
|
||||
@@ -134,7 +134,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint8_t cis_count;
|
||||
uint8_t cig_id;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
cig_id = buf->data[3];
|
||||
cis_count = buf->data[17];
|
||||
@@ -162,8 +162,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
for (uint8_t i = 0; i < rsp_cis_count; i++) {
|
||||
set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2);
|
||||
}
|
||||
assert(set_cig_params.cig_id == cig_id);
|
||||
assert(set_cig_params.cis_count == cis_count);
|
||||
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
|
||||
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
|
||||
}
|
||||
#else /* USE_DIRECT_HCI */
|
||||
struct ble_hci_le_cis_params *cis_params;
|
||||
@@ -184,7 +184,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
mtl_p_to_c = sys_get_le16(buf->data + 15);
|
||||
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)];
|
||||
@@ -213,8 +213,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
LOG_ERR("[B]SetCigParamsRspTimeout[%u]", cig_id);
|
||||
status = BTM_ERR_PROCESSING;
|
||||
} else {
|
||||
assert(set_cig_params.cig_id == cig_id);
|
||||
assert(set_cig_params.cis_count == cis_count);
|
||||
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
|
||||
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
|
||||
status = set_cig_params.status;
|
||||
if (status) {
|
||||
LOG_ERR("[B]SetCigParamsCtrlFail[%u][%02x]", cig_id, status);
|
||||
@@ -250,7 +250,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
uint8_t cis_count;
|
||||
uint8_t cig_id;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
cig_id = buf->data[3];
|
||||
cis_count = buf->data[17];
|
||||
@@ -278,8 +278,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
for (uint8_t i = 0; i < rsp_cis_count; i++) {
|
||||
set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2);
|
||||
}
|
||||
assert(set_cig_params.cig_id == cig_id);
|
||||
assert(set_cig_params.cis_count == cis_count);
|
||||
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
|
||||
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
|
||||
}
|
||||
#else /* USE_DIRECT_HCI */
|
||||
struct ble_hci_le_cis_params_test *cis_params;
|
||||
@@ -302,7 +302,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
framing = buf->data[16];
|
||||
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)];
|
||||
@@ -335,8 +335,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
LOG_ERR("[B]SetCigParamsTestRspTimeout[%u]", cig_id);
|
||||
status = BTM_ERR_PROCESSING;
|
||||
} else {
|
||||
assert(set_cig_params.cig_id == cig_id);
|
||||
assert(set_cig_params.cis_count == cis_count);
|
||||
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
|
||||
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
|
||||
status = set_cig_params.status;
|
||||
if (status) {
|
||||
LOG_ERR("[B]SetCigParamsTestCtrlFail[%u][%02x]", cig_id, status);
|
||||
@@ -377,7 +377,7 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp)
|
||||
cis_count = buf->data[3];
|
||||
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_cis_hdls));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_hdl = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_cis_hdls));
|
||||
@@ -629,7 +629,7 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint8_t big_handle;
|
||||
tBTM_STATUS status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
|
||||
@@ -661,7 +661,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
|
||||
uint16_t conn_handle;
|
||||
tBTM_STATUS status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
|
||||
@@ -721,7 +721,7 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs
|
||||
uint16_t conn_handle;
|
||||
tBTM_STATUS status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
|
||||
@@ -859,7 +859,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.status = 0x00;
|
||||
ev.handle = params->btm_cis_disconnectd_evt.cis_handle;
|
||||
@@ -876,7 +876,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.status = iso_hci_status(params->btm_cis_established_evt.status);
|
||||
ev.conn_handle = params->btm_cis_established_evt.conn_handle;
|
||||
@@ -907,7 +907,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.acl_handle = params->btm_cis_request_evt.acl_handle;
|
||||
ev.cis_handle = params->btm_cis_request_evt.cis_handle;
|
||||
@@ -926,7 +926,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_cmpl.num_bis * 2;
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.status = iso_hci_status(params->btm_big_cmpl.status);
|
||||
ev.big_handle = params->btm_big_cmpl.big_handle;
|
||||
@@ -957,7 +957,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.big_handle = params->btm_big_term.big_handle;
|
||||
ev.reason = params->btm_big_term.reason;
|
||||
@@ -974,7 +974,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_sync_estab.num_bis * 2;
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.status = iso_hci_status(params->btm_big_sync_estab.status);
|
||||
ev.big_handle = params->btm_big_sync_estab.big_handle;
|
||||
@@ -1003,7 +1003,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.big_handle = params->btm_big_sync_lost.big_handle;
|
||||
ev.reason = params->btm_big_sync_lost.reason;
|
||||
@@ -1020,7 +1020,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
|
||||
|
||||
qdata_len = 2 + 1 + sizeof(ev);
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
ev.sync_handle = params->btm_biginfo_report.sync_handle;
|
||||
ev.num_bis = params->btm_biginfo_report.num_bis;
|
||||
@@ -1275,7 +1275,7 @@ int bt_le_bluedroid_iso_init(void)
|
||||
err = iso_enable_cis();
|
||||
if (err) {
|
||||
/* Roll back local init so a retry doesn't trip k_sem_create's
|
||||
* assert(handle == NULL). Reverse order of init, skipping the
|
||||
* BT_LE_ASSERT(handle == NULL). Reverse order of init, skipping the
|
||||
* disable_cis step since enable never took effect. */
|
||||
#if CONFIG_BT_ISO_RX
|
||||
ble_host_register_rx_iso_data_cb(NULL);
|
||||
|
||||
@@ -83,7 +83,7 @@ void bt_le_nimble_gap_post_event(void *param)
|
||||
int err;
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
ev = param;
|
||||
@@ -117,7 +117,7 @@ void bt_le_nimble_gap_post_event(void *param)
|
||||
|
||||
if (qev->ext_scan_recv.data_len) {
|
||||
qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len);
|
||||
assert(qev->ext_scan_recv.data);
|
||||
BT_LE_ASSERT(qev->ext_scan_recv.data);
|
||||
|
||||
memcpy(qev->ext_scan_recv.data, ev->ext_disc.data, qev->ext_scan_recv.data_len);
|
||||
}
|
||||
@@ -172,7 +172,7 @@ void bt_le_nimble_gap_post_event(void *param)
|
||||
|
||||
if (qev->pa_sync_recv.data_len) {
|
||||
qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len);
|
||||
assert(qev->pa_sync_recv.data);
|
||||
BT_LE_ASSERT(qev->pa_sync_recv.data);
|
||||
|
||||
memcpy(qev->pa_sync_recv.data, ev->periodic_report.data, qev->pa_sync_recv.data_len);
|
||||
}
|
||||
@@ -184,7 +184,7 @@ void bt_le_nimble_gap_post_event(void *param)
|
||||
qev->acl_connect.status = ev->connect.status;
|
||||
if (qev->acl_connect.status == 0) {
|
||||
err = ble_gap_conn_find(ev->connect.conn_handle, &desc);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
qev->acl_connect.conn_handle = desc.conn_handle;
|
||||
qev->acl_connect.role = desc.role;
|
||||
@@ -206,7 +206,7 @@ void bt_le_nimble_gap_post_event(void *param)
|
||||
qev->security_change.status = ev->enc_change.status;
|
||||
if (qev->security_change.status == 0) {
|
||||
err = ble_gap_conn_find(ev->enc_change.conn_handle, &desc);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
qev->security_change.conn_handle = ev->enc_change.conn_handle;
|
||||
qev->security_change.role = desc.role;
|
||||
@@ -325,9 +325,15 @@ int bt_le_nimble_scan_stop(void)
|
||||
LOG_DBG("[N]ScanStop");
|
||||
|
||||
rc = ble_gap_disc_cancel();
|
||||
if (rc) {
|
||||
if (rc && rc != BLE_HS_EALREADY) {
|
||||
LOG_ERR("[N]ScanStopFail[%d]", rc);
|
||||
}
|
||||
|
||||
/* EALREADY (not scanning, e.g. after privacy preemption): treat as success
|
||||
* so bt_le_scan_stop clears a stale BT_DEV_SCANNING instead of locking out. */
|
||||
if (rc == BLE_HS_EALREADY) {
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
return nimble_err_to_errno(rc);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ void bt_le_nimble_gatt_post_event(void *param)
|
||||
}
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
switch (ev->type) {
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
@@ -79,7 +79,7 @@ void bt_le_nimble_gatt_post_event(void *param)
|
||||
qev->gattc_notify_rx.len = total_len;
|
||||
|
||||
qev->gattc_notify_rx.value = bt_le_ext_calloc(1, total_len);
|
||||
assert(qev->gattc_notify_rx.value);
|
||||
BT_LE_ASSERT(qev->gattc_notify_rx.value);
|
||||
|
||||
os_mbuf_copydata(ev->notify_rx.om, 0, total_len, qev->gattc_notify_rx.value);
|
||||
}
|
||||
@@ -129,7 +129,7 @@ int bt_le_nimble_gatt_post_disc_event(uint16_t conn_handle, ble_uuid16_t *uuid,
|
||||
}
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_DISCOVER_EVENT;
|
||||
|
||||
@@ -156,7 +156,7 @@ int bt_le_nimble_gatt_post_disc_cmpl_event(uint16_t conn_handle, uint8_t status)
|
||||
LOG_DBG("[N]GattcDiscCmplEvtHdlr[%u][%02x]", conn_handle, status);
|
||||
|
||||
qev = bt_le_ext_calloc(1, sizeof(*qev));
|
||||
assert(qev);
|
||||
BT_LE_ASSERT(qev);
|
||||
|
||||
qev->type = BT_LE_GATTC_DISC_CMPL_EVENT;
|
||||
|
||||
@@ -299,7 +299,7 @@ void bt_le_nimble_gatt_handle_event(uint8_t *data, size_t data_len)
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -317,16 +317,19 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_
|
||||
void *buf, uint16_t buf_len, uint16_t offset,
|
||||
const void *value, uint16_t value_len)
|
||||
{
|
||||
uint16_t len;
|
||||
ssize_t len;
|
||||
|
||||
if (buf_len == UINT16_MAX) {
|
||||
/* TODO: A better solution for using a variable for the cb pointer */
|
||||
struct bt_le_nimble_gatt_read_cb *cb = buf;
|
||||
assert(cb->read_cb);
|
||||
BT_LE_ASSERT(cb->read_cb);
|
||||
|
||||
len = cb->read_cb(cb->read_arg, offset, value, value_len);
|
||||
if (len < 0) {
|
||||
return len;
|
||||
}
|
||||
} else {
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
|
||||
LOG_DBG("[N]Hdl[%u]", attr->handle);
|
||||
|
||||
@@ -339,7 +342,7 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_
|
||||
memcpy(buf, (uint8_t *)value + offset, len);
|
||||
}
|
||||
|
||||
LOG_DBG("[N]GattsAttrRd[%u]", len);
|
||||
LOG_DBG("[N]GattsAttrRd[%u]", (unsigned)len);
|
||||
|
||||
return len;
|
||||
}
|
||||
@@ -375,7 +378,7 @@ static int gatts_notify(struct bt_conn *conn, struct bt_gatt_notify_params *para
|
||||
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
|
||||
struct bt_gatt_chrc *chrc;
|
||||
|
||||
assert(data.attr->user_data);
|
||||
BT_LE_ASSERT(data.attr->user_data);
|
||||
chrc = data.attr->user_data;
|
||||
|
||||
if ((chrc->properties & BT_GATT_CHRC_NOTIFY) == 0) {
|
||||
@@ -486,7 +489,7 @@ int bt_le_nimble_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover_pa
|
||||
return bt_le_nimble_gattc_db_disc_all_chrs(conn, params);
|
||||
|
||||
case BT_GATT_DISCOVER_DESCRIPTOR:
|
||||
assert(params->uuid);
|
||||
BT_LE_ASSERT(params->uuid);
|
||||
|
||||
/* Only descriptors can be filtered */
|
||||
if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 ||
|
||||
|
||||
@@ -157,7 +157,7 @@ static void gattc_db_del(struct gattc_db *adb)
|
||||
struct gattc_db_svc *asvc;
|
||||
sys_snode_t *node;
|
||||
|
||||
assert(adb);
|
||||
BT_LE_ASSERT(adb);
|
||||
|
||||
while ((node = sys_slist_get(&adb->svc_list)) != NULL) {
|
||||
asvc = SYS_SLIST_CONTAINER(node, asvc, node);
|
||||
@@ -182,7 +182,7 @@ static void gattc_db_svc_insert(struct gattc_db *adb, const struct ble_gatt_svc
|
||||
struct gattc_db_svc *asvc;
|
||||
|
||||
asvc = bt_le_ext_calloc(1, sizeof(*asvc));
|
||||
assert(asvc);
|
||||
BT_LE_ASSERT(asvc);
|
||||
|
||||
memcpy(&asvc->svc, svc, sizeof(asvc->svc));
|
||||
sys_slist_init(&asvc->inc_svc_list);
|
||||
@@ -198,7 +198,7 @@ static void gattc_db_inc_svc_insert(struct gattc_db_svc *asvc, const struct ble_
|
||||
struct gattc_db_inc_svc *ainc_svc;
|
||||
|
||||
ainc_svc = bt_le_ext_calloc(1, sizeof(*ainc_svc));
|
||||
assert(ainc_svc);
|
||||
BT_LE_ASSERT(ainc_svc);
|
||||
|
||||
memcpy(&ainc_svc->svc, inc_svc, sizeof(ainc_svc->svc));
|
||||
sys_slist_init(&ainc_svc->chrc_list);
|
||||
@@ -220,7 +220,7 @@ static void gattc_db_chrc_insert(sys_slist_t *chrc_list, const struct ble_gatt_c
|
||||
struct gattc_db_chrc *achrc;
|
||||
|
||||
achrc = bt_le_ext_calloc(1, sizeof(*achrc));
|
||||
assert(achrc);
|
||||
BT_LE_ASSERT(achrc);
|
||||
|
||||
memcpy(&achrc->chrc, chrc, sizeof(achrc->chrc));
|
||||
|
||||
@@ -238,7 +238,10 @@ static void gattc_db_dsc_cccd_store(sys_slist_t *chrc_list,
|
||||
/* LOG_DBG("[N]GattcDbDscCccdStore[%u]", chr_val_handle); */
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(chrc_list, achrc, node) {
|
||||
if (achrc->chrc.val_handle == dsc->handle - 1) {
|
||||
/* Match by the char value handle NimBLE reports this descriptor belongs
|
||||
* to, not dsc->handle-1: a descriptor between value and CCCD would break
|
||||
* the offset assumption and leave CCCD unstored. */
|
||||
if (achrc->chrc.val_handle == chr_val_handle) {
|
||||
if (achrc->cccd.handle) {
|
||||
LOG_WRN("[N]GattcDbCccAlreadyUpd[%u][%u]", chr_val_handle, achrc->cccd.handle);
|
||||
return;
|
||||
@@ -582,12 +585,12 @@ static int gattc_db_disc_all_svcs_cb_safe(uint16_t conn_handle,
|
||||
bt_le_host_lock();
|
||||
|
||||
adb = arg;
|
||||
assert(adb);
|
||||
assert(adb->conn_handle == conn_handle);
|
||||
BT_LE_ASSERT(adb);
|
||||
BT_LE_ASSERT(adb->conn_handle == conn_handle);
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
LOG_DBG("[N]GattcDbDiscAllSvcs[0x%04x][%u][%u]",
|
||||
svc->uuid.u16.value,
|
||||
@@ -628,7 +631,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
asvc = arg;
|
||||
assert(asvc);
|
||||
BT_LE_ASSERT(asvc);
|
||||
|
||||
/* LOG_DBG("[N]GattcDbFindIncSvcsCb[%u][%04x]", conn_handle, error->status); */
|
||||
|
||||
@@ -643,7 +646,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(svc);
|
||||
BT_LE_ASSERT(svc);
|
||||
|
||||
LOG_DBG("[N]GattcDbFindIncSvcs[0x%04x][%u][%u]",
|
||||
svc->uuid.u16.value,
|
||||
@@ -716,7 +719,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
ainc_svc = arg;
|
||||
assert(ainc_svc);
|
||||
BT_LE_ASSERT(ainc_svc);
|
||||
|
||||
/* LOG_DBG("[N]GattcDbDiscAllIncChrsCb[%u][%04x]", conn_handle, error->status); */
|
||||
|
||||
@@ -731,7 +734,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(chrc);
|
||||
BT_LE_ASSERT(chrc);
|
||||
|
||||
LOG_DBG("[N]GattcDbDiscAllIncChrs[%u][%u]", chrc->def_handle, chrc->val_handle);
|
||||
|
||||
@@ -774,7 +777,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
asvc = arg;
|
||||
assert(asvc);
|
||||
BT_LE_ASSERT(asvc);
|
||||
|
||||
/* LOG_DBG("[N]GattcDbDiscAllChrsCb[%u][%04x]", conn_handle, error->status); */
|
||||
|
||||
@@ -789,7 +792,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(chrc);
|
||||
BT_LE_ASSERT(chrc);
|
||||
|
||||
LOG_DBG("[N]GattcDbDiscAllChrs[%u][%u]", chrc->def_handle, chrc->val_handle);
|
||||
|
||||
@@ -833,7 +836,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
ainc_svc = arg;
|
||||
assert(ainc_svc);
|
||||
BT_LE_ASSERT(ainc_svc);
|
||||
|
||||
LOG_DBG("[N]GattcDbDiscAllIncDscsCb[%u][%04x][%u]",
|
||||
conn_handle, error->status, chr_val_handle);
|
||||
@@ -849,7 +852,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(dsc);
|
||||
BT_LE_ASSERT(dsc);
|
||||
|
||||
if (dsc->uuid.u.type == BLE_UUID_TYPE_16 &&
|
||||
dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) {
|
||||
@@ -896,7 +899,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
asvc = arg;
|
||||
assert(asvc);
|
||||
BT_LE_ASSERT(asvc);
|
||||
|
||||
/* LOG_DBG("[N]GattcDbDiscAllDscsCb[%u][%04x][%u]", */
|
||||
/* conn_handle, error->status, chr_val_handle); */
|
||||
@@ -912,7 +915,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(dsc);
|
||||
BT_LE_ASSERT(dsc);
|
||||
|
||||
if (dsc->uuid.u.type == BLE_UUID_TYPE_16 &&
|
||||
dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) {
|
||||
@@ -958,7 +961,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
achrc = arg;
|
||||
assert(achrc);
|
||||
BT_LE_ASSERT(achrc);
|
||||
|
||||
/* LOG_DBG("[N]GattcDbEnableNtfCb[%u][%04x]", conn_handle, error->status); */
|
||||
|
||||
@@ -973,7 +976,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
LOG_DBG("[N]GattcDbEnableNotify[%u]", attr->handle);
|
||||
break;
|
||||
|
||||
@@ -1135,13 +1138,18 @@ static uint8_t gattc_disc_chr_each(struct bt_conn *conn,
|
||||
chrc_uuid.val = achrc->chrc.uuid.u16.value;
|
||||
break;
|
||||
default:
|
||||
return BT_GATT_ITER_STOP;
|
||||
/* Skip unsupported UUID types (e.g. 128-bit custom) instead of aborting
|
||||
* the whole discovery; LE Audio only uses 16-bit characteristics. */
|
||||
LOG_WRN("[N]GattcDbChrcSkipUuid[%u][%u][%u]",
|
||||
conn->handle, achrc->chrc.uuid.u.type, achrc->chrc.val_handle);
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
chrc.uuid = &chrc_uuid.uuid;
|
||||
chrc.value_handle = achrc->chrc.val_handle;
|
||||
chrc.properties = achrc->chrc.properties;
|
||||
|
||||
attr.uuid = BT_UUID_GATT_CHRC; /* declaration attr UUID */
|
||||
attr.user_data = &chrc;
|
||||
attr.handle = achrc->chrc.def_handle;
|
||||
|
||||
@@ -1247,7 +1255,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event)
|
||||
goto end;
|
||||
}
|
||||
|
||||
assert(event->params);
|
||||
BT_LE_ASSERT(event->params);
|
||||
|
||||
switch (event->type) {
|
||||
case GATTC_DISC_TYPE_SVC_BY_UUID:
|
||||
@@ -1273,7 +1281,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event)
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1344,7 +1352,7 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn,
|
||||
*/
|
||||
|
||||
sub_params = params->sub_params;
|
||||
assert(sub_params);
|
||||
BT_LE_ASSERT(sub_params);
|
||||
|
||||
LOG_DBG("[N]GattcDbHdlDiscAllDscs[%u][%u]", conn->handle, sub_params->value_handle);
|
||||
|
||||
@@ -1394,6 +1402,12 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn,
|
||||
}
|
||||
|
||||
end:
|
||||
if (found == NULL && rc == 0) {
|
||||
/* Char missing, or char found with cccd.handle==0 — not success.
|
||||
* Returning 0 here made bt_gatt_subscribe() append a dead node. */
|
||||
rc = -ENODEV;
|
||||
}
|
||||
|
||||
params->func(conn, found, params);
|
||||
|
||||
return rc;
|
||||
|
||||
@@ -101,11 +101,11 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
read_params = arg;
|
||||
assert(read_params);
|
||||
assert(read_params->func);
|
||||
assert(read_params->handle_count == 0);
|
||||
assert(read_params->by_uuid.uuid);
|
||||
assert(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16);
|
||||
BT_LE_ASSERT(read_params);
|
||||
BT_LE_ASSERT(read_params->func);
|
||||
BT_LE_ASSERT(read_params->handle_count == 0);
|
||||
BT_LE_ASSERT(read_params->by_uuid.uuid);
|
||||
BT_LE_ASSERT(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16);
|
||||
|
||||
LOG_DBG("[N]GattcNrpRdByUuidCb[%u][%04x][%s]",
|
||||
conn_handle, error->status, bt_uuid_str(read_params->by_uuid.uuid));
|
||||
@@ -123,19 +123,19 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
|
||||
* events followed by EDONE, so the head must survive across events.
|
||||
* It is removed only on terminal status (EDONE / error) below. */
|
||||
nrp = gatt_nrp_find(conn->handle);
|
||||
assert(nrp);
|
||||
BT_LE_ASSERT(nrp);
|
||||
node = sys_slist_peek_head(&nrp->list);
|
||||
assert(node);
|
||||
BT_LE_ASSERT(node);
|
||||
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
|
||||
assert(nrp_node->type == GATTC_NRP_READ_BY_UUID);
|
||||
assert(nrp_node->read_by_uuid.params == read_params);
|
||||
BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_BY_UUID);
|
||||
BT_LE_ASSERT(nrp_node->read_by_uuid.params == read_params);
|
||||
|
||||
iter_stopped = nrp_node->read_by_uuid.iter_stopped;
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
assert(attr->om);
|
||||
BT_LE_ASSERT(attr);
|
||||
BT_LE_ASSERT(attr->om);
|
||||
|
||||
LOG_DBG("[N]GattcNrpHdl[%u][%u][%u]Len[%u]",
|
||||
attr->handle, read_params->by_uuid.start_handle,
|
||||
@@ -198,10 +198,10 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
read_params = arg;
|
||||
assert(read_params);
|
||||
assert(read_params->func);
|
||||
assert(read_params->handle_count == 1);
|
||||
assert(read_params->single.offset != 0);
|
||||
BT_LE_ASSERT(read_params);
|
||||
BT_LE_ASSERT(read_params->func);
|
||||
BT_LE_ASSERT(read_params->handle_count == 1);
|
||||
BT_LE_ASSERT(read_params->single.offset != 0);
|
||||
|
||||
LOG_DBG("[N]GattcNrpRdLongCb[%u][%04x]", conn_handle, error->status);
|
||||
|
||||
@@ -217,19 +217,19 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
|
||||
/* Peek (don't pop) — per-fragment events may precede EDONE; head is
|
||||
* removed only on terminal status below. */
|
||||
nrp = gatt_nrp_find(conn->handle);
|
||||
assert(nrp);
|
||||
BT_LE_ASSERT(nrp);
|
||||
node = sys_slist_peek_head(&nrp->list);
|
||||
assert(node);
|
||||
BT_LE_ASSERT(node);
|
||||
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
|
||||
assert(nrp_node->type == GATTC_NRP_READ_LONG);
|
||||
assert(nrp_node->read_long.params == read_params);
|
||||
BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_LONG);
|
||||
BT_LE_ASSERT(nrp_node->read_long.params == read_params);
|
||||
|
||||
iter_stopped = nrp_node->read_long.iter_stopped;
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
assert(attr->om);
|
||||
BT_LE_ASSERT(attr);
|
||||
BT_LE_ASSERT(attr->om);
|
||||
|
||||
LOG_DBG("[N]GattcNrpHdl[%u][%u]Offset[%u]Len[%u]",
|
||||
attr->handle, read_params->single.handle, attr->offset, attr->om->om_len);
|
||||
@@ -289,9 +289,9 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
|
||||
original = nrp_node->read_single.params;
|
||||
func = params_copy->func;
|
||||
|
||||
assert(func);
|
||||
assert(params_copy->handle_count == 1);
|
||||
assert(params_copy->single.offset == 0);
|
||||
BT_LE_ASSERT(func);
|
||||
BT_LE_ASSERT(params_copy->handle_count == 1);
|
||||
BT_LE_ASSERT(params_copy->single.offset == 0);
|
||||
|
||||
LOG_DBG("[N]GattcNrpRdSingleCb[%u][%04x]", conn_handle, error->status);
|
||||
|
||||
@@ -309,8 +309,8 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
assert(attr->om);
|
||||
BT_LE_ASSERT(attr);
|
||||
BT_LE_ASSERT(attr->om);
|
||||
|
||||
LOG_DBG("[N]GattcNrpHdl[%u][%u]Len[%u]",
|
||||
attr->handle, original->single.handle, attr->om->om_len);
|
||||
@@ -405,8 +405,8 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
write_params = arg;
|
||||
assert(write_params);
|
||||
assert(write_params->func);
|
||||
BT_LE_ASSERT(write_params);
|
||||
BT_LE_ASSERT(write_params->func);
|
||||
|
||||
LOG_DBG("[N]GattcNrpWrCb[%u][%04x]", conn_handle, error->status);
|
||||
|
||||
@@ -424,7 +424,7 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
LOG_DBG("[N]GattcNrpHdl[%u][%u]", attr->handle, write_params->handle);
|
||||
|
||||
write_params->func(conn, 0, write_params);
|
||||
@@ -483,7 +483,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
|
||||
int rc = 0;
|
||||
|
||||
sub_params = arg;
|
||||
assert(sub_params);
|
||||
BT_LE_ASSERT(sub_params);
|
||||
|
||||
LOG_DBG("[N]GattcNrpSubCb[%u][%04x]", conn_handle, error->status);
|
||||
|
||||
@@ -501,7 +501,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
|
||||
|
||||
switch (error->status) {
|
||||
case 0:
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
LOG_DBG("[N]GattcNrpHdl[%u]", attr->handle);
|
||||
|
||||
#if 0
|
||||
@@ -561,7 +561,7 @@ static int gattc_nrp_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_pa
|
||||
|
||||
void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
|
||||
int16_t attr_handle,
|
||||
uint8_t status)
|
||||
int status)
|
||||
{
|
||||
struct bt_gatt_indicate_params params = {0};
|
||||
struct bt_gatt_attr attr = {0};
|
||||
@@ -604,13 +604,13 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para
|
||||
struct os_mbuf *om = NULL;
|
||||
int rc;
|
||||
|
||||
assert(conn && params);
|
||||
assert(params->attr);
|
||||
BT_LE_ASSERT(conn && params);
|
||||
BT_LE_ASSERT(params->attr);
|
||||
/* 0-length indications are valid (spec); ble_hs_mbuf_from_flat accepts
|
||||
* (NULL, 0). But (NULL, len>0) would manifest as a misleading -ENOMEM,
|
||||
* so catch the programmer error here as a defense-in-depth guard.
|
||||
*/
|
||||
assert(params->len == 0 || params->data != NULL);
|
||||
BT_LE_ASSERT(params->len == 0 || params->data != NULL);
|
||||
|
||||
data.attr = params->attr;
|
||||
data.handle = bt_gatt_attr_get_handle(data.attr);
|
||||
@@ -635,7 +635,7 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para
|
||||
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
|
||||
struct bt_gatt_chrc *chrc;
|
||||
|
||||
assert(data.attr->user_data);
|
||||
BT_LE_ASSERT(data.attr->user_data);
|
||||
chrc = data.attr->user_data;
|
||||
|
||||
if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) {
|
||||
@@ -694,7 +694,7 @@ static void gatt_nrp_del(struct gatt_nrp *nrp)
|
||||
struct gatt_nrp_node *nrp_head;
|
||||
sys_snode_t *node;
|
||||
|
||||
assert(nrp);
|
||||
BT_LE_ASSERT(nrp);
|
||||
|
||||
while (1) {
|
||||
node = sys_slist_get(&nrp->list);
|
||||
@@ -735,7 +735,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
|
||||
}
|
||||
|
||||
nrp_node = bt_le_ext_calloc(1, sizeof(*nrp_node));
|
||||
assert(nrp_node);
|
||||
BT_LE_ASSERT(nrp_node);
|
||||
|
||||
nrp_node->type = type;
|
||||
|
||||
@@ -755,7 +755,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
|
||||
nrp_node->write_req.params = wp;
|
||||
nrp_node->write_req.data_copy = NULL;
|
||||
if (wp->length > 0) {
|
||||
assert(wp->data);
|
||||
BT_LE_ASSERT(wp->data);
|
||||
nrp_node->write_req.data_copy = bt_le_ext_malloc(wp->length);
|
||||
if (nrp_node->write_req.data_copy == NULL) {
|
||||
LOG_ERR("[N]GattNrpWrDataAllocFail[%u]", wp->length);
|
||||
@@ -775,7 +775,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
|
||||
nrp_node->indicate.params_copy = *ip;
|
||||
nrp_node->indicate.data_copy = NULL;
|
||||
if (ip->len > 0) {
|
||||
assert(ip->data);
|
||||
BT_LE_ASSERT(ip->data);
|
||||
nrp_node->indicate.data_copy = bt_le_ext_malloc(ip->len);
|
||||
if (nrp_node->indicate.data_copy == NULL) {
|
||||
LOG_ERR("[N]GattNrpIndDataAllocFail[%u]", ip->len);
|
||||
@@ -789,7 +789,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
}
|
||||
|
||||
if (sys_slist_is_empty(&nrp->list)) {
|
||||
@@ -889,31 +889,31 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
|
||||
/* Fetch and remove the first node from list */
|
||||
node = sys_slist_get(&nrp->list);
|
||||
assert(node);
|
||||
BT_LE_ASSERT(node);
|
||||
|
||||
nrp_head = CONTAINER_OF(node, struct gatt_nrp_node, node);
|
||||
assert(nrp_head->type == type);
|
||||
BT_LE_ASSERT(nrp_head->type == type);
|
||||
|
||||
switch (type) {
|
||||
case GATTC_NRP_READ_BY_UUID:
|
||||
assert(nrp_head->read_by_uuid.params == params);
|
||||
BT_LE_ASSERT(nrp_head->read_by_uuid.params == params);
|
||||
break;
|
||||
case GATTC_NRP_READ_LONG:
|
||||
assert(nrp_head->read_long.params == params);
|
||||
BT_LE_ASSERT(nrp_head->read_long.params == params);
|
||||
break;
|
||||
case GATTC_NRP_READ_SINGLE:
|
||||
assert(nrp_head->read_single.params == params);
|
||||
BT_LE_ASSERT(nrp_head->read_single.params == params);
|
||||
break;
|
||||
case GATTC_NRP_WRITE_REQ:
|
||||
assert(nrp_head->write_req.params == params);
|
||||
BT_LE_ASSERT(nrp_head->write_req.params == params);
|
||||
free(nrp_head->write_req.data_copy);
|
||||
nrp_head->write_req.data_copy = NULL;
|
||||
break;
|
||||
case GATTC_NRP_SUBSCRIBE:
|
||||
assert(nrp_head->subscribe.params == params);
|
||||
BT_LE_ASSERT(nrp_head->subscribe.params == params);
|
||||
break;
|
||||
case GATTS_NRP_INDICATE:
|
||||
assert(nrp_head->indicate.params);
|
||||
BT_LE_ASSERT(nrp_head->indicate.params);
|
||||
|
||||
/* params is the deep copy taken at insert time; cb sees the copy's
|
||||
* address, not the caller's original (which may have been reused).
|
||||
@@ -937,7 +937,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
nrp_head->indicate.data_copy = NULL;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
}
|
||||
|
||||
LOG_DBG("[N]GattNrpFree[%p]", nrp_head);
|
||||
@@ -958,7 +958,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
if (rc) {
|
||||
LOG_ERR("[N]GattcNrpRdByUuidFail[%d]", rc);
|
||||
|
||||
assert(nrp_head->read_by_uuid.params->func);
|
||||
BT_LE_ASSERT(nrp_head->read_by_uuid.params->func);
|
||||
nrp_head->read_by_uuid.params->func(conn, rc, nrp_head->read_by_uuid.params, NULL, 0);
|
||||
}
|
||||
} else if (nrp_head->type == GATTC_NRP_READ_LONG) {
|
||||
@@ -966,7 +966,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
if (rc) {
|
||||
LOG_ERR("[N]GattcNrpRdLongFail[%d]", rc);
|
||||
|
||||
assert(nrp_head->read_long.params->func);
|
||||
BT_LE_ASSERT(nrp_head->read_long.params->func);
|
||||
nrp_head->read_long.params->func(conn, rc, nrp_head->read_long.params, NULL, 0);
|
||||
}
|
||||
} else if (nrp_head->type == GATTC_NRP_READ_SINGLE) {
|
||||
@@ -974,7 +974,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
if (rc) {
|
||||
LOG_ERR("[N]GattcNrpRdSingleFail[%d]", rc);
|
||||
|
||||
assert(nrp_head->read_single.params_copy.func);
|
||||
BT_LE_ASSERT(nrp_head->read_single.params_copy.func);
|
||||
nrp_head->read_single.params_copy.func(conn, rc,
|
||||
nrp_head->read_single.params, NULL, 0);
|
||||
}
|
||||
@@ -985,7 +985,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
if (rc) {
|
||||
LOG_ERR("[N]GattcNrpWrFail[%d]", rc);
|
||||
|
||||
assert(nrp_head->write_req.params->func);
|
||||
BT_LE_ASSERT(nrp_head->write_req.params->func);
|
||||
nrp_head->write_req.params->func(conn, rc, nrp_head->write_req.params);
|
||||
}
|
||||
} else if (nrp_head->type == GATTC_NRP_SUBSCRIBE) {
|
||||
@@ -998,7 +998,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
|
||||
if (rc) {
|
||||
LOG_ERR("[N]GattsNrpIndFail[%d]", rc);
|
||||
|
||||
assert(nrp_head->indicate.params->func);
|
||||
BT_LE_ASSERT(nrp_head->indicate.params->func);
|
||||
nrp_head->indicate.params->func(conn, nrp_head->indicate.params, rc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
#define HOST_NIMBLE_GATT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
|
||||
#include "host/ble_uuid.h"
|
||||
#include "host/ble_gatt.h"
|
||||
|
||||
#include "utils/assert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -35,7 +36,7 @@ static inline uint8_t BT_LE_NIMBLE_GATT_UUID_TO_Z(uint8_t type)
|
||||
} else if (type == BLE_UUID_TYPE_128) {
|
||||
return BT_UUID_TYPE_128;
|
||||
} else {
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
return BT_UUID_TYPE_16;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +114,7 @@ enum {
|
||||
|
||||
void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
|
||||
int16_t attr_handle,
|
||||
uint8_t status);
|
||||
int status);
|
||||
|
||||
int bt_le_nimble_gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params);
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint16_t conn_handle;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 5);
|
||||
BT_LE_ASSERT(buf->len >= 5);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
|
||||
@@ -87,9 +87,9 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint8_t *rp;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 18);
|
||||
BT_LE_ASSERT(buf->len >= 18);
|
||||
|
||||
cig_id = buf->data[3];
|
||||
sdu_interval_c_to_p = sys_get_le24(buf->data + 4);
|
||||
@@ -101,17 +101,17 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
|
||||
mtl_p_to_c = sys_get_le16(buf->data + 15);
|
||||
cis_count = buf->data[17];
|
||||
|
||||
assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params));
|
||||
BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params));
|
||||
|
||||
/* The cis_count can be 0 */
|
||||
if (cis_count) {
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
}
|
||||
|
||||
rp_len = sizeof(struct ble_hci_le_set_cig_params_rp) + cis_count * 2;
|
||||
rp = bt_le_ext_calloc(1, rp_len);
|
||||
assert(rp);
|
||||
BT_LE_ASSERT(rp);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)];
|
||||
@@ -180,9 +180,9 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
uint8_t *rp;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 18);
|
||||
BT_LE_ASSERT(buf->len >= 18);
|
||||
|
||||
cig_id = buf->data[3];
|
||||
sdu_interval_c_to_p = sys_get_le24(buf->data + 4);
|
||||
@@ -195,17 +195,17 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
|
||||
framing = buf->data[16];
|
||||
cis_count = buf->data[17];
|
||||
|
||||
assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test));
|
||||
BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test));
|
||||
|
||||
/* The cis_count can be 0 */
|
||||
if (cis_count) {
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
}
|
||||
|
||||
rp_len = sizeof(struct ble_hci_le_set_cig_params_test_rp) + cis_count * 2;
|
||||
rp = bt_le_ext_calloc(1, rp_len);
|
||||
assert(rp);
|
||||
BT_LE_ASSERT(rp);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)];
|
||||
@@ -269,15 +269,15 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 4);
|
||||
BT_LE_ASSERT(buf->len >= 4);
|
||||
|
||||
/* The cis_count shall be at least 1 */
|
||||
cis_count = buf->data[3];
|
||||
|
||||
assert(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params));
|
||||
BT_LE_ASSERT(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params));
|
||||
|
||||
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_create_cis_params));
|
||||
assert(cis_params);
|
||||
BT_LE_ASSERT(cis_params);
|
||||
|
||||
for (size_t i = 0; i < cis_count; i++) {
|
||||
cis_params[i].cis_handle = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_le_create_cis_params));
|
||||
@@ -306,7 +306,7 @@ static int hci_cmd_remove_cig(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 4);
|
||||
BT_LE_ASSERT(buf->len >= 4);
|
||||
|
||||
cig_id = buf->data[3];
|
||||
|
||||
@@ -331,7 +331,7 @@ static int hci_cmd_accept_cis_req(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 5);
|
||||
BT_LE_ASSERT(buf->len >= 5);
|
||||
|
||||
cis_handle = sys_get_le16(buf->data + 3);
|
||||
|
||||
@@ -357,7 +357,7 @@ static int hci_cmd_reject_cis_req(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 6);
|
||||
BT_LE_ASSERT(buf->len >= 6);
|
||||
|
||||
cis_handle = sys_get_le16(buf->data + 3);
|
||||
reason = buf->data[5];
|
||||
@@ -394,7 +394,7 @@ static int hci_cmd_create_big(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 34);
|
||||
BT_LE_ASSERT(buf->len >= 34);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
adv_handle = buf->data[4];
|
||||
@@ -456,7 +456,7 @@ static int hci_cmd_create_big_test(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 39);
|
||||
BT_LE_ASSERT(buf->len >= 39);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
adv_handle = buf->data[4];
|
||||
@@ -512,7 +512,7 @@ static int hci_cmd_terminate_big(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 5);
|
||||
BT_LE_ASSERT(buf->len >= 5);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
reason = buf->data[4];
|
||||
@@ -545,7 +545,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
|
||||
ARG_UNUSED(rsp);
|
||||
|
||||
assert(buf->len >= 27);
|
||||
BT_LE_ASSERT(buf->len >= 27);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
sync_handle = sys_get_le16(buf->data + 4);
|
||||
@@ -556,7 +556,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
num_bis = buf->data[26];
|
||||
bis = buf->data + 27;
|
||||
|
||||
assert(buf->len >= 27 + num_bis);
|
||||
BT_LE_ASSERT(buf->len >= 27 + num_bis);
|
||||
|
||||
ble_hs_lock();
|
||||
|
||||
@@ -584,9 +584,9 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp)
|
||||
uint8_t big_handle;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 4);
|
||||
BT_LE_ASSERT(buf->len >= 4);
|
||||
|
||||
big_handle = buf->data[3];
|
||||
|
||||
@@ -625,9 +625,9 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
|
||||
uint8_t *codec_cfg;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 16);
|
||||
BT_LE_ASSERT(buf->len >= 16);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
data_path_direction = buf->data[5];
|
||||
@@ -639,7 +639,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
|
||||
codec_cfg_len = buf->data[15];
|
||||
codec_cfg = buf->data + 16;
|
||||
|
||||
assert(buf->len >= 16 + codec_cfg_len);
|
||||
BT_LE_ASSERT(buf->len >= 16 + codec_cfg_len);
|
||||
|
||||
ble_hs_lock();
|
||||
|
||||
@@ -677,9 +677,9 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs
|
||||
uint16_t conn_handle;
|
||||
int status;
|
||||
|
||||
assert(rsp);
|
||||
BT_LE_ASSERT(rsp);
|
||||
|
||||
assert(buf->len >= 6);
|
||||
BT_LE_ASSERT(buf->len >= 6);
|
||||
|
||||
conn_handle = sys_get_le16(buf->data + 3);
|
||||
data_path_direction = buf->data[5];
|
||||
@@ -793,7 +793,7 @@ static void iso_evt_rx(uint8_t event, const void *data,
|
||||
qdata_len = len + 2;
|
||||
|
||||
qdata = bt_le_ext_calloc(1, qdata_len);
|
||||
assert(qdata);
|
||||
BT_LE_ASSERT(qdata);
|
||||
|
||||
qdata[0] = le_meta;
|
||||
qdata[1] = event;
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
/*
|
||||
* 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 <assert.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 "../../../nimble/host/src/ble_l2cap_priv.h"
|
||||
|
||||
#include "common/host.h"
|
||||
|
||||
#include "nimble/hs_error.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ISO_N2CAP, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
|
||||
_Static_assert(CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM && "At least one L2CAP coc shall be supported");
|
||||
|
||||
#define OTS_L2CAP_BUF_COUNT (3 * CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM)
|
||||
#define OTS_L2CAP_MEM_SIZE OS_MEMPOOL_SIZE(OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2)
|
||||
|
||||
static BT_ISO_EXT_RAM_BSS_ATTR os_membuf_t ots_mem[OTS_L2CAP_MEM_SIZE];
|
||||
static BT_ISO_EXT_RAM_BSS_ATTR struct os_mempool ots_mbuf_mempool;
|
||||
static BT_ISO_EXT_RAM_BSS_ATTR struct os_mbuf_pool ots_mbuf_pool;
|
||||
|
||||
static BT_ISO_EXT_RAM_BSS_ATTR struct ble_l2cap_chan *ots_chan;
|
||||
|
||||
static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan)
|
||||
{
|
||||
struct os_mbuf *sdu_rx;
|
||||
int rc;
|
||||
|
||||
LOG_DBG("[N]L2capOtsRecvReady");
|
||||
|
||||
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
|
||||
if (sdu_rx == NULL) {
|
||||
LOG_ERR("[N]L2capNoBufForL2capRecv");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = ble_l2cap_recv_ready(chan, sdu_rx);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capRecvFail[%d]", rc);
|
||||
|
||||
os_mbuf_free_chain(sdu_rx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
|
||||
{
|
||||
struct ble_l2cap_chan_info chan_info;
|
||||
uint16_t result = 0;
|
||||
size_t sdu_len;
|
||||
uint8_t *sdu;
|
||||
int err;
|
||||
|
||||
LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type);
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_L2CAP_EVENT_COC_CONNECTED:
|
||||
if (event->connect.status) {
|
||||
LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ots_chan) {
|
||||
LOG_ERR("[N]L2capCocChanExist");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ots_chan = event->connect.chan;
|
||||
|
||||
if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) {
|
||||
LOG_ERR("[N]L2capCocGetChanInfoFail");
|
||||
/* Roll back the latch so the next COC_CONNECTED isn't refused
|
||||
* by the if (ots_chan) guard above. */
|
||||
ots_chan = NULL;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
LOG_INF("[N]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]",
|
||||
event->connect.conn_handle, chan_info.scid, chan_info.dcid,
|
||||
chan_info.psm, chan_info.our_l2cap_mtu, chan_info.peer_l2cap_mtu,
|
||||
chan_info.our_coc_mtu, chan_info.peer_coc_mtu);
|
||||
|
||||
bt_le_l2cap_connected(event->connect.conn_handle, chan_info.psm,
|
||||
chan_info.dcid, chan_info.peer_coc_mtu,
|
||||
chan_info.scid, chan_info.our_coc_mtu);
|
||||
return 0;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
|
||||
if (ots_chan != event->disconnect.chan) {
|
||||
LOG_ERR("[N]L2capDisconnectInvCocChan");
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_INF("[N]L2capCocDisconnect[%u][%04x]",
|
||||
event->disconnect.conn_handle, event->disconnect.chan->psm);
|
||||
|
||||
bt_le_l2cap_disconnected(event->disconnect.conn_handle, event->disconnect.chan->psm);
|
||||
|
||||
ots_chan = NULL;
|
||||
return 0;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_ACCEPT:
|
||||
/* Don't reject on peer_sdu_size: LE CoC allows asymmetric MTUs. It's the peer's
|
||||
* RX MTU (our TX ceiling) — harmless; our RX is bounded by ble_l2cap_create_server.
|
||||
* (PTS uses 1024 > our 256; rejecting broke SCP.) */
|
||||
|
||||
LOG_DBG("[N]L2capCocAccept[%u][%04x][%04x][%u][%u][%u][%u]",
|
||||
event->accept.conn_handle, event->accept.chan->psm,
|
||||
event->accept.chan->dcid, event->accept.chan->coc_tx.mtu,
|
||||
event->accept.chan->peer_coc_mps, event->accept.peer_sdu_size,
|
||||
event->accept.chan->coc_tx.credits);
|
||||
|
||||
err = bt_le_l2cap_accept(event->accept.conn_handle,
|
||||
event->accept.chan->psm,
|
||||
event->accept.chan->dcid,
|
||||
event->accept.chan->coc_tx.mtu,
|
||||
event->accept.chan->peer_coc_mps,
|
||||
event->accept.chan->coc_tx.credits,
|
||||
&result);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ARG_UNUSED(result);
|
||||
|
||||
return ots_l2cap_recv_ready(event->accept.chan);
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
|
||||
if (ots_chan != event->receive.chan) {
|
||||
LOG_ERR("[N]L2capRecvOnInvCocChan");
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(event->receive.sdu_rx);
|
||||
|
||||
LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]",
|
||||
event->receive.conn_handle, event->receive.chan->psm,
|
||||
event->receive.sdu_rx->om_len);
|
||||
|
||||
sdu_len = OS_MBUF_PKTLEN(event->receive.sdu_rx);
|
||||
|
||||
sdu = bt_le_ext_calloc(1, sdu_len);
|
||||
assert(sdu);
|
||||
|
||||
err = os_mbuf_copydata(event->receive.sdu_rx, 0, sdu_len, sdu);
|
||||
assert(err == 0);
|
||||
|
||||
bt_le_l2cap_received(event->receive.conn_handle,
|
||||
event->receive.chan->psm,
|
||||
sdu, sdu_len);
|
||||
|
||||
os_mbuf_free_chain(event->receive.sdu_rx);
|
||||
free(sdu);
|
||||
|
||||
return ots_l2cap_recv_ready(event->receive.chan);
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED:
|
||||
if (ots_chan != event->tx_unstalled.chan) {
|
||||
LOG_ERR("[N]L2capTxUnstalledOnInvCocChan");
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_WRN("[N]L2capCocTxUnstalled[%u][%d]",
|
||||
event->tx_unstalled.conn_handle, event->tx_unstalled.status);
|
||||
|
||||
/* TODO: transmit the remaining data */
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle)
|
||||
{
|
||||
struct os_mbuf *sdu_rx;
|
||||
int rc;
|
||||
|
||||
if (ots_chan) {
|
||||
LOG_WRN("[N]L2capOtsChanExist");
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
|
||||
if (sdu_rx == NULL) {
|
||||
LOG_ERR("[N]L2capNoBufForConnect");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = ble_l2cap_connect(conn_handle, L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU,
|
||||
sdu_rx, ots_l2cap_event_cb, NULL);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capConnectFail[%d]", rc);
|
||||
|
||||
os_mbuf_free_chain(sdu_rx);
|
||||
return nimble_err_to_errno(rc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (ots_chan == NULL) {
|
||||
LOG_WRN("[N]L2capNoOtsChan");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
|
||||
LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]",
|
||||
ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = ble_l2cap_disconnect(ots_chan);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capDisconnectFail[%d]", rc);
|
||||
return nimble_err_to_errno(rc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
struct os_mbuf *sdu_tx;
|
||||
int rc;
|
||||
|
||||
if (ots_chan == NULL) {
|
||||
LOG_WRN("[N]L2capNoOtsChan");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
|
||||
LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]",
|
||||
ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sdu_tx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
|
||||
if (sdu_tx == NULL) {
|
||||
LOG_ERR("[N]L2capNoBufForSend");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = os_mbuf_append(sdu_tx, buf->data, buf->len);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capAppendBufFail[%d]", rc);
|
||||
os_mbuf_free_chain(sdu_tx);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rc = ble_l2cap_send(ots_chan, sdu_tx);
|
||||
if (rc) {
|
||||
if (rc == BLE_HS_ESTALLED) {
|
||||
/* sdu is queued in tx->sdus[0]; NimBLE will continue on
|
||||
* BLE_L2CAP_EVENT_COC_TX_UNSTALLED. Do NOT free here.
|
||||
*/
|
||||
LOG_WRN("[N]L2capMoreCreditsForSend");
|
||||
} else if (rc == BLE_HS_EBADDATA || rc == BLE_HS_EBUSY) {
|
||||
/* sdu was rejected before being queued; caller still owns it. */
|
||||
LOG_ERR("[N]L2capSendFail[%d]", rc);
|
||||
os_mbuf_free_chain(sdu_tx);
|
||||
} else {
|
||||
/* Internal error inside continue_tx; NimBLE already freed sdu. */
|
||||
LOG_ERR("[N]L2capSendInternalFail[%d]", rc);
|
||||
}
|
||||
|
||||
return nimble_err_to_errno(rc);
|
||||
}
|
||||
|
||||
/* Payload copied into sdu_tx; consume buf here (no async TX-done to unref it),
|
||||
* else the 1-buffer ot_chan_tx_pool leaks. Failure paths leave buf to caller. */
|
||||
net_buf_unref(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_le_nimble_l2cap_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = os_mempool_init(&ots_mbuf_mempool, OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2, ots_mem, "ots_pool");
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capInitOtsMempoolFail[%d]", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = os_mbuf_pool_init(&ots_mbuf_pool, &ots_mbuf_mempool, L2CAP_LE_OTS_MTU, OTS_L2CAP_BUF_COUNT);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capInitOtsMbufPoolFail[%d]", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_OTS
|
||||
rc = ble_l2cap_create_server(L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, ots_l2cap_event_cb, NULL);
|
||||
if (rc) {
|
||||
LOG_ERR("[N]L2capCreateL2capSrvFail[%d]", rc);
|
||||
return rc;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_le_nimble_l2cap_deinit(void)
|
||||
{
|
||||
LOG_DBG("[N]L2capDeinit");
|
||||
|
||||
/* TODO: free the ots_mbuf_pool and ots_mbuf_mempool */
|
||||
|
||||
#if CONFIG_BT_OTS
|
||||
/* TODO: destroy the server */
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
}
|
||||
@@ -175,7 +175,7 @@ static void handle_pa_sync_event_safe(struct bt_le_gap_app_param *param)
|
||||
}
|
||||
|
||||
err = bt_le_per_adv_sync_establish_listener(event.pa_sync.sync_handle);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
@@ -203,6 +203,29 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param)
|
||||
|
||||
bt_le_host_lock();
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* See handle_security_change_event_safe: the producer runs on BTU and cannot
|
||||
* read gatt_conns[], so it sends src_addr and the handle is resolved here.
|
||||
* HCI identifies the PAST sender by handle and gives no address for it, so
|
||||
* btu_hcif recovers the BDA from the LCB without its type - match on value. */
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0,
|
||||
param->pa_sync_past.src_addr.val,
|
||||
true);
|
||||
if (gatt_conn == NULL) {
|
||||
/* ACL disconnected between PAST enqueue and host processing (scan.c
|
||||
* names this race). Keep the sync connection-less, like the non-PAST
|
||||
* path and NimBLE — synced cb fires with conn=NULL. */
|
||||
LOG_WRN("GapPastUnknownSrc");
|
||||
event.pa_sync_past.conn_handle = BT_CONN_HANDLE_INVALID;
|
||||
} else {
|
||||
event.pa_sync_past.conn_handle = gatt_conn->conn_handle;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
if (event.pa_sync_past.status) {
|
||||
goto end;
|
||||
}
|
||||
@@ -220,7 +243,7 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param)
|
||||
}
|
||||
|
||||
err = bt_le_per_adv_sync_establish_listener(event.pa_sync_past.sync_handle);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
@@ -246,7 +269,7 @@ static void handle_pa_sync_lost_event_safe(struct bt_le_gap_app_param *param)
|
||||
}
|
||||
|
||||
err = bt_le_per_adv_sync_delete(event.pa_sync_lost.sync_handle);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
@@ -321,7 +344,7 @@ static void handle_acl_connect_event_safe(struct bt_le_gap_app_param *param)
|
||||
}
|
||||
|
||||
err = bt_le_acl_conn_connected_listener(event.acl_connect.conn_handle);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
@@ -348,7 +371,7 @@ static void handle_acl_disconnect_event_safe(struct bt_le_gap_app_param *param)
|
||||
}
|
||||
|
||||
err = bt_le_acl_conn_delete(event.acl_disconnect.conn_handle);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
end:
|
||||
bt_le_host_unlock();
|
||||
@@ -378,6 +401,27 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
|
||||
|
||||
bt_le_host_lock();
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* Bluedroid's AUTH_CMPL arrives on BTC, which carries no conn_handle and may
|
||||
* not read gatt_conns[] (iso_task mutates it under this lock). So the producer
|
||||
* sends dst only and the identity is resolved here instead. NimBLE gets both
|
||||
* from its own event and fills them at post time. */
|
||||
{
|
||||
struct gatt_conn *gatt_conn;
|
||||
|
||||
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event.security_change.dst.type,
|
||||
event.security_change.dst.val,
|
||||
false);
|
||||
if (gatt_conn == NULL) {
|
||||
LOG_ERR("GapSecChgUnknownDev");
|
||||
goto end;
|
||||
}
|
||||
|
||||
event.security_change.conn_handle = gatt_conn->conn_handle;
|
||||
event.security_change.role = gatt_conn->role;
|
||||
}
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
if (event.security_change.status) {
|
||||
goto end;
|
||||
}
|
||||
@@ -393,11 +437,16 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
|
||||
if (err) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Created already encrypted — mirror update()'s *encrypted out-param. */
|
||||
if (event.security_change.sec_level > BT_SECURITY_L1) {
|
||||
encrypted = true;
|
||||
}
|
||||
} else {
|
||||
err = bt_le_acl_conn_update(event.security_change.conn_handle,
|
||||
event.security_change.sec_level,
|
||||
&encrypted);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
}
|
||||
|
||||
/* Point conn->le.keys at the bonded LTK the adapter captured, so the lib's
|
||||
@@ -413,12 +462,11 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
|
||||
|
||||
err = bt_le_acl_conn_security_changed_listener(event.security_change.conn_handle,
|
||||
event.security_change.sec_level);
|
||||
assert(err == 0);
|
||||
BT_LE_ASSERT(err == 0);
|
||||
|
||||
if (encrypted) {
|
||||
/* TODO: check if bonded or not */
|
||||
err = bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true);
|
||||
assert(err == 0);
|
||||
(void)bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true);
|
||||
}
|
||||
|
||||
end:
|
||||
@@ -483,7 +531,7 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
|
||||
{
|
||||
struct bt_le_gap_app_param *param;
|
||||
|
||||
assert(data && data_len);
|
||||
BT_LE_ASSERT(data && data_len);
|
||||
|
||||
param = (struct bt_le_gap_app_param *)data;
|
||||
|
||||
@@ -519,14 +567,14 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
|
||||
handle_bond_delete_event_safe(param);
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void bt_le_gap_app_post_event(uint8_t type, void *param)
|
||||
void bt_le_gap_app_post_event(uint16_t type, void *param)
|
||||
{
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* For Bluedroid, post the typed event to the ISO task instead. */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/bluetooth/l2cap.h>
|
||||
#include <zephyr/bluetooth/common/bt_str.h>
|
||||
|
||||
#include <../host/keys.h>
|
||||
@@ -53,9 +54,10 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
|
||||
|
||||
ARG_UNUSED(id);
|
||||
|
||||
assert(conn && peer);
|
||||
BT_LE_ASSERT(conn && peer);
|
||||
|
||||
/* TODO(privacy): also match init/resp or resolved identity; dst may stay RPA. */
|
||||
|
||||
/* Check against conn dst address as it may be the identity address */
|
||||
if (bt_addr_le_eq(peer, &conn->le.dst)) {
|
||||
return true;
|
||||
}
|
||||
@@ -63,24 +65,12 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
|
||||
return false;
|
||||
}
|
||||
|
||||
_LIB_IDF
|
||||
struct bt_conn *bt_conn_ref(struct bt_conn *conn)
|
||||
{
|
||||
return conn;
|
||||
}
|
||||
|
||||
_LIB_IDF
|
||||
void bt_conn_unref(struct bt_conn *conn)
|
||||
{
|
||||
ARG_UNUSED(conn);
|
||||
}
|
||||
|
||||
_LIB_ONLY
|
||||
void bt_conn_foreach(enum bt_conn_type type,
|
||||
void (*func)(struct bt_conn *conn, void *data),
|
||||
void *data)
|
||||
{
|
||||
assert(type == BT_CONN_TYPE_LE);
|
||||
BT_LE_ASSERT(type == BT_CONN_TYPE_LE);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) {
|
||||
struct bt_conn *conn = &acl_conns[i];
|
||||
@@ -95,7 +85,7 @@ void bt_conn_foreach(enum bt_conn_type type,
|
||||
_LIB_ONLY
|
||||
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn)
|
||||
{
|
||||
assert(conn);
|
||||
BT_LE_ASSERT(conn);
|
||||
|
||||
return &conn->le.dst;
|
||||
}
|
||||
@@ -105,19 +95,19 @@ uint8_t bt_conn_index(const struct bt_conn *conn)
|
||||
{
|
||||
ptrdiff_t index = 0;
|
||||
|
||||
assert(conn);
|
||||
BT_LE_ASSERT(conn);
|
||||
|
||||
switch (conn->type) {
|
||||
case BT_CONN_TYPE_LE:
|
||||
index = conn - acl_conns;
|
||||
assert(index >= 0 && index < ARRAY_SIZE(acl_conns));
|
||||
BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(acl_conns));
|
||||
break;
|
||||
case BT_CONN_TYPE_ISO:
|
||||
index = conn - iso_conns;
|
||||
assert(index >= 0 && index < ARRAY_SIZE(iso_conns));
|
||||
BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(iso_conns));
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -126,19 +116,6 @@ uint8_t bt_conn_index(const struct bt_conn *conn)
|
||||
return (uint8_t)index;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_SMP
|
||||
_IDF_ONLY
|
||||
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec)
|
||||
{
|
||||
/* This function is used by CIS to set security level,
|
||||
* currently always return failure here to make sure
|
||||
* that before creating CIS, the ACL connection is
|
||||
* encrypted.
|
||||
*/
|
||||
return -ENOTSUP;
|
||||
}
|
||||
#endif /* CONFIG_BT_SMP */
|
||||
|
||||
static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
@@ -152,7 +129,7 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
|
||||
case BT_CONN_DISCONNECTING:
|
||||
return BT_CONN_STATE_DISCONNECTING;
|
||||
default:
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -160,7 +137,10 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
|
||||
_LIB_ONLY
|
||||
int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info)
|
||||
{
|
||||
assert(conn && info);
|
||||
BT_LE_ASSERT(conn && info);
|
||||
|
||||
/* Callers today only pass ACL (BT_CONN_TYPE_LE). ISO would need
|
||||
* info->le.* from conn->iso.acl (union); not implemented until needed. */
|
||||
|
||||
info->type = conn->type;
|
||||
info->role = conn->role;
|
||||
@@ -218,7 +198,6 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb)
|
||||
return err;
|
||||
}
|
||||
|
||||
_NOT_USED
|
||||
int bt_conn_cb_unregister(struct bt_conn_cb *cb)
|
||||
{
|
||||
LOG_DBG("ConnCbUnreg");
|
||||
@@ -236,6 +215,16 @@ int bt_conn_cb_unregister(struct bt_conn_cb *cb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb)
|
||||
{
|
||||
int err;
|
||||
bt_le_host_lock();
|
||||
err = bt_conn_cb_unregister(cb);
|
||||
bt_le_host_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
_LIB_ONLY
|
||||
int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb)
|
||||
{
|
||||
@@ -277,7 +266,7 @@ int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb)
|
||||
_IDF_ONLY
|
||||
struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size)
|
||||
{
|
||||
assert(conns);
|
||||
BT_LE_ASSERT(conns);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
struct bt_conn *conn = &conns[i];
|
||||
@@ -322,7 +311,7 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type)
|
||||
{
|
||||
struct bt_conn *conn = NULL;
|
||||
|
||||
assert(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO);
|
||||
BT_LE_ASSERT(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO);
|
||||
|
||||
/* LOG_DBG("ConnLookupHdl[%u][%u]", handle, type); */
|
||||
|
||||
@@ -337,11 +326,20 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type)
|
||||
|
||||
struct bt_conn *bt_conn_lookup_index(uint8_t index)
|
||||
{
|
||||
struct bt_conn *conn;
|
||||
|
||||
if (index >= ARRAY_SIZE(acl_conns)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return bt_conn_ref(&acl_conns[index]);
|
||||
conn = &acl_conns[index];
|
||||
|
||||
/* Free/wiped slots are type NONE after acl_conn_delete. */
|
||||
if (conn->type == BT_CONN_TYPE_NONE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
@@ -352,17 +350,6 @@ struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle)
|
||||
return bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE);
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle)
|
||||
{
|
||||
struct bt_conn *conn = NULL;
|
||||
/* LOG_DBG("AclConnFind[%u]", conn_handle); */
|
||||
bt_le_host_lock();
|
||||
conn = bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE);
|
||||
bt_le_host_unlock();
|
||||
return conn;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_acl_conn_new(uint16_t conn_handle,
|
||||
uint8_t role,
|
||||
@@ -371,7 +358,7 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
|
||||
{
|
||||
struct bt_conn *conn;
|
||||
|
||||
assert(dst);
|
||||
BT_LE_ASSERT(dst);
|
||||
|
||||
LOG_DBG("AclNew[%u][%u][%u][%s]",
|
||||
conn_handle, role, sec_level, bt_addr_le_str(dst));
|
||||
@@ -383,6 +370,10 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
|
||||
conn->role = role;
|
||||
conn->state = BT_CONN_CONNECTED;
|
||||
conn->sec_level = sec_level;
|
||||
/* AUTH-before-ACL-slot path may create with L2+ directly. */
|
||||
if (sec_level > BT_SECURITY_L1) {
|
||||
conn->encrypt = 1;
|
||||
}
|
||||
memcpy(&conn->le.dst, dst, sizeof(conn->le.dst));
|
||||
} else {
|
||||
LOG_ERR("NoFreeConn[%u]", conn_handle);
|
||||
@@ -418,6 +409,20 @@ void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk)
|
||||
LOG_INF("ConnSetLtk[%u][%s]", conn->handle, bt_hex(ltk, 16));
|
||||
}
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
/* Defer ACL slot wipe while a CIS still holds iso.acl. */
|
||||
static bool bt_iso_acl_has_cis(const struct bt_conn *acl)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
|
||||
if (iso_conns[i].type == BT_CONN_TYPE_ISO && iso_conns[i].iso.acl == acl) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_UNICAST */
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_acl_conn_delete(uint16_t conn_handle)
|
||||
{
|
||||
@@ -432,6 +437,27 @@ int bt_le_acl_conn_delete(uint16_t conn_handle)
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
if (bt_iso_acl_has_cis(conn)) {
|
||||
LOG_INF("AclConnDelDeferred[%u]", conn_handle);
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_UNICAST */
|
||||
|
||||
/* OTS CoC (and any L2CAP) keep chan->conn on this list. Wipe without
|
||||
* detaching leaves dangling pointers if ACL slot is reused. */
|
||||
{
|
||||
struct bt_l2cap_chan *chan, *tmp;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&conn->channels, chan, tmp, node) {
|
||||
sys_slist_find_and_remove(&conn->channels, &chan->node);
|
||||
if (chan->ops != NULL && chan->ops->disconnected != NULL) {
|
||||
chan->ops->disconnected(chan);
|
||||
}
|
||||
chan->conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wipe this connection's LTK slot (key hygiene); the memset below then nulls
|
||||
* conn->le.keys. */
|
||||
memset(&conn_ltk[conn - acl_conns], 0, sizeof(conn_ltk[0]));
|
||||
@@ -461,8 +487,8 @@ int bt_le_acl_conn_update(uint16_t conn_handle,
|
||||
}
|
||||
|
||||
if (conn->sec_level < sec_level) {
|
||||
/* No encryption to encryption */
|
||||
if (conn->encrypt == 0 && conn->sec_level == BT_SECURITY_L1) {
|
||||
/* Mark encrypted on first step into L2+ (not only from L1). */
|
||||
if (conn->encrypt == 0 && sec_level > BT_SECURITY_L1) {
|
||||
conn->encrypt = 1;
|
||||
|
||||
if (encrypted) {
|
||||
@@ -519,8 +545,8 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
|
||||
{
|
||||
bt_conn_state_t old_state;
|
||||
|
||||
assert(conn);
|
||||
assert(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO);
|
||||
|
||||
LOG_DBG("ConnSetState[%u][%u]", conn->state, state);
|
||||
|
||||
@@ -556,7 +582,7 @@ static int iso_disconnect(struct bt_conn *conn, uint8_t reason)
|
||||
_IDF_ONLY
|
||||
int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
assert(conn);
|
||||
BT_LE_ASSERT(conn);
|
||||
|
||||
LOG_DBG("ConnDisconnect[%u][%u][%02x]", conn->state, conn->type, reason);
|
||||
|
||||
@@ -567,10 +593,14 @@ int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
|
||||
}
|
||||
|
||||
if (conn->type == BT_CONN_TYPE_ISO) {
|
||||
return iso_disconnect(conn, reason);
|
||||
int err = iso_disconnect(conn, reason);
|
||||
if (err == 0) {
|
||||
bt_conn_set_state(conn, BT_CONN_DISCONNECTING);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
case BT_CONN_DISCONNECTING:
|
||||
return 0;
|
||||
case BT_CONN_DISCONNECTED:
|
||||
@@ -627,9 +657,8 @@ int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason)
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
/* GATT sub/CCC cleanup is ACL-scoped, not ISO-unicast. */
|
||||
bt_le_acl_conn_disconnected_gatt_listener(conn_handle);
|
||||
#endif /* CONFIG_BT_ISO_UNICAST */
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -676,6 +705,8 @@ int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle,
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
/* TODO(privacy): copy identity into conn->le.dst once adapters post resolve. */
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
|
||||
if (listener->identity_resolved) {
|
||||
listener->identity_resolved(conn, rpa, identity);
|
||||
|
||||
@@ -88,7 +88,7 @@ int bt_gatt_service_register(struct bt_gatt_service *svc)
|
||||
}
|
||||
|
||||
uuid = svc->attrs[0].user_data;
|
||||
assert(uuid);
|
||||
BT_LE_ASSERT(uuid);
|
||||
|
||||
LOG_DBG("GattSvc[%04x]", uuid->val);
|
||||
|
||||
@@ -154,7 +154,7 @@ uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
|
||||
{
|
||||
uint16_t mtu;
|
||||
|
||||
assert(conn);
|
||||
BT_LE_ASSERT(conn);
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
mtu = bt_le_bluedroid_gatt_get_mtu(conn);
|
||||
@@ -230,7 +230,7 @@ uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
|
||||
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) {
|
||||
struct bt_gatt_chrc *chrc = attr->user_data;
|
||||
|
||||
assert(chrc);
|
||||
BT_LE_ASSERT(chrc);
|
||||
handle = chrc->value_handle;
|
||||
|
||||
if (handle == 0) {
|
||||
@@ -267,7 +267,7 @@ struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr)
|
||||
|
||||
LOG_DBG("GattAttrNext");
|
||||
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
handle = bt_gatt_attr_get_handle(attr);
|
||||
|
||||
bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next);
|
||||
@@ -359,7 +359,7 @@ static void foreach_attr_type_dyndb(uint16_t start_handle,
|
||||
for (size_t i = 0; i < svc->attr_count; i++) {
|
||||
struct bt_gatt_attr *attr = &svc->attrs[i];
|
||||
|
||||
assert(attr->uuid);
|
||||
BT_LE_ASSERT(attr->uuid);
|
||||
|
||||
if (gatt_foreach_iter(attr, attr->handle,
|
||||
start_handle,
|
||||
@@ -448,7 +448,7 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t buf_len, uint16_t offset,
|
||||
const void *value, uint16_t value_len)
|
||||
{
|
||||
assert(buf);
|
||||
BT_LE_ASSERT(buf);
|
||||
|
||||
LOG_DBG("GattAttrRd[%u][%u][%u]", buf_len, offset, value_len);
|
||||
|
||||
@@ -471,7 +471,7 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(params);
|
||||
BT_LE_ASSERT(params);
|
||||
|
||||
LOG_DBG("GattNtfCb[%u]", params->len);
|
||||
|
||||
@@ -486,10 +486,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
|
||||
err = bt_le_nimble_gatts_notify(conn, params);
|
||||
#endif
|
||||
|
||||
/* gatts_notify is synchronous (mbuf-copy + dispatch on return); fire the
|
||||
* caller's completion cb here so state machines like PACS_FLAG_NOTIFY_RDY
|
||||
* advance. */
|
||||
if (err == 0 && params->func != NULL) {
|
||||
/* Sync notify: complete cb only when conn is known (not broadcast-all). */
|
||||
if (err == 0 && params->func != NULL && conn != NULL) {
|
||||
params->func(conn, params->user_data);
|
||||
}
|
||||
|
||||
@@ -499,8 +497,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
|
||||
_LIB_IDF
|
||||
int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *params)
|
||||
{
|
||||
assert(params);
|
||||
assert(params->attr && params->attr->uuid);
|
||||
BT_LE_ASSERT(params);
|
||||
BT_LE_ASSERT(params->attr && params->attr->uuid);
|
||||
|
||||
LOG_DBG("GattInd[%s]", bt_uuid_str(params->attr->uuid));
|
||||
|
||||
@@ -534,7 +532,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
|
||||
{
|
||||
struct bt_uuid *uuid = attr->user_data;
|
||||
|
||||
assert(uuid);
|
||||
BT_LE_ASSERT(uuid);
|
||||
|
||||
LOG_DBG("GattAttrRdSvc[%s]", bt_uuid_str(uuid));
|
||||
|
||||
@@ -545,7 +543,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
|
||||
}
|
||||
|
||||
/* BT_UUID_TYPE_32 is not expected for service UUIDs in LE Audio */
|
||||
assert(uuid->type == BT_UUID_TYPE_128);
|
||||
BT_LE_ASSERT(uuid->type == BT_UUID_TYPE_128);
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, BT_UUID_128(uuid)->val, 16);
|
||||
}
|
||||
@@ -581,8 +579,8 @@ ssize_t bt_gatt_attr_read_included(struct bt_conn *conn,
|
||||
uint16_t handle;
|
||||
|
||||
incl = attr->user_data;
|
||||
assert(incl);
|
||||
assert(incl->user_data);
|
||||
BT_LE_ASSERT(incl);
|
||||
BT_LE_ASSERT(incl->user_data);
|
||||
|
||||
handle = bt_gatt_attr_get_handle(incl);
|
||||
uuid = incl->user_data;
|
||||
@@ -618,8 +616,8 @@ ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
|
||||
struct gatt_chrc pdu;
|
||||
uint8_t value_len;
|
||||
|
||||
assert(chrc);
|
||||
assert(chrc->uuid);
|
||||
BT_LE_ASSERT(chrc);
|
||||
BT_LE_ASSERT(chrc->uuid);
|
||||
|
||||
pdu.properties = chrc->properties;
|
||||
pdu.value_handle = sys_cpu_to_le16(bt_gatt_attr_value_handle(attr));
|
||||
@@ -661,7 +659,7 @@ static struct bt_gatt_ccc_cfg *gatts_find_free_ccc_cfg(struct bt_gatt_ccc_manage
|
||||
static struct bt_gatt_ccc_cfg *gatts_find_ccc_cfg_by_conn(const struct bt_conn *conn,
|
||||
struct bt_gatt_ccc_managed_user_data *ccc)
|
||||
{
|
||||
assert(conn);
|
||||
BT_LE_ASSERT(conn);
|
||||
|
||||
for (size_t i = 0; i < ccc->cfg_count; i++) {
|
||||
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
|
||||
@@ -784,10 +782,10 @@ ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
|
||||
const struct bt_gatt_ccc_cfg *cfg;
|
||||
uint16_t value;
|
||||
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
|
||||
ccc = attr->user_data;
|
||||
assert(ccc);
|
||||
BT_LE_ASSERT(ccc);
|
||||
|
||||
LOG_DBG("GattAttrRdCcc[%u][%u]", len, offset);
|
||||
|
||||
@@ -814,10 +812,10 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
|
||||
bool new_entry;
|
||||
uint16_t value;
|
||||
|
||||
assert(attr);
|
||||
BT_LE_ASSERT(attr);
|
||||
|
||||
ccc = attr->user_data;
|
||||
assert(ccc);
|
||||
BT_LE_ASSERT(ccc);
|
||||
|
||||
LOG_DBG("GattAttrWrCcc[%u][%u][%02x]", len, offset, flags);
|
||||
|
||||
@@ -910,8 +908,8 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
|
||||
{
|
||||
const struct bt_gatt_ccc_managed_user_data *ccc;
|
||||
|
||||
assert(conn);
|
||||
assert(attr && attr->uuid);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(attr && attr->uuid);
|
||||
|
||||
LOG_DBG("GattIsSub[%04x][%s]", ccc_type, bt_uuid_str(attr->uuid));
|
||||
|
||||
@@ -950,7 +948,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
|
||||
}
|
||||
|
||||
attr = bt_gatt_attr_next(attr);
|
||||
assert(attr && attr->uuid);
|
||||
BT_LE_ASSERT(attr && attr->uuid);
|
||||
|
||||
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
|
||||
}
|
||||
@@ -962,7 +960,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(attr->uuid);
|
||||
BT_LE_ASSERT(attr->uuid);
|
||||
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
|
||||
}
|
||||
|
||||
@@ -978,7 +976,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
|
||||
}
|
||||
}
|
||||
|
||||
assert(attr->uuid);
|
||||
BT_LE_ASSERT(attr->uuid);
|
||||
|
||||
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
|
||||
|
||||
@@ -987,7 +985,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
|
||||
}
|
||||
|
||||
ccc = attr->user_data;
|
||||
assert(ccc);
|
||||
BT_LE_ASSERT(ccc);
|
||||
|
||||
/* Check if the connection is subscribed */
|
||||
for (size_t i = 0; i < ccc->cfg_count; i++) {
|
||||
@@ -1013,7 +1011,7 @@ static int gatts_ccc_cfg_update(struct bt_conn *conn,
|
||||
bool value_changed;
|
||||
|
||||
ccc = attr->user_data;
|
||||
assert(ccc);
|
||||
BT_LE_ASSERT(ccc);
|
||||
|
||||
/* LOG_DBG("GattsCccCfgUpd[%04x]", value); */
|
||||
|
||||
@@ -1130,10 +1128,10 @@ int bt_gattc_disc_start_safe(uint16_t conn_handle)
|
||||
_LIB_ONLY
|
||||
int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params)
|
||||
{
|
||||
assert(conn);
|
||||
assert(params);
|
||||
assert(params->start_handle && params->end_handle);
|
||||
assert(params->start_handle <= params->end_handle);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(params);
|
||||
BT_LE_ASSERT(params->start_handle && params->end_handle);
|
||||
BT_LE_ASSERT(params->start_handle <= params->end_handle);
|
||||
|
||||
LOG_DBG("GattDisc[%u][%u]", params->start_handle, params->end_handle);
|
||||
|
||||
@@ -1276,7 +1274,7 @@ static int gattc_ccc_discover(struct bt_conn *conn, struct bt_gatt_subscribe_par
|
||||
.val = BT_UUID_GATT_CCC_VAL,
|
||||
};
|
||||
|
||||
assert(params->disc_params);
|
||||
BT_LE_ASSERT(params->disc_params);
|
||||
|
||||
LOG_DBG("GattcCccDisc[%u][%u]", params->value_handle, params->end_handle);
|
||||
|
||||
@@ -1310,11 +1308,11 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
|
||||
bool has_subscription = false;
|
||||
struct gattc_sub *sub;
|
||||
|
||||
assert(conn);
|
||||
assert(params);
|
||||
assert(params->notify);
|
||||
assert(params->value);
|
||||
assert(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE ||
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(params);
|
||||
BT_LE_ASSERT(params->notify);
|
||||
BT_LE_ASSERT(params->value);
|
||||
BT_LE_ASSERT(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE ||
|
||||
(params->end_handle && params->disc_params));
|
||||
|
||||
LOG_DBG("GattcSub[%u][%u][%u][%04x]",
|
||||
@@ -1369,6 +1367,14 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
|
||||
params->value_handle = 0; /* unlinked: clear retry guard */
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Adapter may have returned 0 while leaving ccc unresolved
|
||||
* (cb got attr==NULL). Do not append a dead subscription. */
|
||||
if (params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE) {
|
||||
LOG_ERR("GattcSubCccUnresolved[%u]", params->value_handle);
|
||||
params->value_handle = 0;
|
||||
return -ENOENT;
|
||||
}
|
||||
} else {
|
||||
/* Note:
|
||||
* If the CCC handle is auto-discovered, and during the
|
||||
@@ -1403,8 +1409,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
|
||||
bool found = false;
|
||||
int err = 0;
|
||||
|
||||
assert(conn);
|
||||
assert(params);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(params);
|
||||
|
||||
LOG_DBG("GattcUnsub[%04x][%u][%u][%u]",
|
||||
params->value, params->value_handle, params->ccc_handle, params->end_handle);
|
||||
@@ -1469,10 +1475,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
|
||||
|
||||
LOG_DBG("GattcUnsub[%u][%d]", has_subscription, err);
|
||||
|
||||
if (has_subscription || err) {
|
||||
/* Notify with NULL data to complete unsubscribe */
|
||||
params->notify(conn, params, NULL, 0);
|
||||
}
|
||||
/* Notify with NULL data to complete unsubscribe */
|
||||
params->notify(conn, params, NULL, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1480,8 +1484,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
|
||||
_LIB_IDF
|
||||
int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
|
||||
{
|
||||
assert(conn);
|
||||
assert(params);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(params);
|
||||
|
||||
LOG_DBG("GattRd[%u]", params->handle_count);
|
||||
|
||||
@@ -1496,7 +1500,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
|
||||
}
|
||||
|
||||
if (params->handle_count == 0) {
|
||||
assert(params->by_uuid.uuid);
|
||||
BT_LE_ASSERT(params->by_uuid.uuid);
|
||||
|
||||
if (params->by_uuid.uuid->type != BT_UUID_TYPE_16) {
|
||||
LOG_ERR("GattRdInvUuid[%s]", bt_uuid_str(params->by_uuid.uuid));
|
||||
@@ -1514,11 +1518,11 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
|
||||
_LIB_IDF
|
||||
int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
|
||||
{
|
||||
assert(conn);
|
||||
assert(params);
|
||||
assert(params->func);
|
||||
assert(params->handle);
|
||||
assert((!params->data ^ !params->length) == 0);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(params);
|
||||
BT_LE_ASSERT(params->func);
|
||||
BT_LE_ASSERT(params->handle);
|
||||
BT_LE_ASSERT((!params->data ^ !params->length) == 0);
|
||||
|
||||
LOG_DBG("GattWr[%u][%u][%u]", params->handle, params->length, params->offset);
|
||||
|
||||
@@ -1545,9 +1549,9 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
|
||||
bool sign, bt_gatt_complete_func_t func,
|
||||
void *user_data)
|
||||
{
|
||||
assert(conn);
|
||||
assert(handle);
|
||||
assert((!data ^ !length) == 0);
|
||||
BT_LE_ASSERT(conn);
|
||||
BT_LE_ASSERT(handle);
|
||||
BT_LE_ASSERT((!data ^ !length) == 0);
|
||||
|
||||
ARG_UNUSED(func);
|
||||
ARG_UNUSED(user_data);
|
||||
@@ -1629,7 +1633,7 @@ void bt_le_acl_conn_bond_deleted_gatt_listener(uint8_t id, const bt_addr_le_t *p
|
||||
|
||||
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len)
|
||||
{
|
||||
assert(data && data_len);
|
||||
BT_LE_ASSERT(data && data_len);
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
bt_le_bluedroid_gatt_handle_event(data, data_len);
|
||||
|
||||
@@ -69,7 +69,7 @@ struct net_buf *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len)
|
||||
LOG_DBG("HciCmdCreate[%04x][%u]", opcode, param_len);
|
||||
|
||||
buf = net_buf_alloc(&hci_cmd_pool, K_NO_WAIT);
|
||||
assert(buf);
|
||||
BT_LE_ASSERT(buf);
|
||||
|
||||
net_buf_reserve(buf, BT_BUF_RESERVE);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ void bt_le_host_lock(void)
|
||||
if (err) {
|
||||
/* K_MUTEX_SHORT wait failed: the host stack is wedged. k_mutex_lock has
|
||||
* already logged self/holder task names. Use libc abort() rather
|
||||
* than assert(0) — assert is a no-op under NDEBUG, which would
|
||||
* than BT_LE_ASSERT(0) — assert is a no-op under NDEBUG, which would
|
||||
* let the caller enter the critical section without the mutex
|
||||
* held and cause races. abort() halts in every build.
|
||||
*/
|
||||
@@ -82,22 +82,15 @@ int bt_le_host_init(void)
|
||||
goto delete_mutex;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
err = bt_le_l2cap_init();
|
||||
if (err) {
|
||||
goto deinit_scan;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
err = bt_le_bluedroid_gap_init();
|
||||
if (err) {
|
||||
goto deinit_l2cap;
|
||||
goto deinit_scan;
|
||||
}
|
||||
|
||||
err = bt_le_bluedroid_gatt_init();
|
||||
if (err) {
|
||||
goto deinit_bluedroid_gatt;
|
||||
goto deinit_gatt;
|
||||
}
|
||||
#else
|
||||
/* nimble: reset the per-conn GATT cache/NRP arrays (now .bss/PSRAM, no
|
||||
@@ -108,7 +101,7 @@ int bt_le_host_init(void)
|
||||
|
||||
err = bt_le_iso_init();
|
||||
if (err) {
|
||||
goto deinit_bluedroid_gatt;
|
||||
goto deinit_gatt;
|
||||
}
|
||||
|
||||
err = bt_le_iso_task_init();
|
||||
@@ -120,15 +113,11 @@ int bt_le_host_init(void)
|
||||
|
||||
deinit_iso:
|
||||
bt_le_iso_deinit();
|
||||
deinit_bluedroid_gatt:
|
||||
deinit_gatt:
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
bt_le_bluedroid_gatt_deinit();
|
||||
deinit_l2cap:
|
||||
deinit_scan:
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
bt_le_l2cap_deinit();
|
||||
deinit_scan: /* only reachable when OTS path is compiled in */
|
||||
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
|
||||
bt_le_scan_deinit();
|
||||
delete_mutex:
|
||||
k_mutex_delete(&host_mutex);
|
||||
@@ -148,9 +137,6 @@ void bt_le_host_deinit(void)
|
||||
bt_le_nimble_gattc_db_deinit();
|
||||
bt_le_nimble_gatt_nrp_deinit();
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
bt_le_l2cap_deinit();
|
||||
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
|
||||
bt_le_scan_deinit();
|
||||
|
||||
k_mutex_delete(&host_mutex);
|
||||
|
||||
@@ -57,6 +57,9 @@ struct bt_le_gap_app_pa_sync_past_param {
|
||||
uint8_t adv_phy;
|
||||
uint16_t per_adv_itvl;
|
||||
uint8_t adv_ca;
|
||||
/* PAST sender, not the advertiser in addr above; conn_handle is resolved
|
||||
* from it on Bluedroid. */
|
||||
struct bt_le_addr src_addr;
|
||||
/* ACL conn that delivered the PAST. */
|
||||
uint16_t conn_handle;
|
||||
};
|
||||
@@ -300,7 +303,7 @@ void bt_le_gap_app_biginfo_event(uint8_t *param);
|
||||
|
||||
void bt_le_gap_handle_event(uint8_t *data, size_t data_len);
|
||||
|
||||
void bt_le_gap_app_post_event(uint8_t type, void *param);
|
||||
void bt_le_gap_app_post_event(uint16_t type, void *param);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count);
|
||||
|
||||
struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle);
|
||||
|
||||
struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle);
|
||||
|
||||
int bt_le_acl_conn_new(uint16_t conn_handle,
|
||||
uint8_t role,
|
||||
bt_addr_le_t *dst,
|
||||
|
||||
@@ -77,7 +77,10 @@ struct bt_le_gatts_notify_tx_event {
|
||||
uint16_t conn_handle;
|
||||
uint16_t conn_id;
|
||||
uint16_t attr_handle;
|
||||
uint8_t status;
|
||||
/* int, not uint8_t: NimBLE reports BLE_HS_ERR_ATT_BASE(0x100)+att_code for a
|
||||
* failed indication; uint8_t truncates 0x10e -> 0x0e == BLE_HS_EDONE (success),
|
||||
* masking the failure as confirmation. */
|
||||
int status;
|
||||
};
|
||||
|
||||
/* Bluedroid-side adapter events. NimBLE produces ACL connect/disconnect via
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
#include <zephyr/bluetooth/iso.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
|
||||
#include "utils/assert.h"
|
||||
#include "utils/iso_attr.h"
|
||||
#include "utils/mem.h"
|
||||
|
||||
#include "common/adv.h"
|
||||
#include "common/conn.h"
|
||||
#include "common/iso.h"
|
||||
#include "common/l2cap.h"
|
||||
#include "common/scan.h"
|
||||
#include "common/gatt.h"
|
||||
#include "common/task.h"
|
||||
|
||||
@@ -28,6 +28,7 @@ struct net_buf;
|
||||
struct bt_le_iso_cb {
|
||||
void (*cis_dis)(struct net_buf *buf);
|
||||
void (*cis_est)(struct net_buf *buf);
|
||||
void (*cis_est_v2)(struct net_buf *buf);
|
||||
void (*cis_req)(struct net_buf *buf);
|
||||
void (*big_create)(struct net_buf *buf);
|
||||
void (*big_term)(struct net_buf *buf);
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
|
||||
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef HOST_COMMON_L2CAP_H_
|
||||
#define HOST_COMMON_L2CAP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* TODO */
|
||||
#else
|
||||
#include "nimble/l2cap.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define L2CAP_LE_SUCCESS 0x0000
|
||||
#define L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002
|
||||
#define L2CAP_LE_ERR_NO_RESOURCES 0x0004
|
||||
#define L2CAP_LE_ERR_AUTHENTICATION 0x0005
|
||||
#define L2CAP_LE_ERR_AUTHORIZATION 0x0006
|
||||
#define L2CAP_LE_ERR_KEY_SIZE 0x0007
|
||||
#define L2CAP_LE_ERR_ENCRYPTION 0x0008
|
||||
#define L2CAP_LE_ERR_INVALID_SCID 0x0009
|
||||
#define L2CAP_LE_ERR_SCID_IN_USE 0x000A
|
||||
#define L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B
|
||||
#define L2CAP_LE_ERR_INVALID_PARAMS 0x000C
|
||||
|
||||
#define L2CAP_LE_OTS_PSM 0x0025
|
||||
#define L2CAP_LE_OTS_MTU MIN(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, \
|
||||
CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU)
|
||||
|
||||
int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t scid, uint16_t mtu,
|
||||
uint16_t mps, uint16_t credits,
|
||||
uint16_t *result);
|
||||
|
||||
void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t tx_cid, uint16_t tx_mtu,
|
||||
uint16_t rx_cid, uint16_t rx_mtu);
|
||||
|
||||
void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm);
|
||||
|
||||
void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
|
||||
uint8_t *data, uint16_t len);
|
||||
|
||||
int bt_le_l2cap_init(void);
|
||||
|
||||
void bt_le_l2cap_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HOST_COMMON_L2CAP_H_ */
|
||||
@@ -35,6 +35,8 @@ void bt_le_scan_recv_listener(uint16_t event_type,
|
||||
uint8_t data_len,
|
||||
uint8_t *data);
|
||||
|
||||
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t sync_handle);
|
||||
|
||||
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle);
|
||||
|
||||
int bt_le_per_adv_sync_new(uint16_t sync_handle,
|
||||
|
||||
@@ -60,6 +60,7 @@ enum iso_queue_item_type {
|
||||
ISO_QUEUE_ITEM_TYPE_TIMER_EVENT,
|
||||
ISO_QUEUE_ITEM_TYPE_GAP_EVENT,
|
||||
ISO_QUEUE_ITEM_TYPE_GATT_EVENT,
|
||||
ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT,
|
||||
ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT,
|
||||
|
||||
/* floodable: high-volume best-effort reports, posted non-blocking, newest
|
||||
|
||||
@@ -60,6 +60,7 @@ static struct bt_le_iso_cb iso_cb = {
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
.cis_dis = hci_le_cis_disconnected,
|
||||
.cis_est = hci_le_cis_established,
|
||||
.cis_est_v2 = hci_le_cis_established_v2,
|
||||
#if CONFIG_BT_ISO_PERIPHERAL
|
||||
.cis_req = hci_le_cis_req,
|
||||
#endif /* CONFIG_BT_ISO_PERIPHERAL */
|
||||
@@ -135,12 +136,11 @@ static void hci_le_cis_disconnected(struct net_buf *buf)
|
||||
LOG_DBG("CisDisconnectedEvt[%u]", evt->handle);
|
||||
|
||||
iso = bt_conn_lookup_handle(evt->handle, BT_CONN_TYPE_ISO);
|
||||
assert(iso);
|
||||
BT_LE_ASSERT(iso);
|
||||
|
||||
iso->err = evt->reason;
|
||||
|
||||
bt_iso_disconnected(iso);
|
||||
bt_conn_unref(iso);
|
||||
}
|
||||
|
||||
static int iso_cis_disconn_evt_listener_safe(uint8_t *data)
|
||||
@@ -189,6 +189,27 @@ static int iso_cis_est_evt_listener_safe(uint8_t *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iso_cis_est_evt_v2_listener_safe(uint8_t *data)
|
||||
{
|
||||
struct bt_le_iso_cb *listener = NULL;
|
||||
struct net_buf buf = {0};
|
||||
|
||||
LOG_DBG("CisEstEvtV2Listener");
|
||||
|
||||
bt_le_host_lock();
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
|
||||
if (listener->cis_est_v2) {
|
||||
buf.data = data + 1; /* The first octet is subev_code */
|
||||
listener->cis_est_v2(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
bt_le_host_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_ISO_PERIPHERAL
|
||||
static int iso_cis_req_evt_listener_safe(uint8_t *data)
|
||||
{
|
||||
@@ -346,7 +367,7 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
if (le_meta == false) {
|
||||
assert(event == BT_HCI_EVT_DISCONN_COMPLETE);
|
||||
BT_LE_ASSERT(event == BT_HCI_EVT_DISCONN_COMPLETE);
|
||||
iso_cis_disconn_evt_listener_safe(data + 2);
|
||||
return;
|
||||
}
|
||||
@@ -359,6 +380,9 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
|
||||
case BT_HCI_EVT_LE_CIS_ESTABLISHED:
|
||||
iso_cis_est_evt_listener_safe(data + 2);
|
||||
break;
|
||||
case BT_HCI_EVT_LE_CIS_ESTABLISHED_V2:
|
||||
iso_cis_est_evt_v2_listener_safe(data + 2);
|
||||
break;
|
||||
#if CONFIG_BT_ISO_PERIPHERAL
|
||||
case BT_HCI_EVT_LE_CIS_REQ:
|
||||
iso_cis_req_evt_listener_safe(data + 2);
|
||||
@@ -385,14 +409,14 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
|
||||
break;
|
||||
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
|
||||
default:
|
||||
assert(0);
|
||||
LOG_ERR("HandleIsoEvtUnknown[%u]", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len)
|
||||
{
|
||||
assert(data && data_len);
|
||||
BT_LE_ASSERT(data && data_len);
|
||||
handle_iso_event(data, data_len);
|
||||
free(data);
|
||||
}
|
||||
@@ -442,6 +466,15 @@ static void iso_tx_sdu_clear(uint16_t handle, bool is_big)
|
||||
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&iso_tx_sdu_list, sdu_node, sdu_tmp, node) {
|
||||
iso = sdu_node->chan->iso;
|
||||
|
||||
/* Peripheral CIS clears chan->iso during disconnect (before this runs);
|
||||
* the SDU is orphaned — drop it instead of dereferencing NULL. */
|
||||
if (iso == NULL) {
|
||||
sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node);
|
||||
free((void *)sdu_node->sdu);
|
||||
free(sdu_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((is_big == false && iso->handle == handle) ||
|
||||
(is_big && iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER &&
|
||||
iso->iso.info.broadcaster.big_handle == handle)) {
|
||||
@@ -614,7 +647,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
|
||||
void *ud;
|
||||
int err;
|
||||
|
||||
assert(data && data_len == sizeof(*evt));
|
||||
BT_LE_ASSERT(data && data_len == sizeof(*evt));
|
||||
|
||||
bt_le_host_lock();
|
||||
|
||||
@@ -647,7 +680,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
|
||||
* If using controller from other vendors, the pkt buffer may
|
||||
* needs to be freed here.
|
||||
*/
|
||||
LOG_WRN("IsoTxCompFail[%d]", err);
|
||||
LOG_WRN("IsoTxCompFail[%d]", err);
|
||||
}
|
||||
|
||||
free(sdu_node);
|
||||
@@ -659,7 +692,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
|
||||
}
|
||||
|
||||
chan = iso->iso.chan;
|
||||
assert(chan);
|
||||
BT_LE_ASSERT(chan);
|
||||
|
||||
cb = NULL;
|
||||
ud = NULL;
|
||||
@@ -687,7 +720,7 @@ static void iso_tx_comp_cb(uint16_t conn_handle, void *info, size_t size)
|
||||
* the event to the ISO task for processing.
|
||||
*/
|
||||
|
||||
assert(size == sizeof(struct bt_iso_tx_cb_info));
|
||||
BT_LE_ASSERT(size == sizeof(struct bt_iso_tx_cb_info));
|
||||
|
||||
evt = bt_le_int_calloc(1, sizeof(*evt));
|
||||
if (evt == NULL) {
|
||||
@@ -717,7 +750,7 @@ void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len)
|
||||
{
|
||||
struct net_buf buf = {0};
|
||||
|
||||
assert(data && data_len);
|
||||
BT_LE_ASSERT(data && data_len);
|
||||
|
||||
bt_le_host_lock();
|
||||
net_buf_simple_init_with_data(&buf.b, (void *)data, data_len);
|
||||
|
||||
@@ -1,533 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
|
||||
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
|
||||
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <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 <ots_l2cap_internal.h>
|
||||
|
||||
#include "common/host.h"
|
||||
#include "common/app/gap.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ISO_L2CAP, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
|
||||
#define L2CAP_LE_MIN_MTU 23
|
||||
#define L2CAP_ECRED_MIN_MTU 64
|
||||
|
||||
#define L2CAP_LE_CID_DYN_START 0x0040
|
||||
#define L2CAP_LE_CID_DYN_END 0x007F
|
||||
#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END)
|
||||
|
||||
#define L2CAP_LE_PSM_FIXED_START 0x0001
|
||||
#define L2CAP_LE_PSM_FIXED_END 0x007F
|
||||
#define L2CAP_LE_PSM_DYN_START 0x0080
|
||||
#define L2CAP_LE_PSM_DYN_END 0x00FF
|
||||
#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END)
|
||||
|
||||
static BT_ISO_EXT_RAM_BSS_ATTR sys_slist_t l2cap_servers;
|
||||
|
||||
static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
|
||||
if (BT_L2CAP_LE_CHAN(chan)->psm == psm) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
|
||||
{
|
||||
LOG_DBG("L2capChanAdd[%04x]", psm);
|
||||
|
||||
/* Attach channel to the connection */
|
||||
if (sys_slist_find(&conn->channels, &chan->node, NULL)) {
|
||||
LOG_WRN("L2capChanExists[%04x]", psm);
|
||||
return false;
|
||||
}
|
||||
|
||||
sys_slist_append(&conn->channels, &chan->node);
|
||||
chan->conn = conn;
|
||||
|
||||
/* Set channel PSM */
|
||||
BT_L2CAP_LE_CHAN(chan)->psm = psm;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm)
|
||||
{
|
||||
struct bt_l2cap_server *server = NULL;
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) {
|
||||
if (server->psm == psm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
static inline uint16_t err_to_result(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case -ENOMEM:
|
||||
return L2CAP_LE_ERR_NO_RESOURCES;
|
||||
case -EACCES:
|
||||
return L2CAP_LE_ERR_AUTHORIZATION;
|
||||
case -EPERM:
|
||||
return L2CAP_LE_ERR_KEY_SIZE;
|
||||
case -ENOTSUP:
|
||||
/* This handle the cases where a fixed channel is registered but
|
||||
* for some reason (e.g. controller not supporting a feature)
|
||||
* cannot be used.
|
||||
*/
|
||||
return L2CAP_LE_ERR_PSM_NOT_SUPP;
|
||||
default:
|
||||
return L2CAP_LE_ERR_UNACCEPT_PARAMS;
|
||||
}
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t scid, uint16_t mtu,
|
||||
uint16_t mps, uint16_t credits,
|
||||
uint16_t *result)
|
||||
{
|
||||
struct bt_l2cap_server *server;
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
int err;
|
||||
|
||||
ARG_UNUSED(credits);
|
||||
|
||||
LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_PARAMS;
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
/* Check if there is a server registered */
|
||||
server = l2cap_server_lookup_psm(psm);
|
||||
if (server == NULL) {
|
||||
LOG_ERR("SrvNotReg[%04x]", psm);
|
||||
|
||||
*result = L2CAP_LE_ERR_PSM_NOT_SUPP;
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (!L2CAP_LE_CID_IS_DYN(scid)) {
|
||||
LOG_ERR("NotDynScid[%04x]", scid);
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_SCID;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_tx_cid(conn, scid);
|
||||
if (chan) {
|
||||
LOG_WRN("ScidUsed[%04x]", scid);
|
||||
|
||||
*result = L2CAP_LE_ERR_SCID_IN_USE;
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
if (server->accept == NULL) {
|
||||
LOG_ERR("SrvAcceptNull");
|
||||
|
||||
*result = L2CAP_LE_ERR_INVALID_PARAMS;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
err = server->accept(conn, server, &chan);
|
||||
if (err) {
|
||||
LOG_ERR("SrvAcceptFail[%d]", err);
|
||||
|
||||
*result = err_to_result(err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("SrvAcceptNullChan");
|
||||
*result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (l2cap_chan_add(conn, chan, psm) == false) {
|
||||
*result = L2CAP_LE_ERR_NO_RESOURCES;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*result = L2CAP_LE_SUCCESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm,
|
||||
uint16_t tx_cid, uint16_t tx_mtu,
|
||||
uint16_t rx_cid, uint16_t rx_mtu)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]",
|
||||
conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("PsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid;
|
||||
BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu;
|
||||
BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid;
|
||||
BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu;
|
||||
|
||||
if (chan->ops->connected) {
|
||||
chan->ops->connected(chan);
|
||||
}
|
||||
}
|
||||
|
||||
#define L2CAP_UGLY_TX_DONE_WORKAROUND 1
|
||||
|
||||
_IDF_ONLY
|
||||
void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm)
|
||||
{
|
||||
#if L2CAP_UGLY_TX_DONE_WORKAROUND
|
||||
struct bt_gatt_ots_l2cap *l2cap_ctx;
|
||||
struct bt_l2cap_le_chan *l2chan;
|
||||
#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("PsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
#if L2CAP_UGLY_TX_DONE_WORKAROUND
|
||||
l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
|
||||
l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan);
|
||||
|
||||
if (chan->ops->sent && l2cap_ctx->tx_done) {
|
||||
chan->ops->sent(chan);
|
||||
}
|
||||
#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */
|
||||
|
||||
if (chan->ops->disconnected) {
|
||||
chan->ops->disconnected(chan);
|
||||
}
|
||||
|
||||
sys_slist_find_and_remove(&conn->channels, &chan->node);
|
||||
chan->conn = NULL;
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
|
||||
uint8_t *data, uint16_t len)
|
||||
{
|
||||
struct bt_l2cap_chan *chan;
|
||||
struct net_buf buf = {0};
|
||||
struct bt_conn *conn;
|
||||
|
||||
LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len);
|
||||
|
||||
conn = bt_le_acl_conn_find(conn_handle);
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
|
||||
return;
|
||||
}
|
||||
|
||||
chan = l2cap_lookup_psm(conn, psm);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("PsmNotFound[%04x]", psm);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.data = data;
|
||||
buf.len = len;
|
||||
|
||||
if (chan->ops->recv) {
|
||||
chan->ops->recv(chan, &buf);
|
||||
}
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
|
||||
{
|
||||
#if !CONFIG_BT_BLUEDROID_ENABLED
|
||||
int err;
|
||||
#endif
|
||||
|
||||
LOG_DBG("L2capChanConnect[%04x]", psm);
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capChanNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("InvPsm[%04x]", psm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* L2CAP COC is not yet implemented in the Bluedroid adapter (no
|
||||
* bt_le_bluedroid_l2cap_chan_connect exists). Return early so callers
|
||||
* like bt_gatt_ots_l2cap_connect see a clean -ENOTSUP. */
|
||||
return -ENOTSUP;
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
l2cap_chan_add(conn, chan, psm);
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
#if !CONFIG_BT_BLUEDROID_ENABLED
|
||||
int err;
|
||||
#endif
|
||||
|
||||
LOG_DBG("L2capChanDisconnect");
|
||||
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("L2capChanNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
return -ENOTSUP;
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_chan_disconnect(chan);
|
||||
if (err) {
|
||||
/* If the disconnect failed, remove the channel from the connection.
|
||||
* Otherwise the removal will be handled by the disconnect callback.
|
||||
*/
|
||||
sys_slist_find_and_remove(&chan->conn->channels, &chan->node);
|
||||
chan->conn = NULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
#endif
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
if (chan == NULL || buf == NULL) {
|
||||
LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_DBG("L2capChanSend[%u]", buf->len);
|
||||
|
||||
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
|
||||
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (buf->len > L2CAP_LE_OTS_MTU) {
|
||||
LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len, L2CAP_LE_OTS_MTU);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
return -ENOTSUP;
|
||||
#else
|
||||
return bt_le_nimble_l2cap_chan_send(chan, buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
_IDF_ONLY
|
||||
int bt_l2cap_server_register(struct bt_l2cap_server *server)
|
||||
{
|
||||
LOG_DBG("L2capSrvReg");
|
||||
|
||||
if (server == NULL) {
|
||||
LOG_ERR("SrvNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (server->accept == NULL) {
|
||||
LOG_ERR("SrvAcceptNull");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (server->sec_level > BT_SECURITY_L4) {
|
||||
LOG_ERR("InvSecLevel[%u]", server->sec_level);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (server->psm) {
|
||||
if (server->psm < L2CAP_LE_PSM_FIXED_START ||
|
||||
server->psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("InvPsm[%04x]", server->psm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check if given PSM is already in use */
|
||||
if (l2cap_server_lookup_psm(server->psm)) {
|
||||
LOG_WRN("PsmReg");
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
LOG_DBG("SrvPsm[%04x]", server->psm);
|
||||
} else {
|
||||
uint16_t psm;
|
||||
|
||||
for (psm = L2CAP_LE_PSM_DYN_START;
|
||||
psm <= L2CAP_LE_PSM_DYN_END; psm++) {
|
||||
if (l2cap_server_lookup_psm(psm) == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (psm > L2CAP_LE_PSM_DYN_END) {
|
||||
LOG_ERR("NoFreeDynPsm");
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
|
||||
LOG_DBG("PsmNew[%04x]", psm);
|
||||
|
||||
server->psm = psm;
|
||||
}
|
||||
|
||||
if (server->sec_level < BT_SECURITY_L1) {
|
||||
server->sec_level = BT_SECURITY_L1;
|
||||
}
|
||||
|
||||
sys_slist_append(&l2cap_servers, &server->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_le_l2cap_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
LOG_DBG("L2capInit");
|
||||
|
||||
#if CONFIG_BT_OTS
|
||||
extern int bt_gatt_ots_conn_cb_register(void);
|
||||
err = bt_gatt_ots_conn_cb_register();
|
||||
if (err) {
|
||||
LOG_ERR("OtsConnCbRegFail[%d]", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
extern int bt_gatt_ots_instances_prepare_v2(void);
|
||||
err = bt_gatt_ots_instances_prepare_v2();
|
||||
if (err) {
|
||||
LOG_ERR("PrepOtsInstsFail[%d]", err);
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS */
|
||||
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
extern int bt_gatt_ots_l2cap_init_v2(void);
|
||||
err = bt_gatt_ots_l2cap_init_v2();
|
||||
if (err) {
|
||||
LOG_ERR("OtsL2capInitFail[%d]", err);
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
|
||||
|
||||
#if CONFIG_BT_BLUEDROID_ENABLED
|
||||
/* No adapter-level L2CAP init exists (COC not implemented). Don't fail
|
||||
* host init here — the host-agnostic OTS registrations above are still
|
||||
* valid; any actual COC connect attempt later returns -ENOTSUP in
|
||||
* bt_l2cap_chan_connect, so OTS features fail gracefully at use time
|
||||
* instead of preventing the whole host from coming up. */
|
||||
#else
|
||||
err = bt_le_nimble_l2cap_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_le_l2cap_deinit(void)
|
||||
{
|
||||
LOG_DBG("L2capDeinit");
|
||||
|
||||
/* TODO: L2CAP server deinit */
|
||||
}
|
||||
@@ -198,7 +198,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
|
||||
{
|
||||
struct bt_le_per_adv_sync *per_adv_sync = NULL;
|
||||
|
||||
assert(adv_addr);
|
||||
BT_LE_ASSERT(adv_addr);
|
||||
|
||||
LOG_INF("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid);
|
||||
|
||||
@@ -218,7 +218,8 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
|
||||
return per_adv_sync;
|
||||
}
|
||||
|
||||
static struct bt_le_per_adv_sync *per_adv_sync_find(uint16_t handle)
|
||||
_IDF_ONLY
|
||||
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t handle)
|
||||
{
|
||||
struct bt_le_per_adv_sync *per_adv_sync = NULL;
|
||||
|
||||
@@ -239,7 +240,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle)
|
||||
struct bt_le_per_adv_sync *per_adv_sync = NULL;
|
||||
LOG_DBG("PaSyncFind[%u]", sync_handle);
|
||||
bt_le_host_lock();
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
bt_le_host_unlock();
|
||||
return per_adv_sync;
|
||||
}
|
||||
@@ -284,7 +285,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync) {
|
||||
LOG_WRN("PaSyncExist[%u]", sync_handle);
|
||||
return -EEXIST;
|
||||
@@ -327,7 +328,7 @@ int bt_le_per_adv_sync_delete(uint16_t sync_handle)
|
||||
|
||||
LOG_DBG("PaSyncDelete[%u]", sync_handle);
|
||||
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
|
||||
return -ENODEV;
|
||||
@@ -347,7 +348,7 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
|
||||
|
||||
LOG_DBG("PaSyncEstabListener[%u]", sync_handle);
|
||||
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
|
||||
return -ENODEV;
|
||||
@@ -374,10 +375,6 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
|
||||
}
|
||||
}
|
||||
|
||||
if (info.conn) {
|
||||
bt_conn_unref(info.conn);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -390,7 +387,7 @@ int bt_le_per_adv_sync_lost_listener(uint16_t sync_handle)
|
||||
|
||||
LOG_DBG("PaSyncLostListener[%u]", sync_handle);
|
||||
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
|
||||
return -ENODEV;
|
||||
@@ -426,7 +423,7 @@ int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
per_adv_sync = per_adv_sync_find(sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
|
||||
return -ENODEV;
|
||||
@@ -458,7 +455,7 @@ void hci_le_biginfo_adv_report(struct net_buf *buf)
|
||||
|
||||
/* LOG_DBG("BigInfoRecvListener[%u]", evt->sync_handle); */
|
||||
|
||||
per_adv_sync = per_adv_sync_find(evt->sync_handle);
|
||||
per_adv_sync = bt_le_per_adv_sync_find(evt->sync_handle);
|
||||
if (per_adv_sync == NULL) {
|
||||
LOG_ERR("PaSyncNotFound[%u]", evt->sync_handle);
|
||||
return;
|
||||
|
||||
@@ -36,7 +36,14 @@ static BT_ISO_CTRL_BSS_ATTR QueueSetHandle_t iso_queue_set;
|
||||
|
||||
static BT_ISO_CTRL_BSS_ATTR TaskHandle_t iso_task_handle;
|
||||
|
||||
extern void bt_le_timer_handle_event(void *arg);
|
||||
extern void bt_le_timer_handle_event(void *arg, size_t gen);
|
||||
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
/* Defined in esp_ble_audio (ots/adapter/l2cap.c) - the only L2CAP consumer is
|
||||
* OTS, so the shim lives there. Declared instead of included to keep esp_ble_iso
|
||||
* free of audio headers; both live in the bt component, so the link resolves. */
|
||||
extern void bt_le_l2cap_handle_event(void *data, size_t data_len);
|
||||
#endif
|
||||
|
||||
#if CONFIG_BT_ISO_DISPATCH_MONITOR
|
||||
/* Per-type dispatch timing, indexed by iso_queue_item_type. Written only by
|
||||
@@ -94,7 +101,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
|
||||
|
||||
switch (item->type) {
|
||||
case ISO_QUEUE_ITEM_TYPE_TIMER_EVENT:
|
||||
bt_le_timer_handle_event(item->data);
|
||||
bt_le_timer_handle_event(item->data, item->data_len);
|
||||
break;
|
||||
case ISO_QUEUE_ITEM_TYPE_GAP_EVENT:
|
||||
case ISO_QUEUE_ITEM_TYPE_EXT_ADV_REPORT:
|
||||
@@ -104,6 +111,11 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
|
||||
case ISO_QUEUE_ITEM_TYPE_GATT_EVENT:
|
||||
bt_le_gatt_handle_event(item->data, item->data_len);
|
||||
break;
|
||||
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
|
||||
case ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT:
|
||||
bt_le_l2cap_handle_event(item->data, item->data_len);
|
||||
break;
|
||||
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
|
||||
case ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT:
|
||||
case ISO_QUEUE_ITEM_TYPE_BIGINFO_ADV_REPORT:
|
||||
bt_le_iso_handle_hci_event(item->data, item->data_len);
|
||||
@@ -118,7 +130,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
|
||||
if (item->data) {
|
||||
free(item->data);
|
||||
}
|
||||
assert(0);
|
||||
BT_LE_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -188,9 +200,13 @@ int bt_le_iso_task_post(enum iso_queue_item_type type,
|
||||
wait = 0;
|
||||
break;
|
||||
default:
|
||||
/* Timer / GATT / HCI / GAP lifecycle: reliable, block until space. */
|
||||
/* Timer / GATT / HCI / GAP lifecycle. A self-post from iso_task must
|
||||
* not block (it is the sole consumer -> blocking on its own full queue
|
||||
* deadlocks); external producers get a bounded wait, not portMAX_DELAY,
|
||||
* so a wedged iso_task can't freeze esp_timer / the host task and stall
|
||||
* the ISO data path. */
|
||||
queue = iso_normal_queue;
|
||||
wait = portMAX_DELAY;
|
||||
wait = (xTaskGetCurrentTaskHandle() == iso_task_handle) ? 0 : K_QUEUE_SHORT;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ static void bt_iso_sent_cb(struct bt_conn *iso, void *user_data, int err)
|
||||
struct bt_iso_chan *chan = iso->iso.chan;
|
||||
struct bt_iso_chan_ops *ops;
|
||||
|
||||
assert(chan != NULL && "NUllConnForISOSentCb");
|
||||
BT_LE_ASSERT(chan != NULL && "NUllConnForISOSentCb");
|
||||
|
||||
ops = chan->ops;
|
||||
|
||||
@@ -135,7 +135,6 @@ void hci_iso(struct net_buf *buf)
|
||||
iso(buf)->index = bt_conn_index(iso);
|
||||
|
||||
bt_iso_recv(iso, buf, flags);
|
||||
bt_conn_unref(iso);
|
||||
}
|
||||
|
||||
static void iso_get_and_clear_cb(struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t *cb,
|
||||
@@ -288,6 +287,14 @@ static int validate_iso_setup_data_path_parms(const struct bt_iso_chan *chan, ui
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* LE Setup ISO Data Path is one HCI command (251B usable in the 255B pool):
|
||||
* sizeof(cp)+cc_len must fit, else uint8 param_len wraps and overflows. */
|
||||
CHECKIF(path->cc_len > 251 - sizeof(struct bt_hci_cp_le_setup_iso_path)) {
|
||||
LOG_ERR("InvCcLenTooLarge[%u]", path->cc_len);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -432,14 +439,14 @@ void bt_iso_connected(struct bt_conn *iso)
|
||||
|
||||
bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED);
|
||||
|
||||
if (chan->ops->connected) {
|
||||
if (chan->ops && chan->ops->connected) {
|
||||
chan->ops->connected(chan);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
{
|
||||
assert(chan->iso != NULL && "NullConnForIsoChan");
|
||||
BT_LE_ASSERT(chan->iso != NULL && "NullConnForIsoChan");
|
||||
|
||||
const uint8_t conn_type = chan->iso->iso.info.type;
|
||||
|
||||
@@ -452,12 +459,12 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
* the callback and to be more similar to the ACL disconnected callback. This also means
|
||||
* that the channel cannot be reused or memset in the callback
|
||||
*/
|
||||
if (chan->ops->disconnected) {
|
||||
if (chan->ops && chan->ops->disconnected) {
|
||||
chan->ops->disconnected(chan, reason);
|
||||
}
|
||||
|
||||
/* The peripheral does not have the concept of a CIG, so once a CIS
|
||||
* disconnects it is completely freed by unref'ing it
|
||||
* disconnects it is completely freed.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
|
||||
(conn_type == BT_ISO_CHAN_TYPE_CENTRAL || conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL)) {
|
||||
@@ -466,7 +473,6 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
if (conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL) {
|
||||
/* Release iso conn slot back to the pool. */
|
||||
chan->iso->type = BT_CONN_TYPE_NONE;
|
||||
bt_conn_unref(chan->iso);
|
||||
chan->iso = NULL;
|
||||
#if defined(CONFIG_BT_ISO_CENTRAL)
|
||||
} else {
|
||||
@@ -476,7 +482,7 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
|
||||
/* Update CIG state */
|
||||
cig = get_cig(chan);
|
||||
assert(cig != NULL && "CigNull");
|
||||
BT_LE_ASSERT(cig != NULL && "CigNull");
|
||||
|
||||
is_chan_connected = false;
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&cig->cis_channels, cis_chan, node) {
|
||||
@@ -679,7 +685,7 @@ void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags)
|
||||
chan = iso_chan(iso);
|
||||
if (chan == NULL) {
|
||||
LOG_ERR("NoChanForIsoRecv");
|
||||
} else if (chan->ops->recv != NULL) {
|
||||
} else if (chan->ops && chan->ops->recv) {
|
||||
chan->ops->recv(chan, &iso_info, buf->data, buf->len);
|
||||
}
|
||||
}
|
||||
@@ -854,12 +860,22 @@ int bt_iso_chan_disconnect(struct bt_iso_chan *chan)
|
||||
|
||||
void bt_iso_cleanup_acl(struct bt_conn *iso)
|
||||
{
|
||||
LOG_DBG("IsoCleanupAcl[%u]", iso->iso.acl ? iso->iso.acl->handle : UINT16_MAX);
|
||||
struct bt_conn *acl = iso->iso.acl;
|
||||
|
||||
if (iso->iso.acl) {
|
||||
bt_conn_unref(iso->iso.acl);
|
||||
iso->iso.acl = NULL;
|
||||
LOG_INF("IsoCleanupAcl[%u]", acl ? acl->handle : UINT16_MAX);
|
||||
|
||||
if (acl == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
iso->iso.acl = NULL;
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST
|
||||
/* Finalize deferred ACL wipe once no CIS still points at it. */
|
||||
if (acl->state == BT_CONN_DISCONNECTED) {
|
||||
(void)bt_le_acl_conn_delete(acl->handle);
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_UNICAST */
|
||||
}
|
||||
|
||||
static void store_cis_info(const struct bt_hci_evt_le_cis_established *evt, struct bt_conn *iso)
|
||||
@@ -1009,6 +1025,10 @@ static void store_cis_info_v2(const struct bt_hci_evt_le_cis_established_v2 *evt
|
||||
LOG_DBG("StoreCisInfoV2");
|
||||
|
||||
chan = iso_conn->chan;
|
||||
if (chan == NULL || chan->qos == NULL) {
|
||||
LOG_ERR("CisChanOrQosNullV2[%p]", chan);
|
||||
return;
|
||||
}
|
||||
rx = chan->qos->rx;
|
||||
tx = chan->qos->tx;
|
||||
|
||||
@@ -1085,8 +1105,6 @@ void hci_le_cis_established(struct net_buf *buf)
|
||||
iso->err = evt->status;
|
||||
bt_iso_disconnected(iso);
|
||||
} /* else we wait for disconnect event */
|
||||
|
||||
bt_conn_unref(iso);
|
||||
}
|
||||
|
||||
void hci_le_cis_established_v2(struct net_buf *buf)
|
||||
@@ -1127,8 +1145,6 @@ void hci_le_cis_established_v2(struct net_buf *buf)
|
||||
iso->err = evt->status;
|
||||
bt_iso_disconnected(iso);
|
||||
} /* else we wait for disconnect event */
|
||||
|
||||
bt_conn_unref(iso);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_ISO_PERIPHERAL)
|
||||
@@ -1286,7 +1302,6 @@ void hci_le_cis_req(struct net_buf *buf)
|
||||
if (iso) {
|
||||
LOG_ERR("InvCisHdl[%u]", cis_handle);
|
||||
hci_le_reject_cis(cis_handle, BT_HCI_ERR_CONN_LIMIT_EXCEEDED);
|
||||
bt_conn_unref(iso);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1301,8 +1316,6 @@ void hci_le_cis_req(struct net_buf *buf)
|
||||
/* Add ISO connection */
|
||||
iso = bt_conn_add_iso(acl);
|
||||
|
||||
bt_conn_unref(acl);
|
||||
|
||||
if (!iso) {
|
||||
LOG_ERR("AddCisToAclFail[%u]", acl_handle);
|
||||
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
|
||||
@@ -1318,7 +1331,8 @@ void hci_le_cis_req(struct net_buf *buf)
|
||||
if (err) {
|
||||
LOG_INF("AppRejectedCis[%d]", err);
|
||||
bt_iso_cleanup_acl(iso);
|
||||
bt_conn_unref(iso);
|
||||
/* Release the ISO slot back to the pool. */
|
||||
iso->type = BT_CONN_TYPE_NONE;
|
||||
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
|
||||
return;
|
||||
}
|
||||
@@ -1329,8 +1343,10 @@ void hci_le_cis_req(struct net_buf *buf)
|
||||
|
||||
err = hci_le_accept_cis(cis_handle);
|
||||
if (err) {
|
||||
bt_iso_cleanup_acl(iso);
|
||||
bt_conn_unref(iso);
|
||||
/* iso_accept already bound the app's chan: full teardown so it isn't left
|
||||
CONNECTING with a dangling chan->iso once the slot is released. */
|
||||
iso->err = BT_HCI_ERR_INSUFFICIENT_RESOURCES;
|
||||
bt_iso_disconnected(iso);
|
||||
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
|
||||
return;
|
||||
}
|
||||
@@ -1347,7 +1363,7 @@ static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iso->iso.acl = bt_conn_ref(acl);
|
||||
iso->iso.acl = acl;
|
||||
|
||||
return iso;
|
||||
}
|
||||
@@ -1651,7 +1667,7 @@ static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID");
|
||||
BT_LE_ASSERT(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID");
|
||||
|
||||
return &cigs[iso_chan->iso->iso.info.unicast.cig_id];
|
||||
}
|
||||
@@ -1726,7 +1742,6 @@ static void cleanup_cig(struct bt_iso_cig *cig)
|
||||
if (cis->iso != NULL) {
|
||||
/* Release iso conn slot back to the pool. */
|
||||
cis->iso->type = BT_CONN_TYPE_NONE;
|
||||
bt_conn_unref(cis->iso);
|
||||
cis->iso = NULL;
|
||||
}
|
||||
|
||||
@@ -1761,6 +1776,27 @@ static bool valid_cig_param(const struct bt_iso_cig_param *param, bool advanced,
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Built in the 255B HCI cmd pool (hci.c): BT_BUF_RESERVE + the 3B cmd header
|
||||
* eat into it first, and test params cost 14B per CIS instead of 9B, so
|
||||
* 31 CIS (spec max) never fits. Cap num_cis to the real budget. */
|
||||
{
|
||||
const size_t avail = 255U - BT_BUF_RESERVE - BT_HCI_CMD_HDR_SIZE;
|
||||
size_t hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params);
|
||||
size_t cis_sz = sizeof(struct bt_hci_cis_params);
|
||||
|
||||
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
|
||||
if (advanced) {
|
||||
hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params_test);
|
||||
cis_sz = sizeof(struct bt_hci_cis_params_test);
|
||||
}
|
||||
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
|
||||
|
||||
if (param->num_cis > (avail - hdr_sz) / cis_sz) {
|
||||
LOG_ERR("TooLargeNumCisForHciCmd[%u][%u]", param->num_cis, advanced);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < param->num_cis; i++) {
|
||||
struct bt_iso_chan *cis = param->cis_channels[i];
|
||||
|
||||
@@ -1991,7 +2027,8 @@ static void restore_cig(struct bt_iso_cig *cig, uint8_t existing_num_cis)
|
||||
* bt_iso_cig_reconfigure was called
|
||||
*/
|
||||
if (cis->iso != NULL && cis->iso->iso.info.unicast.cis_id >= existing_num_cis) {
|
||||
bt_conn_unref(cis->iso);
|
||||
/* Release the ISO slot back to the pool. */
|
||||
cis->iso->type = BT_CONN_TYPE_NONE;
|
||||
cis->iso = NULL;
|
||||
|
||||
sys_slist_remove(&cig->cis_channels, prev, &cis->node);
|
||||
@@ -2243,12 +2280,12 @@ int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count)
|
||||
struct bt_iso_chan *iso_chan = param[i].iso_chan;
|
||||
struct bt_iso_cig *cig;
|
||||
|
||||
iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl);
|
||||
iso_chan->iso->iso.acl = param[i].acl;
|
||||
bt_conn_set_state(iso_chan->iso, BT_CONN_INITIATING);
|
||||
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
|
||||
|
||||
cig = get_cig(iso_chan);
|
||||
assert(cig && "CigNull");
|
||||
BT_LE_ASSERT(cig && "CigNull");
|
||||
cig->state = BT_ISO_CIG_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
@@ -2313,7 +2350,6 @@ static void cleanup_big(struct bt_iso_big *big)
|
||||
if (bis->iso != NULL) {
|
||||
/* Release iso conn slot back to the pool. */
|
||||
bis->iso->type = BT_CONN_TYPE_NONE;
|
||||
bt_conn_unref(bis->iso);
|
||||
bis->iso = NULL;
|
||||
}
|
||||
|
||||
@@ -2428,7 +2464,7 @@ static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big,
|
||||
}
|
||||
|
||||
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
|
||||
assert(bis != NULL && "BisNull");
|
||||
BT_LE_ASSERT(bis != NULL && "BisNull");
|
||||
|
||||
/* All BIS will share the same QOS */
|
||||
qos = bis->qos->tx;
|
||||
@@ -2491,7 +2527,7 @@ static int hci_le_create_big_test(const struct bt_le_ext_adv *padv, struct bt_is
|
||||
}
|
||||
|
||||
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
|
||||
assert(bis != NULL && "BisNull");
|
||||
BT_LE_ASSERT(bis != NULL && "BisNull");
|
||||
|
||||
/* All BIS will share the same QOS */
|
||||
qos = bis->qos;
|
||||
@@ -2972,7 +3008,7 @@ int bt_iso_big_terminate(struct bt_iso_big *big)
|
||||
}
|
||||
|
||||
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
|
||||
assert(bis != NULL && "BisNull");
|
||||
BT_LE_ASSERT(bis != NULL && "BisNull");
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
|
||||
bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "utils/assert.h"
|
||||
|
||||
#define TAG "ISO_ASSERT"
|
||||
|
||||
/* Always logged (no LOG_LEVEL gate) — this is the last message before abort, and
|
||||
* the user needs the context to diagnose. */
|
||||
void bt_le_assert(const char *tag, size_t info,
|
||||
const char *file, int line, const char *func)
|
||||
{
|
||||
esp_log_write(ESP_LOG_ERROR, TAG,
|
||||
BT_ISO_LOG_COLOR_E
|
||||
"E (%lu) %s: Assert[%s][info=%u][%s:%d][%s]"
|
||||
BT_ISO_LOG_RESET_COLOR "\n",
|
||||
esp_log_timestamp(), TAG,
|
||||
tag, (unsigned)info, file, line, func);
|
||||
abort();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also registered as lib_ext_funcs._assert, so tag/info stay free-form for the
|
||||
* lib's own call sites; BT_LE_ASSERT() passes the stringified condition. */
|
||||
void bt_le_assert(const char *tag, size_t info,
|
||||
const char *file, int line, const char *func) __attribute__((noreturn));
|
||||
|
||||
/* Use instead of assert(): CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE sets
|
||||
* -DNDEBUG, which turns every assert() into a no-op and lets execution fall
|
||||
* through onto the very pointer it was guarding. This one always checks. */
|
||||
#define BT_LE_ASSERT(_cond) \
|
||||
do { \
|
||||
if (!(_cond)) { \
|
||||
bt_le_assert(#_cond, 0, __FILE__, __LINE__, __func__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -24,6 +24,8 @@
|
||||
#else
|
||||
#include "host/ble_gap.h"
|
||||
#include "host/ble_store.h"
|
||||
|
||||
#include "utils/assert.h"
|
||||
#endif
|
||||
|
||||
LOG_MODULE_REGISTER(ISO_UTILS, CONFIG_BT_ISO_LOG_LEVEL);
|
||||
@@ -40,7 +42,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
|
||||
int num = esp_ble_get_bond_device_num();
|
||||
esp_ble_bond_dev_t *list;
|
||||
|
||||
assert(func);
|
||||
BT_LE_ASSERT(func);
|
||||
(void)id;
|
||||
|
||||
LOG_DBG("[B]ForeachBond[%d]", num);
|
||||
@@ -135,7 +137,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
|
||||
ble_addr_t peers[CONFIG_BT_MAX_PAIRED];
|
||||
int num = 0;
|
||||
|
||||
assert(func);
|
||||
BT_LE_ASSERT(func);
|
||||
(void)id;
|
||||
|
||||
if (ble_store_util_bonded_peers(peers, &num, ARRAY_SIZE(peers)) != 0) {
|
||||
|
||||
@@ -28,11 +28,11 @@ static void iso_timer_cb(void *arg)
|
||||
struct k_work *work = arg;
|
||||
int err;
|
||||
|
||||
assert(work);
|
||||
assert(work->timer);
|
||||
assert(work->handler);
|
||||
BT_LE_ASSERT(work);
|
||||
BT_LE_ASSERT(work->timer);
|
||||
BT_LE_ASSERT(work->handler);
|
||||
|
||||
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, 0);
|
||||
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, work->gen);
|
||||
if (err) {
|
||||
LOG_ERR("TimerCbPostFail[%d]", err);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ static void iso_timer_cb(void *arg)
|
||||
|
||||
int k_work_submit(struct k_work *work)
|
||||
{
|
||||
assert(work);
|
||||
BT_LE_ASSERT(work);
|
||||
|
||||
if (work->handler == NULL) {
|
||||
LOG_WRN("TimerSubmitHdlrNull");
|
||||
@@ -65,7 +65,7 @@ bool k_work_is_pending(struct k_work *work)
|
||||
{
|
||||
bool is_pending;
|
||||
|
||||
assert(work);
|
||||
BT_LE_ASSERT(work);
|
||||
|
||||
if (work->handler == NULL) {
|
||||
LOG_WRN("TimerIsPendingHdlrNull");
|
||||
@@ -81,20 +81,24 @@ bool k_work_is_pending(struct k_work *work)
|
||||
|
||||
void k_work_init(struct k_work *work, k_work_handler_t handler)
|
||||
{
|
||||
assert(work);
|
||||
BT_LE_ASSERT(work);
|
||||
|
||||
/* Clear timer/timeout_us/gen too: callers must not have to zero-init */
|
||||
memset(work, 0, sizeof(*work));
|
||||
|
||||
work->handler = handler;
|
||||
}
|
||||
|
||||
struct k_work_delayable *k_work_delayable_from_work(struct k_work *work)
|
||||
{
|
||||
assert(work);
|
||||
BT_LE_ASSERT(work);
|
||||
return (struct k_work_delayable *)work;
|
||||
}
|
||||
|
||||
void k_work_init_delayable(struct k_work_delayable *dwork,
|
||||
k_work_handler_t handler)
|
||||
{
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
const esp_timer_create_args_t timer_args = {
|
||||
.callback = &iso_timer_cb,
|
||||
@@ -123,13 +127,18 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork)
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_INF("TimerDeinitNotCreated");
|
||||
return;
|
||||
}
|
||||
|
||||
dwork->work.gen++;
|
||||
|
||||
/* esp_timer_delete rejects a running timer */
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
|
||||
err = esp_timer_delete(dwork->work.timer);
|
||||
if (err) {
|
||||
LOG_ERR("TimerDeinitDelFail[%d]", err);
|
||||
@@ -141,7 +150,7 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork)
|
||||
|
||||
int k_work_cancel_delayable(struct k_work_delayable *dwork)
|
||||
{
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_INF("TimerCancelNotCreated");
|
||||
@@ -149,6 +158,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork)
|
||||
}
|
||||
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
dwork->work.gen++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -156,7 +166,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork)
|
||||
bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork,
|
||||
struct k_work_sync *sync)
|
||||
{
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
ARG_UNUSED(sync);
|
||||
|
||||
@@ -167,6 +177,8 @@ bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork,
|
||||
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
|
||||
dwork->work.gen++;
|
||||
|
||||
/* TODO: check the return result */
|
||||
return false;
|
||||
}
|
||||
@@ -175,7 +187,7 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms)
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_WRN("TimerSchNotCreated");
|
||||
@@ -184,6 +196,8 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms)
|
||||
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
|
||||
dwork->work.gen++;
|
||||
|
||||
dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000;
|
||||
|
||||
if (ms == K_NO_WAIT) {
|
||||
@@ -204,7 +218,7 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms)
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_WRN("TimerReschNotCreated");
|
||||
@@ -213,6 +227,8 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms)
|
||||
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
|
||||
dwork->work.gen++;
|
||||
|
||||
dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000;
|
||||
|
||||
if (ms == K_NO_WAIT) {
|
||||
@@ -233,8 +249,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(dwork);
|
||||
assert(period_us > 0);
|
||||
BT_LE_ASSERT(dwork);
|
||||
BT_LE_ASSERT(period_us > 0);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_WRN("TimerPeriodicNotCreated");
|
||||
@@ -243,6 +259,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_
|
||||
|
||||
esp_timer_stop(dwork->work.timer);
|
||||
|
||||
dwork->work.gen++;
|
||||
|
||||
err = esp_timer_start_periodic(dwork->work.timer, period_us);
|
||||
if (err) {
|
||||
LOG_ERR("TimerPeriodicStartFail[%d]", err);
|
||||
@@ -265,7 +283,7 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork)
|
||||
k_timeout_t timeout;
|
||||
int64_t delta_us;
|
||||
|
||||
assert(dwork);
|
||||
BT_LE_ASSERT(dwork);
|
||||
|
||||
if (dwork->work.timer == NULL) {
|
||||
LOG_WRN("TimerRemainingNotCreated");
|
||||
@@ -286,9 +304,14 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork)
|
||||
return timeout;
|
||||
}
|
||||
|
||||
void bt_le_timer_handle_event(void *arg)
|
||||
void bt_le_timer_handle_event(void *arg, size_t gen)
|
||||
{
|
||||
struct k_work *work = arg;
|
||||
|
||||
k_work_submit_safe(work);
|
||||
bt_le_host_lock();
|
||||
/* Drop the event if the work was re-armed or cancelled after it fired */
|
||||
if ((uint32_t)gen == work->gen) {
|
||||
k_work_submit(work);
|
||||
}
|
||||
bt_le_host_unlock();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#endif /* CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER */
|
||||
|
||||
#define CONFIG_BT_MAX_CONN CONFIG_BT_ACL_CONNECTIONS
|
||||
#define CONFIG_BT_SMP CONFIG_BT_BLE_SMP_ENABLE
|
||||
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_SMP_MAX_BONDS
|
||||
|
||||
#if CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST
|
||||
@@ -57,7 +56,6 @@ _Static_assert(CONFIG_BT_ISO_MAX_CHAN == 0, "Too large ISO channels");
|
||||
#else /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
#define CONFIG_BT_MAX_CONN CONFIG_BT_NIMBLE_MAX_CONNECTIONS
|
||||
#define CONFIG_BT_SMP CONFIG_BT_NIMBLE_SECURITY_ENABLE
|
||||
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_NIMBLE_MAX_BONDS
|
||||
|
||||
#if CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES
|
||||
|
||||
@@ -231,44 +231,6 @@ int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info);
|
||||
*/
|
||||
int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason);
|
||||
|
||||
/** @brief Set security level for a connection.
|
||||
*
|
||||
* This function enable security (encryption) for a connection. If the device
|
||||
* has bond information for the peer with sufficiently strong key encryption
|
||||
* will be enabled. If the connection is already encrypted with sufficiently
|
||||
* strong key this function does nothing.
|
||||
*
|
||||
* If the device has no bond information for the peer and is not already paired
|
||||
* then the pairing procedure will be initiated. Note that @p sec has no effect
|
||||
* on the security level selected for the pairing process. The selection is
|
||||
* instead controlled by the values of the registered @ref bt_conn_auth_cb. If
|
||||
* the device has bond information or is already paired and the keys are too
|
||||
* weak then the pairing procedure will be initiated.
|
||||
*
|
||||
* This function may return an error if the required level of security defined using
|
||||
* @p sec is not possible to achieve due to local or remote device limitation
|
||||
* (e.g., input output capabilities), or if the maximum number of paired devices
|
||||
* has been reached.
|
||||
*
|
||||
* This function may return an error if the pairing procedure has already been
|
||||
* initiated by the local device or the peer device.
|
||||
*
|
||||
* @note When @kconfig{CONFIG_BT_SMP_SC_ONLY} is enabled then the security
|
||||
* level will always be level 4.
|
||||
*
|
||||
* @note When @kconfig{CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY} is enabled then the
|
||||
* security level will always be level 3.
|
||||
*
|
||||
* @note When @ref BT_SECURITY_FORCE_PAIR within @p sec is enabled then the pairing
|
||||
* procedure will always be initiated.
|
||||
*
|
||||
* @param conn Connection object.
|
||||
* @param sec Requested minimum security level.
|
||||
*
|
||||
* @return 0 on success or negative error
|
||||
*/
|
||||
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec);
|
||||
|
||||
enum bt_security_err {
|
||||
/** Security procedure successful. */
|
||||
BT_SECURITY_ERR_SUCCESS,
|
||||
@@ -417,6 +379,7 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb);
|
||||
* @retval -ENOENT if @p cb was not registered
|
||||
*/
|
||||
int bt_conn_cb_unregister(struct bt_conn_cb *cb);
|
||||
int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb);
|
||||
|
||||
/**
|
||||
* @brief Register a callback structure for connection events.
|
||||
|
||||
@@ -1294,6 +1294,8 @@ int bt_iso_big_terminate_safe(struct bt_iso_big *big);
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_iso_big_sync(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param,
|
||||
struct bt_iso_big **out_big);
|
||||
int bt_iso_big_sync_safe(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param,
|
||||
struct bt_iso_big **out_big);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/sys/slist.h>
|
||||
@@ -21,6 +20,8 @@
|
||||
#include "freertos/semphr.h"
|
||||
#include "toolchain.h"
|
||||
|
||||
#include "utils/assert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -36,17 +37,17 @@ struct k_mutex {
|
||||
|
||||
static inline void k_mutex_create(struct k_mutex *mutex)
|
||||
{
|
||||
assert(mutex);
|
||||
assert(mutex->handle == NULL);
|
||||
BT_LE_ASSERT(mutex);
|
||||
BT_LE_ASSERT(mutex->handle == NULL);
|
||||
|
||||
mutex->handle = xSemaphoreCreateRecursiveMutex();
|
||||
assert(mutex->handle);
|
||||
BT_LE_ASSERT(mutex->handle);
|
||||
}
|
||||
|
||||
static inline void k_mutex_delete(struct k_mutex *mutex)
|
||||
{
|
||||
assert(mutex);
|
||||
assert(mutex->handle);
|
||||
BT_LE_ASSERT(mutex);
|
||||
BT_LE_ASSERT(mutex->handle);
|
||||
|
||||
vSemaphoreDelete(mutex->handle);
|
||||
mutex->handle = NULL;
|
||||
@@ -65,8 +66,8 @@ static inline void k_mutex_delete(struct k_mutex *mutex)
|
||||
|
||||
static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout)
|
||||
{
|
||||
assert(mutex);
|
||||
assert(mutex->handle);
|
||||
BT_LE_ASSERT(mutex);
|
||||
BT_LE_ASSERT(mutex->handle);
|
||||
|
||||
if (xSemaphoreTakeRecursive(mutex->handle, timeout) == pdTRUE) {
|
||||
return 0;
|
||||
@@ -89,8 +90,8 @@ static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout)
|
||||
|
||||
static inline int k_mutex_unlock(struct k_mutex *mutex)
|
||||
{
|
||||
assert(mutex);
|
||||
assert(mutex->handle);
|
||||
BT_LE_ASSERT(mutex);
|
||||
BT_LE_ASSERT(mutex->handle);
|
||||
|
||||
if (xSemaphoreGiveRecursive(mutex->handle) != pdTRUE) {
|
||||
K_MUTEX_LOG_ERR("UnlockFail");
|
||||
@@ -112,18 +113,18 @@ struct k_sem {
|
||||
|
||||
static inline void k_sem_create(struct k_sem *sem)
|
||||
{
|
||||
assert(sem);
|
||||
assert(sem->handle == NULL);
|
||||
BT_LE_ASSERT(sem);
|
||||
BT_LE_ASSERT(sem->handle == NULL);
|
||||
|
||||
sem->handle = xSemaphoreCreateBinary();
|
||||
assert(sem->handle);
|
||||
BT_LE_ASSERT(sem->handle);
|
||||
sem->result = 0;
|
||||
}
|
||||
|
||||
static inline void k_sem_delete(struct k_sem *sem)
|
||||
{
|
||||
assert(sem);
|
||||
assert(sem->handle);
|
||||
BT_LE_ASSERT(sem);
|
||||
BT_LE_ASSERT(sem->handle);
|
||||
|
||||
vSemaphoreDelete(sem->handle);
|
||||
sem->handle = NULL;
|
||||
@@ -141,8 +142,8 @@ static inline void k_sem_delete(struct k_sem *sem)
|
||||
|
||||
static inline int k_sem_take(struct k_sem *sem, uint32_t timeout)
|
||||
{
|
||||
assert(sem);
|
||||
assert(sem->handle);
|
||||
BT_LE_ASSERT(sem);
|
||||
BT_LE_ASSERT(sem->handle);
|
||||
|
||||
/* Do NOT touch sem->result here. The producer may have already written
|
||||
* it and called k_sem_give before this take ran (BTU/HCI cb on a
|
||||
@@ -164,8 +165,8 @@ static inline int k_sem_take(struct k_sem *sem, uint32_t timeout)
|
||||
|
||||
static inline int k_sem_give(struct k_sem *sem)
|
||||
{
|
||||
assert(sem);
|
||||
assert(sem->handle);
|
||||
BT_LE_ASSERT(sem);
|
||||
BT_LE_ASSERT(sem->handle);
|
||||
|
||||
if (xSemaphoreGive(sem->handle) != pdTRUE) {
|
||||
K_SEM_LOG_ERR("GiveFail");
|
||||
@@ -181,13 +182,20 @@ static inline int k_sem_give(struct k_sem *sem)
|
||||
* caller would see uninitialized response data. */
|
||||
static inline void k_sem_reset(struct k_sem *sem)
|
||||
{
|
||||
assert(sem);
|
||||
assert(sem->handle);
|
||||
BT_LE_ASSERT(sem);
|
||||
BT_LE_ASSERT(sem->handle);
|
||||
|
||||
xQueueReset(sem->handle);
|
||||
sem->result = 0;
|
||||
}
|
||||
|
||||
/* Queue */
|
||||
|
||||
/* Bounded wait for a reliable-tier task-queue post instead of portMAX_DELAY,
|
||||
* so a wedged consumer can't freeze an external producer (esp_timer / host
|
||||
* task) and stall the ISO data path. */
|
||||
#define K_QUEUE_SHORT (1000 / portTICK_PERIOD_MS)
|
||||
|
||||
/* Timer */
|
||||
|
||||
typedef uint32_t k_timeout_t;
|
||||
@@ -202,6 +210,10 @@ typedef uint32_t k_timeout_t;
|
||||
#define K_MINUTES(m) K_SECONDS((m) * 60)
|
||||
#define K_HOURS(h) K_MINUTES((h) * 60)
|
||||
|
||||
/* Defer work onto iso_task. In this port K_NO_WAIT runs the handler INLINE
|
||||
* (timer.c), which fires e.g. GATT indications before the CP write response. */
|
||||
#define K_NO_WAIT_ASYNC K_MSEC(1)
|
||||
|
||||
struct k_work;
|
||||
|
||||
typedef void (*k_work_handler_t)(struct k_work *work);
|
||||
@@ -211,6 +223,8 @@ struct k_work {
|
||||
k_work_handler_t handler;
|
||||
int64_t timeout_us;
|
||||
void *user_data;
|
||||
uint32_t gen; /* Bumped on (re)schedule/cancel/deinit so a timer event
|
||||
* queued before the change is skipped (see timer.c). */
|
||||
};
|
||||
|
||||
struct k_work_sync {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user