feat(ble_iso): Split ISO related files into components/bt/esp_ble_iso

This commit is contained in:
Liu Linyan
2026-05-06 16:48:27 +08:00
parent 16f8e1fda1
commit e0c8786166
139 changed files with 212 additions and 187 deletions
+142
View File
@@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Nordic Semiconductor ASA
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* 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/conn.h>
#include <../host/hci_core.h>
#include "common/host.h"
static struct bt_le_ext_adv ext_adv_pool[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
static struct bt_le_ext_adv *ext_adv_find(uint8_t adv_handle)
{
struct bt_le_ext_adv *adv = NULL;
for (size_t i = 0; i < ARRAY_SIZE(ext_adv_pool); i++) {
if (atomic_test_bit(ext_adv_pool[i].flags,
BT_PER_ADV_PARAMS_SET) &&
ext_adv_pool[i].handle == adv_handle) {
LOG_DBG("ExtAdvFound[%u][%u]", i, adv_handle);
adv = &ext_adv_pool[i];
break;
}
}
return adv;
}
static struct bt_le_ext_adv *ext_adv_new(void)
{
struct bt_le_ext_adv *adv = NULL;
for (size_t i = 0; i < ARRAY_SIZE(ext_adv_pool); i++) {
if (atomic_test_bit(ext_adv_pool[i].flags,
BT_PER_ADV_PARAMS_SET) == false) {
adv = &ext_adv_pool[i];
memset(adv, 0, sizeof(*adv));
atomic_set_bit(adv->flags, BT_PER_ADV_PARAMS_SET);
break;
}
}
return adv;
}
static void ext_adv_delete(struct bt_le_ext_adv *adv)
{
atomic_clear_bit(adv->flags, BT_PER_ADV_PARAMS_SET);
}
_IDF_ONLY
struct bt_le_ext_adv *bt_le_ext_adv_find(uint8_t adv_handle)
{
LOG_DBG("ExtAdvFind[%u]", adv_handle);
return ext_adv_find(adv_handle);
}
_IDF_ONLY
int bt_le_ext_adv_new_safe(uint8_t adv_handle)
{
struct bt_le_ext_adv *adv = NULL;
int err = 0;
bt_le_host_lock();
adv = ext_adv_find(adv_handle);
if (adv) {
LOG_WRN("ExtAdvExist[%u]", adv_handle);
err = -EEXIST;
goto end;
}
adv = ext_adv_new();
if (adv == NULL) {
LOG_ERR("NoFreeExtAdv[%u]", adv_handle);
err = -ENOMEM;
goto end;
}
adv->handle = adv_handle;
end:
bt_le_host_unlock();
return err;
}
_IDF_ONLY
int bt_le_ext_adv_delete_safe(uint8_t adv_handle)
{
struct bt_le_ext_adv *adv = NULL;
int err = 0;
bt_le_host_lock();
adv = ext_adv_find(adv_handle);
if (adv == NULL) {
LOG_ERR("ExtAdvNotFound[%u]", adv_handle);
err = -ENODEV;
goto end;
}
ext_adv_delete(adv);
end:
bt_le_host_unlock();
return err;
}
_LIB_ONLY
int bt_le_ext_adv_get_info(const struct bt_le_ext_adv *adv,
struct bt_le_ext_adv_info *info)
{
if (adv == NULL || info == NULL) {
LOG_ERR("ParamNull[%p][%p]", adv, info);
return -EINVAL;
}
if (!IS_ARRAY_ELEMENT(ext_adv_pool, adv)) {
LOG_ERR("ExtAdvNotInPool[%p]", adv);
return -EINVAL;
}
/* Force to use ENABLED state for LIB usage */
info->ext_adv_state = BT_LE_EXT_ADV_STATE_ENABLED;
info->per_adv_state = BT_LE_PER_ADV_STATE_ENABLED;
return 0;
}
@@ -0,0 +1,473 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci.h>
#include <../host/hci_core.h>
#include <../host/iso_internal.h>
#include "common/host.h"
#include "common/app/gap.h"
static bt_le_gap_app_cb gap_app_cb;
int bt_le_gap_app_cb_register(bt_le_gap_app_cb cb)
{
LOG_DBG("GapAppCbReg");
if (cb == NULL) {
LOG_ERR("GapAppCbNull");
return -EINVAL;
}
if (gap_app_cb) {
LOG_WRN("GapAppCbExist");
return -EALREADY;
}
gap_app_cb = cb;
return 0;
}
void bt_le_gap_app_cb_unregister(void)
{
if (gap_app_cb) {
LOG_DBG("GapAppCbUnreg");
gap_app_cb = NULL;
}
}
static inline void bt_le_gap_app_cb_evt(struct bt_le_gap_app_event *event)
{
if (gap_app_cb) {
gap_app_cb(event);
}
}
struct biginfo {
uint16_t sync_handle;
uint8_t num_bis;
uint8_t nse;
uint16_t iso_interval;
uint8_t bn;
uint8_t pto;
uint8_t irc;
uint16_t max_pdu;
uint8_t sdu_interval[3];
uint16_t max_sdu;
uint8_t phy;
uint8_t framing;
uint8_t encryption;
} __packed;
void bt_le_gap_app_biginfo_event(uint8_t *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_BIGINFO_RECV,
};
struct biginfo *biginfo;
biginfo = (void *)param;
event.biginfo_recv.sync_handle = biginfo->sync_handle;
event.biginfo_recv.num_bis = biginfo->num_bis;
event.biginfo_recv.nse = biginfo->nse;
event.biginfo_recv.iso_interval = biginfo->iso_interval;
event.biginfo_recv.bn = biginfo->bn;
event.biginfo_recv.pto = biginfo->pto;
event.biginfo_recv.irc = biginfo->irc;
event.biginfo_recv.max_pdu = biginfo->max_pdu;
event.biginfo_recv.sdu_interval = sys_get_le24(biginfo->sdu_interval);
event.biginfo_recv.max_sdu = biginfo->max_sdu;
event.biginfo_recv.phy = biginfo->phy;
event.biginfo_recv.framing = biginfo->framing;
event.biginfo_recv.encryption = biginfo->encryption;
bt_le_gap_app_cb_evt(&event);
}
static void handle_ext_scan_recv_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_EXT_SCAN_RECV,
};
event.ext_scan_recv.event_type = param->ext_scan_recv.event_type;
event.ext_scan_recv.addr.type = param->ext_scan_recv.addr.type;
memcpy(event.ext_scan_recv.addr.val, param->ext_scan_recv.addr.val, BT_ADDR_SIZE);
event.ext_scan_recv.rssi = param->ext_scan_recv.rssi;
event.ext_scan_recv.tx_power = param->ext_scan_recv.tx_power;
event.ext_scan_recv.sid = param->ext_scan_recv.sid;
event.ext_scan_recv.pri_phy = param->ext_scan_recv.pri_phy;
event.ext_scan_recv.sec_phy = param->ext_scan_recv.sec_phy;
event.ext_scan_recv.per_adv_itvl = param->ext_scan_recv.per_adv_itvl;
event.ext_scan_recv.data_len = param->ext_scan_recv.data_len;
event.ext_scan_recv.data = param->ext_scan_recv.data;
bt_le_host_lock();
bt_le_scan_recv_listener(event.ext_scan_recv.event_type,
event.ext_scan_recv.addr.type,
event.ext_scan_recv.addr.val,
event.ext_scan_recv.rssi,
event.ext_scan_recv.tx_power,
event.ext_scan_recv.sid,
event.ext_scan_recv.pri_phy,
event.ext_scan_recv.sec_phy,
event.ext_scan_recv.per_adv_itvl,
event.ext_scan_recv.data_len,
event.ext_scan_recv.data);
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
if (param->ext_scan_recv.data) {
free(param->ext_scan_recv.data);
}
}
static void handle_pa_sync_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_PA_SYNC,
};
struct bt_le_per_adv_sync *per_adv_sync;
int err;
event.pa_sync.status = param->pa_sync.status;
event.pa_sync.sync_handle = param->pa_sync.sync_handle;
event.pa_sync.addr.type = param->pa_sync.addr.type;
memcpy(event.pa_sync.addr.val, param->pa_sync.addr.val, BT_ADDR_SIZE);
event.pa_sync.sid = param->pa_sync.sid;
event.pa_sync.adv_phy = param->pa_sync.adv_phy;
event.pa_sync.per_adv_itvl = param->pa_sync.per_adv_itvl;
event.pa_sync.adv_ca = param->pa_sync.adv_ca;
bt_le_host_lock();
if (event.pa_sync.status) {
goto end;
}
err = bt_le_per_adv_sync_new(event.pa_sync.sync_handle,
event.pa_sync.sid,
event.pa_sync.adv_phy,
event.pa_sync.per_adv_itvl,
event.pa_sync.addr.type,
event.pa_sync.addr.val,
&per_adv_sync);
if (err) {
goto end;
}
err = bt_le_per_adv_sync_establish_listener(event.pa_sync.sync_handle);
assert(err == 0);
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_pa_sync_lost_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_PA_SYNC_LOST,
};
int err;
event.pa_sync_lost.sync_handle = param->pa_sync_lost.sync_handle;
event.pa_sync_lost.reason = param->pa_sync_lost.reason;
bt_le_host_lock();
err = bt_le_per_adv_sync_lost_listener(event.pa_sync_lost.sync_handle);
if (err) {
goto end;
}
err = bt_le_per_adv_sync_delete(event.pa_sync_lost.sync_handle);
assert(err == 0);
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_pa_sync_recv_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_PA_SYNC_RECV,
};
event.pa_sync_recv.sync_handle = param->pa_sync_recv.sync_handle;
event.pa_sync_recv.tx_power = param->pa_sync_recv.tx_power;
event.pa_sync_recv.rssi = param->pa_sync_recv.rssi;
event.pa_sync_recv.data_status = param->pa_sync_recv.data_status;
event.pa_sync_recv.data_len = param->pa_sync_recv.data_len;
event.pa_sync_recv.data = param->pa_sync_recv.data;
bt_le_host_lock();
bt_le_per_adv_sync_report_recv_listener(event.pa_sync_recv.sync_handle,
event.pa_sync_recv.data,
event.pa_sync_recv.data_len);
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
if (param->pa_sync_recv.data) {
free(param->pa_sync_recv.data);
}
}
static void handle_acl_connect_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_ACL_CONNECT,
};
struct bt_conn *conn;
bt_addr_le_t dst;
int err;
event.acl_connect.status = param->acl_connect.status;
event.acl_connect.conn_handle = param->acl_connect.conn_handle;
event.acl_connect.role = param->acl_connect.role;
event.acl_connect.dst.type = param->acl_connect.dst.type;
memcpy(event.acl_connect.dst.val, param->acl_connect.dst.val, BT_ADDR_SIZE);
bt_le_host_lock();
if (event.acl_connect.status) {
goto end;
}
conn = bt_le_acl_conn_find(event.acl_connect.conn_handle);
if (conn) {
LOG_WRN("AclConnExist[%u]", event.acl_connect.conn_handle);
goto end;
}
dst.type = event.acl_connect.dst.type;
memcpy(dst.a.val, event.acl_connect.dst.val, BT_ADDR_SIZE);
/* Not encrypted at this moment */
err = bt_le_acl_conn_new(event.acl_connect.conn_handle,
event.acl_connect.role,
&dst, BT_SECURITY_L1);
if (err) {
goto end;
}
err = bt_le_acl_conn_connected_listener(event.acl_connect.conn_handle);
assert(err == 0);
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_acl_disconnect_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_ACL_DISCONNECT,
};
int err;
event.acl_disconnect.conn_handle = param->acl_disconnect.conn_handle;
event.acl_disconnect.reason = param->acl_disconnect.reason;
bt_le_host_lock();
err = bt_le_acl_conn_disconnected_listener(event.acl_disconnect.conn_handle,
event.acl_disconnect.reason);
if (err) {
goto end;
}
err = bt_le_acl_conn_delete(event.acl_disconnect.conn_handle);
assert(err == 0);
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_SECURITY_CHANGE,
};
struct bt_conn *conn;
bt_addr_le_t dst;
bool encrypted;
int err;
event.security_change.status = param->security_change.status;
event.security_change.conn_handle = param->security_change.conn_handle;
event.security_change.role = param->security_change.role;
event.security_change.sec_level = param->security_change.sec_level;
event.security_change.bonded = param->security_change.bonded;
event.security_change.dst.type = param->security_change.dst.type;
memcpy(event.security_change.dst.val, param->security_change.dst.val, BT_ADDR_SIZE);
encrypted = false;
bt_le_host_lock();
if (event.security_change.status) {
goto end;
}
conn = bt_le_acl_conn_find(event.security_change.conn_handle);
if (conn == NULL) {
dst.type = event.security_change.dst.type;
memcpy(dst.a.val, event.security_change.dst.val, BT_ADDR_SIZE);
err = bt_le_acl_conn_new(event.security_change.conn_handle,
event.security_change.role,
&dst, event.security_change.sec_level);
if (err) {
goto end;
}
} else {
err = bt_le_acl_conn_update(event.security_change.conn_handle,
event.security_change.sec_level,
&encrypted);
assert(err == 0);
}
err = bt_le_acl_conn_security_changed_listener(event.security_change.conn_handle,
event.security_change.sec_level);
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);
}
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_identity_resolve_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_IDENTITY_RESOLVE,
};
bt_addr_le_t identity;
bt_addr_le_t rpa;
event.identity_resolve.conn_handle = param->identity_resolve.conn_handle;
event.identity_resolve.rpa.type = param->identity_resolve.rpa.type;
memcpy(event.identity_resolve.rpa.val, param->identity_resolve.rpa.val, BT_ADDR_SIZE);
event.identity_resolve.identity.type = param->identity_resolve.identity.type;
memcpy(event.identity_resolve.identity.val, param->identity_resolve.identity.val, BT_ADDR_SIZE);
identity.type = event.identity_resolve.identity.type;
memcpy(identity.a.val, event.identity_resolve.identity.val, BT_ADDR_SIZE);
rpa.type = event.identity_resolve.rpa.type;
memcpy(rpa.a.val, event.identity_resolve.rpa.val, BT_ADDR_SIZE);
bt_le_host_lock();
bt_le_acl_conn_identity_resolved_listener(event.identity_resolve.conn_handle, &rpa, &identity);
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_bond_delete_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_BOND_DELETE,
};
bt_addr_le_t peer;
event.bond_delete.id = param->bond_delete.id;
event.bond_delete.peer.type = param->bond_delete.peer.type;
memcpy(event.bond_delete.peer.val, param->bond_delete.peer.val, BT_ADDR_SIZE);
peer.type = event.bond_delete.peer.type;
memcpy(peer.a.val, event.bond_delete.peer.val, sizeof(peer.a.val));
bt_le_host_lock();
bt_le_acl_conn_bond_deleted_listener(event.bond_delete.id, &peer);
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
{
struct bt_le_gap_app_param *param;
assert(data && data_len);
param = (struct bt_le_gap_app_param *)data;
switch (param->type) {
case BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV:
handle_ext_scan_recv_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC:
handle_pa_sync_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_LOST:
handle_pa_sync_lost_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_RECV:
handle_pa_sync_recv_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_ACL_CONNECT:
handle_acl_connect_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_ACL_DISCONNECT:
handle_acl_disconnect_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_SECURITY_CHANGE:
handle_security_change_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_IDENTITY_RESOLVE:
handle_identity_resolve_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_BOND_DELETE:
handle_bond_delete_event_safe(param);
break;
default:
assert(0);
break;
}
free(data);
}
void bt_le_gap_app_post_event(uint8_t type, void *param)
{
/* Currently type is not used */
ARG_UNUSED(type);
bt_le_nimble_gap_post_event(param);
}
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci.h>
#include <../host/hci_core.h>
#include <../host/iso_internal.h>
#include "common/host.h"
#include "common/app/gatt.h"
static bt_le_gatt_app_cb gatt_app_cb;
int bt_le_gatt_app_cb_register(bt_le_gatt_app_cb cb)
{
LOG_DBG("GattAppCbReg");
if (cb == NULL) {
LOG_ERR("GattAppCbNull");
return -EINVAL;
}
if (gatt_app_cb) {
LOG_WRN("GattAppCbExist");
return -EALREADY;
}
gatt_app_cb = cb;
return 0;
}
void bt_le_gatt_app_cb_unregister(void)
{
if (gatt_app_cb) {
LOG_DBG("GattAppCbUnreg");
gatt_app_cb = NULL;
}
}
static inline void bt_le_gatt_app_cb_evt(struct bt_le_gatt_app_event *event)
{
if (gatt_app_cb) {
gatt_app_cb(event);
}
}
void bt_le_gatt_app_mtu_change_event(uint16_t conn_handle, uint16_t mtu)
{
struct bt_le_gatt_app_event event = {
.type = BT_LE_GATT_APP_EVENT_GATT_MTU_CHANGE,
};
LOG_DBG("GattMtuChangeAppEvt[%u][%u]", conn_handle, mtu);
event.gatt_mtu_change.conn_handle = conn_handle;
event.gatt_mtu_change.mtu = mtu;
bt_le_gatt_app_cb_evt(&event);
}
void bt_le_gattc_app_disc_cmpl_event(struct bt_le_gattc_disc_cmpl_event *param)
{
struct bt_le_gatt_app_event event = {
.type = BT_LE_GATT_APP_EVENT_GATTC_DISC_CMPL,
};
LOG_DBG("GattcDiscCmplAppEvt[%u][%02x]", param->conn_handle, param->status);
event.gattc_disc_cmpl.status = param->status;
event.gattc_disc_cmpl.conn_handle = param->conn_handle;
bt_le_gatt_app_cb_evt(&event);
}
void bt_le_gatts_app_subscribe_event(struct bt_le_gatts_subscribe_event *param)
{
struct bt_le_gatt_app_event event = {
.type = BT_LE_GATT_APP_EVENT_GATTS_SUBSCRIBE,
};
/* LOG_DBG("GattsSubAppEvt[%d][%u][%u][%u][%u][%u][%u][%02x]", */
/* err, param->conn_handle, param->attr_handle, param->prev_notify, */
/* param->cur_notify, param->prev_indicate, param->cur_indicate, */
/* param->reason); */
event.gatts_subscribe.conn_handle = param->conn_handle;
event.gatts_subscribe.attr_handle = param->attr_handle;
event.gatts_subscribe.prev_notify = param->prev_notify;
event.gatts_subscribe.cur_notify = param->cur_notify;
event.gatts_subscribe.prev_indicate = param->prev_indicate;
event.gatts_subscribe.cur_indicate = param->cur_indicate;
event.gatts_subscribe.reason = param->reason;
bt_le_gatt_app_cb_evt(&event);
}
void bt_le_gatt_app_post_event(uint8_t type, void *param)
{
/* Currently type is not used */
ARG_UNUSED(type);
bt_le_nimble_gatt_post_event(param);
}
@@ -0,0 +1,672 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2025 Nordic Semiconductor ASA
* 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/common/bt_str.h>
#include <../host/keys.h>
#include <../host/conn_internal.h>
#include <../host/iso_internal.h>
#include "common/host.h"
#include "common/app/gap.h"
static struct bt_conn acl_conns[CONFIG_BT_MAX_CONN];
extern struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN];
static sys_slist_t conn_cbs = SYS_SLIST_STATIC_INIT(&conn_cbs);
static sys_slist_t auth_info_cbs = SYS_SLIST_STATIC_INIT(&auth_info_cbs);
_IDF_ONLY
void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count)
{
LOG_DBG("GetAclConns[%u]", ARRAY_SIZE(acl_conns));
*conns = acl_conns;
*count = ARRAY_SIZE(acl_conns);
}
_IDF_ONLY
bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
const bt_addr_le_t *peer)
{
/* LOG_DBG("IsLeAddr[%s][%s]", bt_addr_le_str(&conn->le.dst), bt_addr_le_str(peer)); */
ARG_UNUSED(id);
assert(conn && peer);
/* Check against conn dst address as it may be the identity address */
if (bt_addr_le_eq(peer, &conn->le.dst)) {
return true;
}
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);
for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = &acl_conns[i];
/* Add this to avoid unexpected error log */
if (conn->type == BT_CONN_TYPE_LE) {
func(&acl_conns[i], data);
}
}
}
_LIB_ONLY
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn)
{
assert(conn);
return &conn->le.dst;
}
_LIB_IDF
uint8_t bt_conn_index(const struct bt_conn *conn)
{
ptrdiff_t index = 0;
assert(conn);
switch (conn->type) {
case BT_CONN_TYPE_LE:
index = conn - acl_conns;
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));
break;
default:
assert(0);
break;
}
/* LOG_DBG("ConnIdx[%u]", index); */
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) {
case BT_CONN_DISCONNECTED:
case BT_CONN_DISCONNECT_COMPLETE:
return BT_CONN_STATE_DISCONNECTED;
case BT_CONN_INITIATING:
return BT_CONN_STATE_CONNECTING;
case BT_CONN_CONNECTED:
return BT_CONN_STATE_CONNECTED;
case BT_CONN_DISCONNECTING:
return BT_CONN_STATE_DISCONNECTING;
default:
assert(0);
return 0;
}
}
_LIB_ONLY
int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info)
{
assert(conn && info);
info->type = conn->type;
info->role = conn->role;
info->id = conn->id;
info->state = conn_internal_to_public_state(conn->state);
info->le.dst = &conn->le.dst;
info->le.interval = conn->le.interval;
if (conn->encrypt) {
/* Currently the flags is updated for lib usage.
* TODO:
* Update the flags based on the security information.
*/
info->security.flags = (BT_SECURITY_FLAG_SC | BT_SECURITY_FLAG_OOB);
info->security.level = conn->sec_level;
info->security.enc_key_size = BT_ENC_KEY_SIZE_MAX; /* 16 octets */
} else {
info->security.flags = 0;
info->security.level = BT_SECURITY_L1;
info->security.enc_key_size = 0;
}
/* LOG_DBG("ConnGetInfo[%u][%02x]", info->state, info->security.flags); */
return 0;
}
_LIB_ONLY
int bt_conn_cb_register(struct bt_conn_cb *cb)
{
LOG_DBG("ConnCbReg");
if (cb == NULL) {
LOG_ERR("ConnCbNull");
return -EINVAL;
}
if (sys_slist_find(&conn_cbs, &cb->_node, NULL)) {
LOG_WRN("ConnCbExist");
return -EEXIST;
}
sys_slist_append(&conn_cbs, &cb->_node);
return 0;
}
_IDF_ONLY
int bt_conn_cb_register_safe(struct bt_conn_cb *cb)
{
int err;
bt_le_host_lock();
err = bt_conn_cb_register(cb);
bt_le_host_unlock();
return err;
}
_NOT_USED
int bt_conn_cb_unregister(struct bt_conn_cb *cb)
{
LOG_DBG("ConnCbUnreg");
if (cb == NULL) {
LOG_ERR("ConnCbNull");
return -EINVAL;
}
if (!sys_slist_find_and_remove(&conn_cbs, &cb->_node)) {
LOG_ERR("ConnCbNotFound");
return -EALREADY;
}
return 0;
}
_LIB_ONLY
int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb)
{
LOG_DBG("ConnAuthInfoCbReg");
if (cb == NULL) {
LOG_ERR("ConnAuthInfoCbNull");
return -EINVAL;
}
if (sys_slist_find(&auth_info_cbs, &cb->node, NULL)) {
LOG_WRN("ConnAuthInfoCbExist");
return -EALREADY;
}
sys_slist_append(&auth_info_cbs, &cb->node);
return 0;
}
_NOT_USED
int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb)
{
LOG_DBG("ConnAuthInfoCbUnreg");
if (cb == NULL) {
LOG_ERR("ConnAuthInfoCbNull");
return -EINVAL;
}
if (!sys_slist_find_and_remove(&auth_info_cbs, &cb->node)) {
LOG_ERR("ConnAuthInfoCbNotFound");
return -EALREADY;
}
return 0;
}
_IDF_ONLY
struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size)
{
assert(conns);
for (size_t i = 0; i < size; i++) {
struct bt_conn *conn = &conns[i];
if (conn->type == BT_CONN_TYPE_NONE) {
(void)memset(conn, 0, sizeof(struct bt_conn));
return conn;
}
}
return NULL;
}
static struct bt_conn *acl_conn_lookup(uint16_t handle)
{
for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) {
if (acl_conns[i].type == BT_CONN_TYPE_LE &&
acl_conns[i].handle == handle) {
return &acl_conns[i];
}
}
LOG_INF("AclConnNotFound[%u]", handle);
return NULL;
}
static struct bt_conn *iso_conn_lookup(uint16_t handle)
{
for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
if (iso_conns[i].type == BT_CONN_TYPE_ISO &&
iso_conns[i].handle == handle) {
return &iso_conns[i];
}
}
LOG_INF("IsoConnNotFound[%u]", handle);
return NULL;
}
_IDF_ONLY
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);
/* LOG_DBG("ConnLookupHdl[%u][%u]", handle, type); */
if (type == BT_CONN_TYPE_LE) {
conn = acl_conn_lookup(handle);
} else {
conn = iso_conn_lookup(handle);
}
return conn;
}
_IDF_ONLY
struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle)
{
/* LOG_DBG("AclConnFind[%u]", 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,
bt_addr_le_t *dst,
uint8_t sec_level)
{
struct bt_conn *conn;
assert(dst);
LOG_DBG("AclNew[%u][%u][%u][%s]",
conn_handle, role, sec_level, bt_addr_le_str(dst));
conn = bt_conn_new(acl_conns, ARRAY_SIZE(acl_conns));
if (conn) {
conn->handle = conn_handle;
conn->type = BT_CONN_TYPE_LE;
conn->role = role;
conn->state = BT_CONN_CONNECTED;
conn->sec_level = sec_level;
memcpy(&conn->le.dst, dst, sizeof(conn->le.dst));
} else {
LOG_ERR("NoFreeConn[%u]", conn_handle);
}
return (conn ? 0 : -ENOMEM);
}
_IDF_ONLY
int bt_le_acl_conn_delete(uint16_t conn_handle)
{
struct bt_conn *conn;
LOG_DBG("AclConnDel[%u]", conn_handle);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
memset(conn, 0, sizeof(struct bt_conn));
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_update(uint16_t conn_handle,
uint8_t sec_level,
bool *encrypted)
{
struct bt_conn *conn;
LOG_DBG("AclConnUpd[%u][%u]", conn_handle, sec_level);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
if (encrypted) {
*encrypted = false;
}
if (conn->sec_level < sec_level) {
/* No encryption to encryption */
if (conn->encrypt == 0 && conn->sec_level == BT_SECURITY_L1) {
conn->encrypt = 1;
if (encrypted) {
*encrypted = true;
}
}
LOG_DBG("SecLevelUpd[%u][%u]", conn->sec_level, sec_level);
conn->sec_level = sec_level;
}
return 0;
}
static void acl_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
{
LOG_DBG("AclConnSetState");
conn->state = state;
switch (conn->state) {
case BT_CONN_CONNECTED:
break;
case BT_CONN_INITIATING:
break;
case BT_CONN_DISCONNECT_COMPLETE:
break;
default:
break;
}
}
static void iso_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
{
LOG_DBG("IsoConnSetState[%u]", state);
conn->state = state;
switch (conn->state) {
case BT_CONN_CONNECTED:
bt_iso_connected(conn);
break;
case BT_CONN_INITIATING:
break;
case BT_CONN_DISCONNECT_COMPLETE:
break;
default:
break;
}
}
_IDF_ONLY
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);
LOG_DBG("ConnSetState[%u][%u]", conn->state, state);
if (conn->state == state) {
return;
}
old_state = conn->state;
switch (old_state) {
case BT_CONN_DISCONNECTED:
/* TODO: check if anything to do */
break;
case BT_CONN_INITIATING:
/* TODO: check if anything to do */
break;
default:
break;
}
if (conn->type == BT_CONN_TYPE_LE) {
acl_conn_set_state(conn, state);
} else {
iso_conn_set_state(conn, state);
}
}
static int iso_disconnect(struct bt_conn *conn, uint8_t reason)
{
return bt_le_iso_disconnect(conn->handle, reason);
}
_IDF_ONLY
int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
{
assert(conn);
LOG_DBG("ConnDisconnect[%u][%u][%02x]", conn->state, conn->type, reason);
switch (conn->state) {
case BT_CONN_CONNECTED:
if (conn->type == BT_CONN_TYPE_LE) {
return -ENOTSUP;
}
if (conn->type == BT_CONN_TYPE_ISO) {
return iso_disconnect(conn, reason);
}
assert(0);
case BT_CONN_DISCONNECTING:
return 0;
case BT_CONN_DISCONNECTED:
default:
return -ENOTCONN;
}
}
_IDF_ONLY
int bt_le_acl_conn_connected_listener(uint16_t conn_handle)
{
struct bt_conn_cb *listener = NULL;
struct bt_conn *conn;
LOG_DBG("AclConnConnectedListener[%u]", conn_handle);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->connected) {
listener->connected(conn, BT_HCI_ERR_SUCCESS);
}
}
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason)
{
struct bt_conn_cb *listener = NULL;
struct bt_conn *conn;
LOG_DBG("AclConnDisconnectedListener[%u][%02x]", conn_handle, reason);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->disconnected) {
listener->disconnected(conn, reason);
}
}
#if CONFIG_BT_ISO_UNICAST
bt_le_acl_conn_disconnected_gatt_listener(conn_handle);
#endif /* CONFIG_BT_ISO_UNICAST */
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_security_changed_listener(uint16_t conn_handle, bt_security_t level)
{
struct bt_conn_cb *listener = NULL;
struct bt_conn *conn;
LOG_DBG("AclConnSecurityChangedListener[%u][%u]", conn_handle, level);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->security_changed) {
listener->security_changed(conn, level, BT_SECURITY_ERR_SUCCESS);
}
}
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle,
const bt_addr_le_t *rpa,
const bt_addr_le_t *identity)
{
struct bt_conn_cb *listener = NULL;
struct bt_conn *conn;
LOG_DBG("AclConnIdentityResolvedListener[%u][%s]",
conn_handle, bt_addr_le_str(rpa));
LOG_DBG("AclConnIdentityResolvedListener[%s]",
bt_addr_le_str(identity));
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->identity_resolved) {
listener->identity_resolved(conn, rpa, identity);
}
}
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_pairing_completed_listener(uint16_t conn_handle, bool bonded)
{
struct bt_conn_auth_info_cb *listener = NULL;
struct bt_conn *conn;
LOG_DBG("AclConnPairingCompletedListener[%u][%u]", conn_handle, bonded);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
return -ENOTCONN;
}
SYS_SLIST_FOR_EACH_CONTAINER(&auth_info_cbs, listener, node) {
if (listener->pairing_complete) {
listener->pairing_complete(conn, bonded);
}
}
return 0;
}
_IDF_ONLY
int bt_le_acl_conn_bond_deleted_listener(uint8_t id, const bt_addr_le_t *peer)
{
struct bt_conn_auth_info_cb *listener = NULL;
LOG_DBG("AclConnBondDeletedListener[%02x][%s]", id, bt_addr_le_str(peer));
SYS_SLIST_FOR_EACH_CONTAINER(&auth_info_cbs, listener, node) {
if (listener->bond_deleted) {
listener->bond_deleted(id, peer);
}
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2017-2025 Nordic Semiconductor ASA
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2025 NXP
* 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/buf.h>
#include <zephyr/bluetooth/hci.h>
#include <../host/hci_core.h>
#include <../host/iso_internal.h>
#include "common/host.h"
struct cmd_data {
/** HCI status of the command completion */
uint8_t status;
/** The command OpCode that the buffer contains */
uint16_t opcode;
/** The state to update when command completes with success. */
struct bt_hci_cmd_state_set *state;
};
static struct cmd_data cmd_data;
#define cmd(buf) (&cmd_data)
NET_BUF_POOL_FIXED_DEFINE(hci_cmd_pool, 1, 255, 0, NULL);
_IDF_ONLY
struct net_buf *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len)
{
struct bt_hci_cmd_hdr *hdr;
struct net_buf *buf;
LOG_DBG("HciCmdCreate[%04x][%u]", opcode, param_len);
buf = net_buf_alloc(&hci_cmd_pool, K_NO_WAIT);
assert(buf);
net_buf_reserve(buf, BT_BUF_RESERVE);
cmd(buf)->opcode = opcode;
cmd(buf)->state = NULL;
hdr = net_buf_add(buf, sizeof(*hdr));
hdr->opcode = sys_cpu_to_le16(opcode);
hdr->param_len = param_len;
return buf;
}
_IDF_ONLY
int bt_hci_cmd_send_sync(uint16_t opcode,
struct net_buf *buf,
struct net_buf **rsp)
{
LOG_DBG("HciCmdSendSync[%04x]", opcode);
return bt_le_nimble_iso_cmd_send_sync(opcode, buf, rsp);
}
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* 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/logging/log.h>
#include "common/host.h"
#include "common/app/gap.h"
#include "common/app/gatt.h"
static struct k_mutex host_mutex;
#define TIMEOUT_MS (5000 / portTICK_PERIOD_MS) /* 5s */
#if HOST_LOCK_DEBUG
void bt_le_host_lock_debug(const char *func, int line)
#else /* HOST_LOCK_DEBUG */
void bt_le_host_lock(void)
#endif /* HOST_LOCK_DEBUG */
{
/* LOG_DBG("%s: %d", func, line); */
k_mutex_lock(&host_mutex, TIMEOUT_MS);
}
#if HOST_LOCK_DEBUG
void bt_le_host_unlock_debug(const char *func, int line)
#else /* HOST_LOCK_DEBUG */
void bt_le_host_unlock(void)
#endif /* HOST_LOCK_DEBUG */
{
/* LOG_DBG("%s: %d", func, line); */
k_mutex_unlock(&host_mutex);
}
int bt_le_host_init(void)
{
int err;
LOG_DBG("HostInit");
k_mutex_create(&host_mutex);
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
err = bt_le_l2cap_init();
if (err) {
goto delete_mutex;
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
err = bt_le_iso_init();
if (err) {
goto deinit_l2cap;
}
err = bt_le_iso_task_init();
if (err) {
goto deinit_iso;
}
return 0;
deinit_iso:
bt_le_iso_deinit();
deinit_l2cap:
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
delete_mutex:
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
k_mutex_delete(&host_mutex);
return err;
}
void bt_le_host_deinit(void)
{
LOG_DBG("HostDeinit");
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
bt_le_iso_deinit();
bt_le_iso_task_deinit();
k_mutex_delete(&host_mutex);
}
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Nordic Semiconductor ASA
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_ADV_H_
#define HOST_COMMON_ADV_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_le_ext_adv *bt_le_ext_adv_find(uint8_t adv_handle);
int bt_le_ext_adv_new_safe(uint8_t adv_handle);
int bt_le_ext_adv_delete_safe(uint8_t adv_handle);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_ADV_H_ */
@@ -0,0 +1,273 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_APP_GAP_H_
#define HOST_COMMON_APP_GAP_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "nimble/gap.h"
#ifdef __cplusplus
extern "C" {
#endif
struct bt_le_addr {
uint8_t type;
uint8_t val[6];
};
struct bt_le_gap_app_ext_scan_recv_param {
uint16_t event_type;
struct bt_le_addr addr;
int8_t rssi;
int8_t tx_power;
uint8_t sid;
uint8_t pri_phy;
uint8_t sec_phy;
uint16_t per_adv_itvl;
uint8_t data_len;
uint8_t *data;
};
struct bt_le_gap_app_pa_sync_param {
uint8_t status;
uint16_t sync_handle;
struct bt_le_addr addr;
uint8_t sid;
uint8_t adv_phy;
uint16_t per_adv_itvl;
uint8_t adv_ca;
};
struct bt_le_gap_app_pa_sync_lost_param {
uint16_t sync_handle;
uint8_t reason;
};
struct bt_le_gap_app_pa_sync_recv_param {
uint16_t sync_handle;
int8_t tx_power;
int8_t rssi;
uint8_t data_status;
uint8_t data_len;
uint8_t *data;
};
struct bt_le_gap_app_acl_connect_param {
uint8_t status;
uint16_t conn_handle;
uint8_t role;
struct bt_le_addr dst;
};
struct bt_le_gap_app_acl_disconnect_param {
uint16_t conn_handle;
uint8_t reason;
};
struct bt_le_gap_app_security_change_param {
uint8_t status;
uint16_t conn_handle;
uint8_t role;
uint8_t sec_level;
uint8_t bonded : 1;
struct bt_le_addr dst;
};
struct bt_le_gap_app_identity_resolve_param {
uint16_t conn_handle;
struct bt_le_addr rpa;
struct bt_le_addr identity;
};
struct bt_le_gap_app_bond_delete_param {
uint8_t id;
struct bt_le_addr peer;
};
struct bt_le_gap_app_biginfo_recv_param {
uint16_t sync_handle;
uint8_t num_bis;
uint8_t nse;
uint16_t iso_interval;
uint8_t bn;
uint8_t pto;
uint8_t irc;
uint16_t max_pdu;
uint32_t sdu_interval;
uint16_t max_sdu;
uint8_t phy;
uint8_t framing;
uint8_t encryption;
};
struct bt_le_gap_app_param {
uint8_t type;
union {
struct bt_le_gap_app_ext_scan_recv_param ext_scan_recv;
struct bt_le_gap_app_pa_sync_param pa_sync;
struct bt_le_gap_app_pa_sync_lost_param pa_sync_lost;
struct bt_le_gap_app_pa_sync_recv_param pa_sync_recv;
struct bt_le_gap_app_acl_connect_param acl_connect;
struct bt_le_gap_app_acl_disconnect_param acl_disconnect;
struct bt_le_gap_app_security_change_param security_change;
struct bt_le_gap_app_identity_resolve_param identity_resolve;
struct bt_le_gap_app_bond_delete_param bond_delete;
};
};
enum bt_le_gap_app_param_type {
BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV,
BT_LE_GAP_APP_PARAM_PA_SYNC,
BT_LE_GAP_APP_PARAM_PA_SYNC_LOST,
BT_LE_GAP_APP_PARAM_PA_SYNC_RECV,
BT_LE_GAP_APP_PARAM_ACL_CONNECT,
BT_LE_GAP_APP_PARAM_ACL_DISCONNECT,
BT_LE_GAP_APP_PARAM_SECURITY_CHANGE,
BT_LE_GAP_APP_PARAM_IDENTITY_RESOLVE,
BT_LE_GAP_APP_PARAM_BOND_DELETE,
BT_LE_GAP_APP_PARAM_MAX,
};
struct bt_le_gap_app_event_ext_scan_recv {
uint16_t event_type;
struct bt_le_addr addr;
int8_t rssi;
int8_t tx_power;
uint8_t sid;
uint8_t pri_phy;
uint8_t sec_phy;
uint16_t per_adv_itvl;
uint8_t data_len;
uint8_t *data; /* Will be freed after the event is processed */
};
struct bt_le_gap_app_event_pa_sync {
uint8_t status;
uint16_t sync_handle;
struct bt_le_addr addr;
uint8_t sid;
uint8_t adv_phy;
uint16_t per_adv_itvl;
uint8_t adv_ca;
};
struct bt_le_gap_app_event_pa_sync_lost {
uint16_t sync_handle;
uint8_t reason;
};
struct bt_le_gap_app_event_pa_sync_recv {
uint16_t sync_handle;
int8_t tx_power;
int8_t rssi;
uint8_t data_status;
uint8_t data_len;
uint8_t *data; /* Will be freed after the event is processed */
};
struct bt_le_gap_app_event_acl_connect {
uint8_t status;
uint16_t conn_handle;
uint8_t role;
struct bt_le_addr dst;
};
struct bt_le_gap_app_event_acl_disconnect {
uint16_t conn_handle;
uint8_t reason;
};
struct bt_le_gap_app_event_security_change {
uint8_t status;
uint16_t conn_handle;
uint8_t role;
uint8_t sec_level;
uint8_t bonded : 1;
struct bt_le_addr dst;
};
struct bt_le_gap_app_event_identity_resolve {
uint16_t conn_handle;
struct bt_le_addr rpa;
struct bt_le_addr identity;
};
struct bt_le_gap_app_event_bond_delete {
uint8_t id;
struct bt_le_addr peer;
};
struct bt_le_gap_app_event_biginfo_recv {
uint16_t sync_handle;
uint8_t num_bis;
uint8_t nse;
uint16_t iso_interval;
uint8_t bn;
uint8_t pto;
uint8_t irc;
uint16_t max_pdu;
uint32_t sdu_interval;
uint16_t max_sdu;
uint8_t phy;
uint8_t framing;
uint8_t encryption;
};
struct bt_le_gap_app_event {
uint8_t type;
union {
struct bt_le_gap_app_event_ext_scan_recv ext_scan_recv;
struct bt_le_gap_app_event_pa_sync pa_sync;
struct bt_le_gap_app_event_pa_sync_lost pa_sync_lost;
struct bt_le_gap_app_event_pa_sync_recv pa_sync_recv;
struct bt_le_gap_app_event_acl_connect acl_connect;
struct bt_le_gap_app_event_acl_disconnect acl_disconnect;
struct bt_le_gap_app_event_security_change security_change;
struct bt_le_gap_app_event_identity_resolve identity_resolve;
struct bt_le_gap_app_event_bond_delete bond_delete;
struct bt_le_gap_app_event_biginfo_recv biginfo_recv;
};
};
enum bt_le_gap_app_event_type {
BT_LE_GAP_APP_EVENT_EXT_SCAN_RECV,
BT_LE_GAP_APP_EVENT_PA_SYNC,
BT_LE_GAP_APP_EVENT_PA_SYNC_LOST,
BT_LE_GAP_APP_EVENT_PA_SYNC_RECV,
BT_LE_GAP_APP_EVENT_ACL_CONNECT,
BT_LE_GAP_APP_EVENT_ACL_DISCONNECT,
BT_LE_GAP_APP_EVENT_SECURITY_CHANGE,
BT_LE_GAP_APP_EVENT_IDENTITY_RESOLVE,
BT_LE_GAP_APP_EVENT_BOND_DELETE,
BT_LE_GAP_APP_EVENT_BIGINFO_RECV,
BT_LE_GAP_APP_EVENT_MAX,
};
typedef void (*bt_le_gap_app_cb)(struct bt_le_gap_app_event *event);
int bt_le_gap_app_cb_register(bt_le_gap_app_cb cb);
void bt_le_gap_app_cb_unregister(void);
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);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_APP_GAP_H_ */
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_APP_GATT_H_
#define HOST_COMMON_APP_GATT_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "nimble/gatt.h"
#ifdef __cplusplus
extern "C" {
#endif
struct bt_le_gatt_app_event_gatt_mtu_change {
uint16_t conn_handle;
uint16_t mtu;
};
struct bt_le_gatt_app_event_gattc_disc_cmpl {
uint8_t status;
uint16_t conn_handle;
};
struct bt_le_gatt_app_event_gatts_subscribe {
uint16_t conn_handle;
uint16_t attr_handle;
uint8_t prev_notify : 1;
uint8_t cur_notify : 1;
uint8_t prev_indicate : 1;
uint8_t cur_indicate : 1;
uint8_t reason;
};
struct bt_le_gatt_app_event {
uint8_t type;
union {
struct bt_le_gatt_app_event_gatt_mtu_change gatt_mtu_change;
struct bt_le_gatt_app_event_gattc_disc_cmpl gattc_disc_cmpl;
struct bt_le_gatt_app_event_gatts_subscribe gatts_subscribe;
};
};
enum bt_le_gatt_app_event_type {
BT_LE_GATT_APP_EVENT_GATT_MTU_CHANGE,
BT_LE_GATT_APP_EVENT_GATTC_DISC_CMPL,
BT_LE_GATT_APP_EVENT_GATTS_SUBSCRIBE,
BT_LE_GATT_APP_EVENT_MAX,
};
typedef void (*bt_le_gatt_app_cb)(struct bt_le_gatt_app_event *event);
int bt_le_gatt_app_cb_register(bt_le_gatt_app_cb cb);
void bt_le_gatt_app_cb_unregister(void);
void bt_le_gatt_app_mtu_change_event(uint16_t conn_handle, uint16_t mtu);
void bt_le_gattc_app_disc_cmpl_event(struct bt_le_gattc_disc_cmpl_event *param);
void bt_le_gatts_app_subscribe_event(struct bt_le_gatts_subscribe_event *param);
void bt_le_gatt_app_post_event(uint8_t type, void *param);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_APP_GATT_H_ */
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2025 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_CONN_H_
#define HOST_COMMON_CONN_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "nimble/gap.h"
#include "nimble/iso.h"
#ifdef __cplusplus
extern "C" {
#endif
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,
uint8_t sec_level);
int bt_le_acl_conn_delete(uint16_t conn_handle);
int bt_le_acl_conn_update(uint16_t conn_handle,
uint8_t sec_level,
bool *encrypted);
int bt_le_acl_conn_connected_listener(uint16_t conn_handle);
int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason);
int bt_le_acl_conn_security_changed_listener(uint16_t conn_handle, bt_security_t level);
int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle,
const bt_addr_le_t *rpa,
const bt_addr_le_t *identity);
int bt_le_acl_conn_pairing_completed_listener(uint16_t conn_handle, bool bonded);
int bt_le_acl_conn_bond_deleted_listener(uint8_t id, const bt_addr_le_t *peer);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_CONN_H_ */
@@ -0,0 +1,382 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_GATT_H_
#define HOST_COMMON_GATT_H_
#include <stdint.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/gatt.h>
#include "nimble/gatt.h"
#ifdef __cplusplus
extern "C" {
#endif
struct bt_le_gatt_mtu_change_event {
uint16_t conn_handle;
uint16_t mtu;
};
enum {
GATTC_DISC_TYPE_DUMMY,
GATTC_DISC_TYPE_SVC_BY_UUID,
GATTC_DISC_TYPE_INC_SVCS,
GATTC_DISC_TYPE_CHRS_BY_UUID,
GATTC_DISC_TYPE_ALL_CHRS,
GATTC_DISC_TYPE_ALL_DSCS, /* Not used for now */
GATTC_DISC_TYPE_MAX,
};
struct bt_le_gattc_discover_event {
uint8_t type;
uint16_t conn_handle;
uint16_t uuid;
struct bt_gatt_discover_params *params;
};
struct bt_le_gattc_disc_cmpl_event {
uint8_t status;
uint16_t conn_handle;
};
struct bt_le_gatts_subscribe_event {
uint16_t conn_handle;
uint16_t attr_handle;
uint8_t prev_notify : 1;
uint8_t cur_notify : 1;
uint8_t prev_indicate : 1;
uint8_t cur_indicate : 1;
uint8_t reason;
};
struct bt_le_gattc_notify_rx_event {
bool is_notify;
uint16_t conn_handle;
uint16_t attr_handle;
uint16_t len;
uint8_t *value;
};
struct bt_le_gatts_notify_tx_event {
bool is_notify;
uint16_t conn_handle;
uint16_t attr_handle;
uint8_t status;
};
struct bt_le_gatt_event_param {
uint8_t type;
union {
struct bt_le_gatt_mtu_change_event gatt_mtu_change;
struct bt_le_gattc_discover_event gattc_discover;
struct bt_le_gattc_disc_cmpl_event gattc_disc_cmpl;
struct bt_le_gatts_subscribe_event gatts_subscribe;
struct bt_le_gattc_notify_rx_event gattc_notify_rx;
struct bt_le_gatts_notify_tx_event gatts_notify_tx;
};
};
enum {
BT_LE_GATT_MTU_CHANGE_EVENT,
BT_LE_GATTC_DISCOVER_EVENT,
BT_LE_GATTC_DISC_CMPL_EVENT,
BT_LE_GATTS_SUBSCRIBE_EVENT,
BT_LE_GATTC_NOTIFY_RX_EVENT,
BT_LE_GATTS_NOTIFY_TX_EVENT,
BT_LE_GATT_EVENT_MAX,
};
static inline char *audio_svc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_GAP_VAL:
return "GAP";
case BT_UUID_GATT_VAL:
return "GATT";
case BT_UUID_AICS_VAL:
return "AICS";
case BT_UUID_CAS_VAL:
return "CAS";
case BT_UUID_VCS_VAL:
return "VCS";
case BT_UUID_VOCS_VAL:
return "VOCS";
case BT_UUID_CSIS_VAL:
return "CSIS";
case BT_UUID_MCS_VAL:
return "MCS";
case BT_UUID_GMCS_VAL:
return "GMCS";
case BT_UUID_TBS_VAL:
return "TBS";
case BT_UUID_GTBS_VAL:
return "GTBS";
case BT_UUID_MICS_VAL:
return "MICS";
case BT_UUID_ASCS_VAL:
return "ASCS";
case BT_UUID_BASS_VAL:
return "BASS";
case BT_UUID_PACS_VAL:
return "PACS";
case BT_UUID_BASIC_AUDIO_VAL:
return "BASIC_AUDIO";
case BT_UUID_HAS_VAL:
return "HAS";
case BT_UUID_TMAS_VAL:
return "TMAS";
case BT_UUID_PBA_VAL:
return "PBA";
case BT_UUID_GMAS_VAL:
return "GMAS";
case BT_UUID_OTS_VAL:
return "OTS";
default:
return "Unknown";
}
}
static inline char *audio_chrc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_OTS_FEATURE_VAL:
return "OTS_FEATURE";
case BT_UUID_OTS_NAME_VAL:
return "OTS_NAME";
case BT_UUID_OTS_TYPE_VAL:
return "OTS_TYPE";
case BT_UUID_OTS_SIZE_VAL:
return "OTS_SIZE";
case BT_UUID_OTS_FIRST_CREATED_VAL:
return "OTS_FIRST_CREATED";
case BT_UUID_OTS_LAST_MODIFIED_VAL:
return "OTS_LAST_MODIFIED";
case BT_UUID_OTS_ID_VAL:
return "OTS_ID";
case BT_UUID_OTS_PROPERTIES_VAL:
return "OTS_PROPERTIES";
case BT_UUID_OTS_ACTION_CP_VAL:
return "OTS_ACTION_CP";
case BT_UUID_OTS_LIST_CP_VAL:
return "OTS_LIST_CP";
case BT_UUID_OTS_LIST_FILTER_VAL:
return "OTS_LIST_FILTER";
case BT_UUID_OTS_CHANGED_VAL:
return "OTS_CHANGED";
case BT_UUID_GATT_TMAPR_VAL:
return "TMAP_ROLE";
case BT_UUID_AICS_STATE_VAL:
return "AICS_STATE";
case BT_UUID_AICS_GAIN_SETTINGS_VAL:
return "AICS_GAIN_SETTINGS";
case BT_UUID_AICS_INPUT_TYPE_VAL:
return "AICS_INPUT_TYPE";
case BT_UUID_AICS_INPUT_STATUS_VAL:
return "AICS_INPUT_STATUS";
case BT_UUID_AICS_CONTROL_VAL:
return "AICS_CONTROL";
case BT_UUID_AICS_DESCRIPTION_VAL:
return "AICS_DESCRIPTION";
case BT_UUID_VCS_STATE_VAL:
return "VCS_STATE";
case BT_UUID_VCS_CONTROL_VAL:
return "VCS_CONTROL";
case BT_UUID_VCS_FLAGS_VAL:
return "VCS_FLAGS";
case BT_UUID_VOCS_STATE_VAL:
return "VOCS_STATE";
case BT_UUID_VOCS_LOCATION_VAL:
return "VOCS_LOCATION";
case BT_UUID_VOCS_CONTROL_VAL:
return "VOCS_CONTROL";
case BT_UUID_VOCS_DESCRIPTION_VAL:
return "VOCS_DESCRIPTION";
case BT_UUID_CSIS_SIRK_VAL:
return "CSIS_SIRK";
case BT_UUID_CSIS_SET_SIZE_VAL:
return "CSIS_SET_SIZE";
case BT_UUID_CSIS_SET_LOCK_VAL:
return "CSIS_SET_LOCK";
case BT_UUID_CSIS_RANK_VAL:
return "CSIS_RANK";
case BT_UUID_MCS_PLAYER_NAME_VAL:
return "MCS_PLAYER_NAME";
case BT_UUID_MCS_ICON_OBJ_ID_VAL:
return "MCS_ICON_OBJ_ID";
case BT_UUID_MCS_ICON_URL_VAL:
return "MCS_ICON_URL";
case BT_UUID_MCS_TRACK_CHANGED_VAL:
return "MCS_TRACK_CHANGED";
case BT_UUID_MCS_TRACK_TITLE_VAL:
return "MCS_TRACK_TITLE";
case BT_UUID_MCS_TRACK_DURATION_VAL:
return "MCS_TRACK_DURATION";
case BT_UUID_MCS_TRACK_POSITION_VAL:
return "MCS_TRACK_POSITION";
case BT_UUID_MCS_PLAYBACK_SPEED_VAL:
return "MCS_PLAYBACK_SPEED";
case BT_UUID_MCS_SEEKING_SPEED_VAL:
return "MCS_SEEKING_SPEED";
case BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL:
return "MCS_TRACK_SEGMENTS_OBJ_ID";
case BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL:
return "MCS_CURRENT_TRACK_OBJ_ID";
case BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL:
return "MCS_NEXT_TRACK_OBJ_ID";
case BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL:
return "MCS_PARENT_GROUP_OBJ_ID";
case BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL:
return "MCS_CURRENT_GROUP_OBJ_ID";
case BT_UUID_MCS_PLAYING_ORDER_VAL:
return "MCS_PLAYING_ORDER";
case BT_UUID_MCS_PLAYING_ORDERS_VAL:
return "MCS_PLAYING_ORDERS";
case BT_UUID_MCS_MEDIA_STATE_VAL:
return "MCS_MEDIA_STATE";
case BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL:
return "MCS_MEDIA_CONTROL_POINT";
case BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL:
return "MCS_MEDIA_CONTROL_OPCODES";
case BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL:
return "MCS_SEARCH_RESULTS_OBJ_ID";
case BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL:
return "MCS_SEARCH_CONTROL_POINT";
case BT_UUID_TBS_PROVIDER_NAME_VAL:
return "TBS_PROVIDER_NAME";
case BT_UUID_TBS_UCI_VAL:
return "TBS_UCI";
case BT_UUID_TBS_TECHNOLOGY_VAL:
return "TBS_TECHNOLOGY";
case BT_UUID_TBS_URI_LIST_VAL:
return "TBS_URI_LIST";
case BT_UUID_TBS_SIGNAL_STRENGTH_VAL:
return "TBS_SIGNAL_STRENGTH";
case BT_UUID_TBS_SIGNAL_INTERVAL_VAL:
return "TBS_SIGNAL_INTERVAL";
case BT_UUID_TBS_LIST_CURRENT_CALLS_VAL:
return "TBS_LIST_CURRENT_CALLS";
case BT_UUID_CCID_VAL:
return "CCID";
case BT_UUID_TBS_STATUS_FLAGS_VAL:
return "TBS_STATUS_FLAGS";
case BT_UUID_TBS_INCOMING_URI_VAL:
return "TBS_INCOMING_URI";
case BT_UUID_TBS_CALL_STATE_VAL:
return "TBS_CALL_STATE";
case BT_UUID_TBS_CALL_CONTROL_POINT_VAL:
return "TBS_CALL_CONTROL_POINT";
case BT_UUID_TBS_OPTIONAL_OPCODES_VAL:
return "TBS_OPTIONAL_OPCODES";
case BT_UUID_TBS_TERMINATE_REASON_VAL:
return "TBS_TERMINATE_REASON";
case BT_UUID_TBS_INCOMING_CALL_VAL:
return "TBS_INCOMING_CALL";
case BT_UUID_TBS_FRIENDLY_NAME_VAL:
return "TBS_FRIENDLY_NAME";
case BT_UUID_MICS_MUTE_VAL:
return "MICS_MUTE";
case BT_UUID_ASCS_ASE_SNK_VAL:
return "ASCS_ASE_SNK";
case BT_UUID_ASCS_ASE_SRC_VAL:
return "ASCS_ASE_SRC";
case BT_UUID_ASCS_ASE_CP_VAL:
return "ASCS_ASE_CP";
case BT_UUID_BASS_CONTROL_POINT_VAL:
return "BASS_CP";
case BT_UUID_BASS_RECV_STATE_VAL:
return "BASS_RECV_STATE";
case BT_UUID_PACS_SNK_VAL:
return "PACS_SNK";
case BT_UUID_PACS_SNK_LOC_VAL:
return "PACS_SNK_LOC";
case BT_UUID_PACS_SRC_VAL:
return "PACS_SRC";
case BT_UUID_PACS_SRC_LOC_VAL:
return "PACS_SRC_LOC";
case BT_UUID_PACS_AVAILABLE_CONTEXT_VAL:
return "PACS_AVAILABLE_CONTEXT";
case BT_UUID_PACS_SUPPORTED_CONTEXT_VAL:
return "PACS_SUPPORTED_CONTEXT";
case BT_UUID_HAS_HEARING_AID_FEATURES_VAL:
return "HAS_HEARING_AID_FEATURES";
case BT_UUID_HAS_PRESET_CONTROL_POINT_VAL:
return "HAS_PRESET_CONTROL_POINT";
case BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL:
return "HAS_ACTIVE_PRESET_INDEX";
default:
return "Unknown";
}
}
struct gattc_sub {
uint8_t id;
bt_addr_le_t peer;
sys_slist_t list;
};
struct gatt_incl {
uint16_t start_handle;
uint16_t end_handle;
uint16_t uuid16;
} __attribute__((packed));
struct gatt_chrc {
uint8_t properties;
uint16_t value_handle;
union {
uint16_t uuid16;
uint8_t uuid[16];
};
} __attribute__((packed));
struct bt_att_write_req {
uint16_t handle;
uint8_t value[0];
} __attribute__((packed));
struct notify_data {
const struct bt_gatt_attr *attr;
uint16_t handle;
int err;
uint16_t type;
union {
struct bt_gatt_notify_params *nfy_params;
struct bt_gatt_indicate_params *ind_params;
};
};
struct bt_gatt_attr *bt_gatts_find_attr_by_handle(uint16_t handle);
bool bt_gatts_find_attr_by_uuid(struct notify_data *found,
const struct bt_uuid *uuid);
int bt_gatts_sub_changed(uint16_t conn_handle,
uint16_t ccc_handle,
uint8_t cur_notify,
uint8_t cur_indicate,
uint8_t reason);
int bt_gattc_disc_start_safe(uint16_t conn_handle);
struct gattc_sub *bt_gattc_sub_find(struct bt_conn *conn);
void bt_le_acl_conn_disconnected_gatt_listener(uint16_t conn_handle);
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_GATT_H_ */
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_HOST_H_
#define HOST_COMMON_HOST_H_
#include <stdint.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/bluetooth.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"
#ifdef __cplusplus
extern "C" {
#endif
#define HOST_LOCK_DEBUG 0
#if HOST_LOCK_DEBUG
void bt_le_host_lock_debug(const char *func, int line);
void bt_le_host_unlock_debug(const char *func, int line);
#define bt_le_host_lock(...) bt_le_host_lock_debug(__func__, __LINE__)
#define bt_le_host_unlock(...) bt_le_host_unlock_debug(__func__, __LINE__)
#else /* HOST_LOCK_DEBUG */
void bt_le_host_lock(void);
void bt_le_host_unlock(void);
#endif /* HOST_LOCK_DEBUG */
int bt_le_host_init(void);
void bt_le_host_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_HOST_H_ */
@@ -0,0 +1,79 @@
/*
* 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_ISO_H_
#define HOST_COMMON_ISO_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "nimble/iso.h"
#ifdef __cplusplus
extern "C" {
#endif
struct bt_le_iso_cb {
void (*cis_dis)(struct net_buf *buf);
void (*cis_est)(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);
void (*big_est)(struct net_buf *buf);
void (*big_lost)(struct net_buf *buf);
void (*biginfo)(struct net_buf *buf);
sys_snode_t node;
};
struct bt_iso_tx_cb_info {
uint16_t completed_pkts;
uint16_t not_txd_pkts;
uint8_t pkt_cnt;
struct {
uint16_t seq_num;
uint32_t tx_time_stamp;
uint32_t time_offset : 24;
uint32_t drift : 1;
uint32_t sdu_ref_time;
uint32_t sdu_hdl_time;
uint32_t sdu_rec_time;
} pkt[2];
};
int bt_le_iso_cb_register_safe(struct bt_le_iso_cb *cb);
void bt_le_iso_cb_unregister_safe(struct bt_le_iso_cb *cb);
void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len);
void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len);
void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len);
int bt_le_iso_tx(struct bt_iso_chan *chan,
struct net_buf *buf,
uint16_t seq_num,
uint32_t time_stamp,
bool ts_valid);
int bt_le_iso_rx(const uint8_t *data, uint16_t len, void *arg);
int bt_le_iso_disconnect(uint16_t conn_handle, uint8_t reason);
int bt_le_iso_init(void);
void bt_le_iso_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_ISO_H_ */
@@ -0,0 +1,60 @@
/*
* 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"
#include "nimble/l2cap.h"
#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_ */
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Nordic Semiconductor
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_SCAN_H_
#define HOST_COMMON_SCAN_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "nimble/gap.h"
#ifdef __cplusplus
extern "C" {
#endif
void bt_le_scan_recv_listener(uint16_t event_type,
uint8_t addr_type,
uint8_t addr_val[6],
int8_t rssi,
int8_t tx_power,
uint8_t sid,
uint8_t pri_phy,
uint8_t sec_phy,
uint16_t per_adv_itvl,
uint8_t data_len,
uint8_t *data);
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,
uint8_t sid,
uint8_t phy,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
struct bt_le_per_adv_sync **out_sync);
int bt_le_per_adv_sync_delete(uint16_t sync_handle);
int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle);
int bt_le_per_adv_sync_lost_listener(uint16_t sync_handle);
int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle,
const uint8_t *data,
size_t data_length);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_SCAN_H_ */
@@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_TASK_H_
#define HOST_COMMON_TASK_H_
#include <stdint.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_BT_NIMBLE_PINNED_TO_CORE
#define ISO_TASK_CORE CONFIG_BT_NIMBLE_PINNED_TO_CORE
#else /* CONFIG_BT_NIMBLE_PINNED_TO_CORE */
#define ISO_TASK_CORE (0)
#endif /* CONFIG_BT_NIMBLE_PINNED_TO_CORE */
#define ISO_TASK_STACK_SIZE 4096
#define ISO_TASK_NAME "iso_task"
/* Ref:
* - NimBLE Host task: configMAX_PRIORITIES - 4
*/
#define ISO_TASK_PRIO (configMAX_PRIORITIES - 4)
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_ISO_HCI_EVENT,
ISO_QUEUE_ITEM_TYPE_ISO_TX_COMP,
ISO_QUEUE_ITEM_TYPE_ISO_RX_DATA,
ISO_QUEUE_ITEM_TYPE_MAX,
};
struct iso_queue_item {
uint8_t type;
uint8_t *data;
size_t data_len;
};
#define ISO_QUEUE_ITEM_COUNT 100
#define ISO_QUEUE_ITEM_SIZE sizeof(struct iso_queue_item)
int bt_le_iso_task_post(enum iso_queue_item_type type,
void *data, size_t data_len);
int bt_le_iso_task_init(void);
void bt_le_iso_task_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_TASK_H_ */
+844
View File
@@ -0,0 +1,844 @@
/*
* 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/hci_types.h>
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/conn_internal.h>
#include <../host/iso_internal.h>
#include <../host/hci_core.h>
#include "common/host.h"
#include "common/task.h"
#include "common/app/gap.h"
#ifdef CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL
#define ISO_STD_FLOW_CTRL true
#else /* CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL */
#define ISO_STD_FLOW_CTRL false
#endif /* CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL */
#define ISO_PKT_FIRST_FRAG (0b00)
#define ISO_PKT_CONT_FRAG (0b01)
#define ISO_PKT_COMP_SDU (0b10)
#define ISO_PKT_LAST_FRAG (0b11)
#define BT_LE_FEAT_SET(feat, n) (feat[(n) >> 3] |= BIT((n) & 7))
#define BT_LE_FEAT_UNSET(feat, n) (feat[(n) >> 3] &= ~BIT((n) & 7))
static sys_slist_t iso_cbs = SYS_SLIST_STATIC_INIT(&iso_cbs);
#if CONFIG_BT_ISO_UNICAST
static void hci_le_cis_disconnected(struct net_buf *buf);
#endif /* CONFIG_BT_ISO_UNICAST */
#if CONFIG_BT_ISO_TX
static void iso_tx_sdu_clear(uint16_t conn_handle, bool is_big);
#endif /* CONFIG_BT_ISO_TX */
extern void hci_le_biginfo_adv_report(struct net_buf *buf);
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,
#if CONFIG_BT_ISO_PERIPHERAL
.cis_req = hci_le_cis_req,
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#endif /* CONFIG_BT_ISO_UNICAST */
#if CONFIG_BT_ISO_BROADCASTER
.big_create = hci_le_big_complete,
.big_term = hci_le_big_terminate,
#endif /* CONFIG_BT_ISO_BROADCASTER */
#if CONFIG_BT_ISO_SYNC_RECEIVER
.big_est = hci_le_big_sync_established,
.big_lost = hci_le_big_sync_lost,
.biginfo = hci_le_biginfo_adv_report,
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
};
bool iso_log_enable = true;
bool iso_hw_log_enable = true;
bool isoal_post_lost = true;
extern uint16_t r_ble_ll_iso_free_buf_num_get(uint16_t conn_handle);
extern int r_ble_hci_trans_hs_iso_tx(const uint8_t *data, uint16_t length, void *arg);
extern void r_ble_ll_isoal_tx_comp_cb_set(void *cb);
_IDF_ONLY
int bt_le_iso_cb_register_safe(struct bt_le_iso_cb *cb)
{
int err = 0;
LOG_DBG("IsoCbReg");
if (cb == NULL) {
LOG_ERR("IsoCbNull");
return -EINVAL;
}
bt_le_host_lock();
if (sys_slist_find(&iso_cbs, &cb->node, NULL)) {
LOG_WRN("IsoCbExist");
err = -EEXIST;
goto end;
}
sys_slist_append(&iso_cbs, &cb->node);
end:
bt_le_host_unlock();
return err;
}
_IDF_ONLY
void bt_le_iso_cb_unregister_safe(struct bt_le_iso_cb *cb)
{
LOG_DBG("IsoCbUnreg");
if (cb == NULL) {
LOG_ERR("IsoCbNull");
return;
}
bt_le_host_lock();
sys_slist_find_and_remove(&iso_cbs, &cb->node);
bt_le_host_unlock();
}
#if CONFIG_BT_ISO_UNICAST
static void hci_le_cis_disconnected(struct net_buf *buf)
{
struct bt_hci_evt_disconn_complete *evt = (void *)buf->data;
struct bt_conn *iso;
LOG_DBG("CisDisconnectedEvt[%u]", evt->handle);
iso = bt_conn_lookup_handle(evt->handle, BT_CONN_TYPE_ISO);
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)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("CisDisconnEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->cis_dis) {
buf.data = data;
listener->cis_dis(&buf);
}
}
#if CONFIG_BT_ISO_TX
iso_tx_sdu_clear(sys_get_le16(data + 1), false);
#endif /* CONFIG_BT_ISO_TX */
bt_le_host_unlock();
return 0;
}
static int iso_cis_est_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("CisEstEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->cis_est) {
buf.data = data + 1; /* The first octet is subev_code */
listener->cis_est(&buf);
}
}
bt_le_host_unlock();
return 0;
}
#if CONFIG_BT_ISO_PERIPHERAL
static int iso_cis_req_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("CisReqEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->cis_req) {
buf.data = data + 1; /* The first octet is subev_code */
listener->cis_req(&buf);
}
}
bt_le_host_unlock();
return 0;
}
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#endif /* CONFIG_BT_ISO_UNICAST */
#if CONFIG_BT_ISO_BROADCASTER
static int iso_create_big_comp_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("CreateBigCompEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->big_create) {
buf.data = data + 1; /* The first octet is subev_code */
listener->big_create(&buf);
}
}
bt_le_host_unlock();
return 0;
}
static int iso_term_big_comp_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("TermBigCompEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->big_term) {
buf.data = data + 1; /* The first octet is subev_code */
listener->big_term(&buf);
}
}
iso_tx_sdu_clear(data[1], true);
bt_le_host_unlock();
return 0;
}
#endif /* CONFIG_BT_ISO_BROADCASTER */
#if CONFIG_BT_ISO_SYNC_RECEIVER
static int iso_big_sync_est_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("BigSyncEstEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->big_est) {
buf.data = data + 1; /* The first octet is subev_code */
listener->big_est(&buf);
}
}
bt_le_host_unlock();
return 0;
}
static int iso_big_sync_lost_evt_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("BigSyncLostEvtListener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->big_lost) {
buf.data = data + 1; /* The first octet is subev_code */
listener->big_lost(&buf);
}
}
bt_le_host_unlock();
return 0;
}
static int iso_biginfo_adv_report_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
/* LOG_DBG("BigInfoAdvReportEvtListener"); */
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->biginfo) {
buf.data = data + 1; /* The first octet is subev_code */
listener->biginfo(&buf);
}
}
bt_le_host_unlock();
/* The first octet is subev_code */
bt_le_gap_app_biginfo_event(data + 1);
return 0;
}
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
static void handle_iso_event(uint8_t *data, size_t data_len)
{
uint8_t event;
bool le_meta;
/* Note:
* The data_len is guaranteed by the Host that it is exactly
* matching the expected length.
*/
le_meta = data[0];
event = data[1];
if (event != BT_HCI_EVT_LE_BIGINFO_ADV_REPORT) {
LOG_DBG("HandleIsoEvt[%u][%u]", le_meta, event);
}
#if CONFIG_BT_ISO_UNICAST
if (le_meta == false) {
assert(event == BT_HCI_EVT_DISCONN_COMPLETE);
iso_cis_disconn_evt_listener_safe(data + 2);
return;
}
#else /* CONFIG_BT_ISO_UNICAST */
ARG_UNUSED(le_meta);
#endif /* CONFIG_BT_ISO_UNICAST */
switch (event) {
#if CONFIG_BT_ISO_UNICAST
case BT_HCI_EVT_LE_CIS_ESTABLISHED:
iso_cis_est_evt_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);
break;
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#endif /* CONFIG_BT_ISO_UNICAST */
#if CONFIG_BT_ISO_BROADCASTER
case BT_HCI_EVT_LE_BIG_COMPLETE:
iso_create_big_comp_evt_listener_safe(data + 2);
break;
case BT_HCI_EVT_LE_BIG_TERMINATE:
iso_term_big_comp_evt_listener_safe(data + 2);
break;
#endif /* CONFIG_BT_ISO_BROADCASTER */
#if CONFIG_BT_ISO_SYNC_RECEIVER
case BT_HCI_EVT_LE_BIG_SYNC_ESTABLISHED:
iso_big_sync_est_evt_listener_safe(data + 2);
break;
case BT_HCI_EVT_LE_BIG_SYNC_LOST:
iso_big_sync_lost_evt_listener_safe(data + 2);
break;
case BT_HCI_EVT_LE_BIGINFO_ADV_REPORT:
iso_biginfo_adv_report_listener_safe(data + 2);
break;
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
default:
assert(0);
break;
}
}
void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len)
{
assert(data && data_len);
handle_iso_event(data, data_len);
free(data);
}
#if CONFIG_BT_ISO_TX
struct iso_tx_sdu_node {
struct bt_iso_chan *chan;
const uint8_t *sdu;
uint16_t sdu_len;
sys_snode_t node;
};
static sys_slist_t iso_tx_sdu_list = SYS_SLIST_STATIC_INIT(&iso_tx_sdu_list);
static int iso_tx_sdu_insert(struct bt_iso_chan *chan, const uint8_t *sdu, uint16_t sdu_len)
{
struct iso_tx_sdu_node *sdu_node;
sdu_node = calloc(1, sizeof(*sdu_node));
if (sdu_node == NULL) {
LOG_ERR("IsoTxInsertNoMem[%u]", sizeof(*sdu_node));
return -ENOMEM;
}
sdu_node->chan = chan;
sdu_node->sdu = sdu;
sdu_node->sdu_len = sdu_len;
sys_slist_append(&iso_tx_sdu_list, &sdu_node->node);
return 0;
}
static void iso_tx_sdu_clear(uint16_t handle, bool is_big)
{
struct iso_tx_sdu_node *sdu_node;
struct iso_tx_sdu_node *sdu_tmp;
struct bt_conn *iso;
sys_snode_t *prev;
LOG_DBG("IsoTxSduClear[%u][%u]", handle, is_big);
prev = NULL;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&iso_tx_sdu_list, sdu_node, sdu_tmp, node) {
iso = sdu_node->chan->iso;
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)) {
LOG_DBG("IsoSduRem[%u]", sdu_node->sdu_len);
sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node);
free((void *)sdu_node->sdu);
free(sdu_node);
continue;
}
prev = &sdu_node->node;
}
}
static int validate_iso_send(struct bt_iso_chan *chan, struct net_buf *buf)
{
if (chan == NULL || buf == NULL ||
(buf->data == NULL && buf->len != 0)) {
LOG_ERR("InvSendParam[%p][%p]", chan, buf);
return -EINVAL;
}
if (chan->state != BT_ISO_STATE_CONNECTED) {
LOG_ERR("ChanNotConn[%u]", chan->iso->handle);
return -ENOTCONN;
}
if (chan->iso->iso.info.can_send == false) {
LOG_ERR("ChanNotSend[%u]", chan->iso->handle);
return -EINVAL;
}
if (buf->len > chan->qos->tx->sdu) {
LOG_ERR("ChanCannotSend[%u][%u][%u]",
chan->iso->handle, buf->len, chan->qos->tx->sdu);
return -EMSGSIZE;
}
return 0;
}
static int iso_tx_now(struct bt_iso_chan *chan, const uint8_t *sdu,
uint16_t sdu_len, uint16_t seq_num,
uint32_t time_stamp, bool ts_valid)
{
uint16_t data_total_len;
uint8_t pb_flag;
uint8_t *pkt;
int err;
pb_flag = ISO_PKT_COMP_SDU;
data_total_len = (ts_valid ? 4 : 0) + 4 + sdu_len;
/* From Spec:
* In the Host to Controller direction, Data_Total_Length shall
* be less than or equal to the size of buffer supported by the
* Controller (which is returned using the ISO_Data_Packet_Length
* return parameter of the LE Read Buffer Size command).
*
* TODO:
* Check if ISO SDU needs to be fragmented here.
*/
pkt = malloc(4 + data_total_len);
if (pkt == NULL) {
LOG_ERR("IsoTxNoMem[%u]", 4 + data_total_len);
return -ENOMEM;
}
sys_put_le16((ts_valid << 14) | (pb_flag << 12) | chan->iso->handle, pkt);
sys_put_le16(data_total_len, pkt + 2);
if (ts_valid) {
sys_put_le32(time_stamp, pkt + 4);
}
sys_put_le16(seq_num, pkt + (ts_valid ? 8 : 4));
sys_put_le16(sdu_len, pkt + (ts_valid ? 10 : 6));
memcpy(pkt + (ts_valid ? 12 : 8), sdu, sdu_len);
#if ISO_STD_FLOW_CTRL
/* TODO:
* - Use variables to record the ISO buffer size & count;
* - Check if the available ISO buffers are enough to hold the SDU;
*/
#else /* ISO_STD_FLOW_CTRL */
/* If the list is not empty, which means some ISO SDUs ahead of this
* one are still waiting to be transmitted. In this case, this SDU
* must be appended in the list.
*
* If the list is empty, but no free buffer exists in the controller,
* then ISO flow control is triggered.
*/
if (sys_slist_is_empty(&iso_tx_sdu_list) == false ||
r_ble_ll_iso_free_buf_num_get(chan->iso->handle) == 0) {
/* LOG_INF("IsoFlowCtrl!"); */
err = iso_tx_sdu_insert(chan, pkt, 4 + data_total_len);
if (err) {
free(pkt);
}
return err;
}
#endif /* ISO_STD_FLOW_CTRL */
err = r_ble_hci_trans_hs_iso_tx(pkt, 4 + data_total_len, NULL);
if (err) {
/* err = -1: No BIS or CIS found;
* err = -2: Bad ISO packet;
* err = -3: Not able to send (e.g., CIS tx bn is 0, etc);
* err = -4: Not ready to send (e.g. BIS or CIS not established, DP not setup, etc.);
* err = -5: Last SDU fragment (generated over HCI) is failed to send;
*
* Note:
* The allocated pkt buffer will be freed in our controller.
*
* TODO:
* If using controller from other vendors, the pkt buffer may
* needs to be freed here.
*/
LOG_WRN("IsoTxFail[%d]", err);
return err;
}
#if ISO_STD_FLOW_CTRL
/* TODO:
* - Decrease the available ISO buffer count;
* - Increase the available ISO buffer count once NoCP is received;
*/
#endif /* ISO_STD_FLOW_CTRL */
return 0;
}
int bt_le_iso_tx(struct bt_iso_chan *chan,
struct net_buf *buf,
uint16_t seq_num,
uint32_t time_stamp,
bool ts_valid)
{
int err;
err = validate_iso_send(chan, buf);
if (err) {
return err;
}
return iso_tx_now(chan, buf->data, buf->len, seq_num, time_stamp, ts_valid);
}
struct iso_tx_comp_event {
uint16_t conn_handle;
struct bt_iso_tx_cb_info info;
};
void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
{
struct iso_tx_comp_event *evt = (struct iso_tx_comp_event *)data;
struct iso_tx_sdu_node *sdu_node;
struct bt_iso_chan *chan;
struct bt_conn *iso;
bt_conn_tx_cb_t cb;
sys_snode_t *node;
void *ud;
int err;
assert(data && data_len == sizeof(*evt));
bt_le_host_lock();
while (1) {
/* If no free buffer exists, no need to check if any SDU is pending */
if (r_ble_ll_iso_free_buf_num_get(evt->conn_handle) == 0) {
break;
}
/* Fetch and remove the first ISO SDU from list if list is not empty */
node = sys_slist_get(&iso_tx_sdu_list);
if (node == NULL) {
break;
}
sdu_node = CONTAINER_OF(node, struct iso_tx_sdu_node, node);
err = r_ble_hci_trans_hs_iso_tx(sdu_node->sdu, sdu_node->sdu_len, NULL);
if (err) {
/* err = -1: No BIS or CIS found;
* err = -2: Bad ISO packet;
* err = -3: Not able to send (e.g., CIS tx bn is 0, etc);
* err = -4: Not ready to send (e.g. BIS or CIS not established, DP not setup, etc.);
* err = -5: Last SDU fragment (generated over HCI) is failed to send;
*
* Note:
* The allocated pkt buffer will be freed in our controller.
*
* TODO:
* If using controller from other vendors, the pkt buffer may
* needs to be freed here.
*/
LOG_WRN("IsoTxFail[%d]", err);
}
free(sdu_node);
}
iso = bt_conn_lookup_handle(evt->conn_handle, BT_CONN_TYPE_ISO);
if (iso == NULL) {
goto end;
}
chan = iso->iso.chan;
assert(chan);
cb = NULL;
ud = NULL;
chan->iso->get_and_clear_cb(NULL, NULL, &cb, &ud);
if (cb) {
cb(chan->iso, &evt->info, 0);
}
end:
bt_le_host_unlock();
free(data);
}
static void iso_tx_comp_cb(uint16_t conn_handle, void *info, size_t size)
{
struct iso_tx_comp_event *evt;
int err;
/* Note:
* This callback is invoked in the BLE Controller task, the operation in
* this function shall be as simple as possible and avoid log output.
* Avoid acquiring host_lock here to prevent deadlock when the ISO task
* holds host_lock while waiting for a Controller response. Instead, post
* the event to the ISO task for processing.
*/
assert(size == sizeof(struct bt_iso_tx_cb_info));
evt = calloc(1, sizeof(*evt));
if (evt == NULL) {
LOG_ERR("IsoTxCompNoMem[%u]", sizeof(*evt));
return;
}
evt->conn_handle = conn_handle;
memcpy(&evt->info, info, sizeof(evt->info));
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_ISO_TX_COMP, evt, sizeof(*evt));
if (err) {
LOG_ERR("IsoTxCompPostFail[%d]", err);
free(evt);
}
}
#else /* CONFIG_BT_ISO_TX */
void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
{
ARG_UNUSED(data);
ARG_UNUSED(data_len);
}
#endif /* CONFIG_BT_ISO_TX */
#if CONFIG_BT_ISO_RX
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_host_lock();
net_buf_simple_init_with_data(&buf.b, (void *)data, data_len);
hci_iso(&buf);
bt_le_host_unlock();
free(data);
}
int bt_le_iso_rx(const uint8_t *data, uint16_t len, void *arg)
{
uint8_t *rx_data;
int err;
/* Note:
* This callback is invoked in the BLE Controller task. The data directly
* points to the start of an ISO SDU, i.e. the conn_handle, etc. No extra
* information for now.
* Avoid acquiring host_lock here to prevent deadlock when the ISO task
* holds host_lock while waiting for a Controller response. Instead, copy
* the data and post the event to the ISO task for further processing.
*/
ARG_UNUSED(arg);
rx_data = calloc(1, len);
if (rx_data == NULL) {
LOG_ERR("IsoRxNoMem[%u]", len);
return -ENOMEM;
}
memcpy(rx_data, data, len);
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_ISO_RX_DATA, rx_data, len);
if (err) {
LOG_ERR("IsoRxPostFail[%d]", err);
free(rx_data);
return -EIO;
}
return 0;
}
#else /* CONFIG_BT_ISO_RX */
void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len)
{
ARG_UNUSED(data);
ARG_UNUSED(data_len);
}
#endif /* CONFIG_BT_ISO_RX */
int bt_le_iso_disconnect(uint16_t conn_handle, uint8_t reason)
{
return bt_le_nimble_iso_disconnect(conn_handle, reason);
}
static void iso_features_set(void)
{
#if CONFIG_BT_ISO_CENTRAL
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_CIS_CENTRAL);
#endif /* CONFIG_BT_ISO_CENTRAL */
#if CONFIG_BT_ISO_PERIPHERAL
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_CIS_PERIPHERAL);
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#if CONFIG_BT_ISO_BROADCASTER
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_ISO_BROADCASTER);
#endif /* CONFIG_BT_ISO_BROADCASTER */
#if CONFIG_BT_ISO_SYNC_RECEIVER
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_SYNC_RECEIVER);
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
LOG_DBG("IsoFeatSet[%s]", bt_hex(bt_dev.le.features, sizeof(bt_dev.le.features)));
}
static void iso_features_unset(void)
{
#if CONFIG_BT_ISO_CENTRAL
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_CIS_CENTRAL);
#endif /* CONFIG_BT_ISO_CENTRAL */
#if CONFIG_BT_ISO_PERIPHERAL
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_CIS_PERIPHERAL);
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#if CONFIG_BT_ISO_BROADCASTER
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_ISO_BROADCASTER);
#endif /* CONFIG_BT_ISO_BROADCASTER */
#if CONFIG_BT_ISO_SYNC_RECEIVER
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_SYNC_RECEIVER);
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
LOG_DBG("IsoFeatUnset[%s]", bt_hex(bt_dev.le.features, sizeof(bt_dev.le.features)));
}
int bt_le_iso_init(void)
{
int err;
LOG_DBG("IsoInit");
iso_features_set();
err = bt_le_iso_cb_register_safe(&iso_cb);
if (err) {
return err;
}
#if CONFIG_BT_ISO_TX
r_ble_ll_isoal_tx_comp_cb_set(iso_tx_comp_cb);
#endif /* CONFIG_BT_ISO_TX */
err = bt_le_nimble_iso_init();
if (err) {
return err;
}
return 0;
}
void bt_le_iso_deinit(void)
{
LOG_DBG("IsoDeinit");
iso_features_unset();
bt_le_iso_cb_unregister_safe(&iso_cb);
#if CONFIG_BT_ISO_TX
r_ble_ll_isoal_tx_comp_cb_set(NULL);
#endif /* CONFIG_BT_ISO_TX */
bt_le_nimble_iso_deinit();
}
@@ -0,0 +1,504 @@
/*
* 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"
#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 sys_slist_t l2cap_servers = SYS_SLIST_STATIC_INIT(&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_ERR("NotConn[%d]", __LINE__);
*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_ERR("NotConn[%d]", __LINE__);
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_ERR("NotConn[%d]", __LINE__);
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_ERR("NotConn[%d]", __LINE__);
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)
{
int err;
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;
}
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
if (err) {
return err;
}
l2cap_chan_add(conn, chan, psm);
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;
}
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;
}
_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;
}
return bt_le_nimble_l2cap_chan_send(chan, buf);
}
_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 */
err = bt_le_nimble_l2cap_init();
if (err) {
return err;
}
return 0;
}
void bt_le_l2cap_deinit(void)
{
LOG_DBG("L2capDeinit");
/* TODO: L2CAP server deinit */
}
@@ -0,0 +1,492 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Nordic Semiconductor
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* 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/conn.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/hci_core.h>
#include "common/host.h"
static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs);
_LIB_ONLY
int bt_le_scan_cb_register(struct bt_le_scan_cb *cb)
{
LOG_DBG("ScanCbReg");
if (cb == NULL) {
LOG_ERR("ScanCbNull");
return -EINVAL;
}
if (sys_slist_find(&scan_cbs, &cb->node, NULL)) {
LOG_WRN("ScanCbExist");
return -EEXIST;
}
sys_slist_append(&scan_cbs, &cb->node);
return 0;
}
_NOT_USED
void bt_le_scan_cb_unregister(struct bt_le_scan_cb *cb)
{
LOG_DBG("ScanCbUnreg");
if (cb == NULL) {
LOG_ERR("ScanCbNull");
return;
}
sys_slist_find_and_remove(&scan_cbs, &cb->node);
}
static uint8_t get_adv_type(uint8_t evt_type)
{
/* LOG_DBG("GetAdvType[%02x]", evt_type); */
switch (evt_type) {
case (BT_HCI_LE_ADV_EVT_TYPE_CONN |
BT_HCI_LE_ADV_EVT_TYPE_SCAN |
BT_HCI_LE_ADV_EVT_TYPE_LEGACY):
return BT_GAP_ADV_TYPE_ADV_IND;
case (BT_HCI_LE_ADV_EVT_TYPE_CONN |
BT_HCI_LE_ADV_EVT_TYPE_DIRECT |
BT_HCI_LE_ADV_EVT_TYPE_LEGACY):
return BT_GAP_ADV_TYPE_ADV_DIRECT_IND;
case (BT_HCI_LE_ADV_EVT_TYPE_SCAN |
BT_HCI_LE_ADV_EVT_TYPE_LEGACY):
return BT_GAP_ADV_TYPE_ADV_SCAN_IND;
case BT_HCI_LE_ADV_EVT_TYPE_LEGACY:
return BT_GAP_ADV_TYPE_ADV_NONCONN_IND;
case (BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP |
BT_HCI_LE_ADV_EVT_TYPE_CONN |
BT_HCI_LE_ADV_EVT_TYPE_SCAN |
BT_HCI_LE_ADV_EVT_TYPE_LEGACY):
case (BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP |
BT_HCI_LE_ADV_EVT_TYPE_SCAN |
BT_HCI_LE_ADV_EVT_TYPE_LEGACY):
return BT_GAP_ADV_TYPE_SCAN_RSP;
default:
return BT_GAP_ADV_TYPE_EXT_ADV;
}
}
_IDF_ONLY
void bt_le_scan_recv_listener(uint16_t event_type,
uint8_t addr_type,
uint8_t addr_val[6],
int8_t rssi,
int8_t tx_power,
uint8_t sid,
uint8_t pri_phy,
uint8_t sec_phy,
uint16_t per_adv_itvl,
uint8_t data_len,
uint8_t *data)
{
struct net_buf_simple_state state = {0};
struct bt_le_scan_recv_info info = {0};
struct bt_le_scan_cb *listener = NULL;
struct net_buf_simple buf = {0};
bt_addr_le_t addr = {0};
/* LOG_DBG("ScanRecvListener[%u]", data_len); */
if (data == NULL || data_len == 0) {
LOG_DBG("ScanRecvDataNull[%u]", data_len);
return;
}
addr.type = addr_type;
memcpy(addr.a.val, addr_val, BT_ADDR_SIZE);
info.addr = &addr;
info.sid = sid;
info.rssi = rssi;
info.tx_power = tx_power;
info.adv_type = get_adv_type(event_type);
info.adv_props = event_type;
info.interval = per_adv_itvl;
info.primary_phy = pri_phy;
info.secondary_phy = sec_phy;
net_buf_simple_init_with_data(&buf, (void *)data, data_len);
SYS_SLIST_FOR_EACH_CONTAINER(&scan_cbs, listener, node) {
if (listener->recv) {
net_buf_simple_save(&buf, &state);
listener->recv(&info, &buf);
net_buf_simple_restore(&buf, &state);
}
}
}
static struct bt_le_per_adv_sync per_adv_sync_pool[CONFIG_BT_PER_ADV_SYNC_MAX];
static sys_slist_t pa_sync_cbs = SYS_SLIST_STATIC_INIT(&pa_sync_cbs);
_LIB_ONLY
int bt_le_per_adv_sync_cb_register(struct bt_le_per_adv_sync_cb *cb)
{
LOG_DBG("PaSyncCbReg");
if (cb == NULL) {
LOG_ERR("PaSyncCbNull");
return -EINVAL;
}
if (sys_slist_find(&pa_sync_cbs, &cb->node, NULL)) {
LOG_WRN("PaSyncCbExist");
return -EEXIST;
}
sys_slist_append(&pa_sync_cbs, &cb->node);
return 0;
}
_LIB_ONLY
int bt_le_per_adv_sync_get_info(struct bt_le_per_adv_sync *per_adv_sync,
struct bt_le_per_adv_sync_info *info)
{
LOG_DBG("PaSyncGetInfo");
if (per_adv_sync == NULL || info == NULL) {
LOG_ERR("PaSyncInfoNull[%p][%p]", per_adv_sync, info);
return -EINVAL;
}
bt_addr_le_copy(&info->addr, &per_adv_sync->addr);
info->sid = per_adv_sync->sid;
info->phy = per_adv_sync->phy;
info->interval = per_adv_sync->interval;
LOG_DBG("[%s][%u][%u][%u]",
bt_addr_le_str(&info->addr), info->sid, info->phy, info->interval);
return 0;
}
_LIB_ONLY
struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *adv_addr,
uint8_t sid)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
assert(adv_addr);
LOG_DBG("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid);
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags, BT_PER_ADV_SYNC_SYNCED) &&
bt_addr_le_eq(&per_adv_sync_pool[i].addr, adv_addr) &&
per_adv_sync_pool[i].sid == sid) {
per_adv_sync = &per_adv_sync_pool[i];
break;
}
}
if (per_adv_sync == NULL) {
LOG_INF("PaSyncNotFound");
}
return per_adv_sync;
}
static struct bt_le_per_adv_sync *per_adv_sync_find(uint16_t handle)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags,
BT_PER_ADV_SYNC_SYNCED) &&
per_adv_sync_pool[i].handle == handle) {
per_adv_sync = &per_adv_sync_pool[i];
break;
}
}
return per_adv_sync;
}
_IDF_ONLY
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);
bt_le_host_unlock();
return per_adv_sync;
}
static struct bt_le_per_adv_sync *per_adv_sync_new(void)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags,
BT_PER_ADV_SYNC_SYNCED) == false) {
per_adv_sync = &per_adv_sync_pool[i];
break;
}
}
if (per_adv_sync) {
(void)memset(per_adv_sync, 0, sizeof(*per_adv_sync));
atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED);
}
return per_adv_sync;
}
_IDF_ONLY
int bt_le_per_adv_sync_new(uint16_t sync_handle,
uint8_t sid,
uint8_t phy,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
struct bt_le_per_adv_sync **out_sync)
{
struct bt_le_per_adv_sync *per_adv_sync;
LOG_DBG("PaSyncNew[%u][%u][%u][%u]", sync_handle, sid, phy, interval);
if (addr_type > BT_ADDR_LE_RANDOM_ID || addr == NULL) {
LOG_ERR("InvAddr[%02x][%p]", addr_type, addr);
return -EINVAL;
}
per_adv_sync = per_adv_sync_find(sync_handle);
if (per_adv_sync) {
LOG_WRN("PaSyncExist[%u]", sync_handle);
return -EEXIST;
}
per_adv_sync = per_adv_sync_new();
if (per_adv_sync == NULL) {
LOG_ERR("NoFreePaSync[%u]", sync_handle);
return -ENOMEM;
}
per_adv_sync->handle = sync_handle;
per_adv_sync->sid = sid;
per_adv_sync->phy = phy;
per_adv_sync->interval = interval;
per_adv_sync->addr.type = addr_type;
memcpy(per_adv_sync->addr.a.val, addr, BT_ADDR_SIZE);
LOG_DBG("Addr[%s]", bt_addr_le_str(&per_adv_sync->addr));
if (out_sync) {
*out_sync = per_adv_sync;
}
return 0;
}
_IDF_ONLY
int bt_le_per_adv_sync_delete(uint16_t sync_handle)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
LOG_DBG("PaSyncDelete[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
}
(void)memset(per_adv_sync, 0, sizeof(*per_adv_sync));
return 0;
}
_IDF_ONLY
int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
{
struct bt_le_per_adv_sync_synced_info info = {0};
struct bt_le_per_adv_sync *per_adv_sync = NULL;
struct bt_le_per_adv_sync_cb *listener = NULL;
LOG_DBG("PaSyncEstabListener[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
}
info.addr = &per_adv_sync->addr;
info.sid = per_adv_sync->sid;
info.interval = per_adv_sync->interval;
info.phy = per_adv_sync->phy;
info.conn = NULL; /* TODO: update for PAST */
SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) {
if (listener->synced) {
listener->synced(per_adv_sync, &info);
}
}
return 0;
}
_IDF_ONLY
int bt_le_per_adv_sync_lost_listener(uint16_t sync_handle)
{
struct bt_le_per_adv_sync_term_info info = {0};
struct bt_le_per_adv_sync *per_adv_sync = NULL;
struct bt_le_per_adv_sync_cb *listener = NULL;
LOG_DBG("PaSyncLostListener[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
}
info.addr = &per_adv_sync->addr;
info.sid = per_adv_sync->sid;
SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) {
if (listener->term) {
listener->term(per_adv_sync, &info);
}
}
return 0;
}
_IDF_ONLY
int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle,
const uint8_t *data,
size_t data_length)
{
struct bt_le_per_adv_sync_recv_info info = {0};
struct bt_le_per_adv_sync *per_adv_sync = NULL;
struct bt_le_per_adv_sync_cb *listener = NULL;
struct net_buf_simple_state state = {0};
struct net_buf_simple buf = {0};
/* LOG_DBG("PaSyncReportRecvListener[%u][%u]", sync_handle, data_length); */
if (data == NULL || data_length == 0) {
LOG_DBG("PaSyncRecvDataNull[%u]", data_length);
return -EINVAL;
}
per_adv_sync = per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
}
info.addr = &per_adv_sync->addr;
info.sid = per_adv_sync->sid;
net_buf_simple_init_with_data(&buf, (void *)data, data_length);
SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) {
if (listener->recv) {
net_buf_simple_save(&buf, &state);
listener->recv(per_adv_sync, &info, &buf);
net_buf_simple_restore(&buf, &state);
}
}
return 0;
}
_IDF_ONLY
void hci_le_biginfo_adv_report(struct net_buf *buf)
{
struct bt_hci_evt_le_biginfo_adv_report *evt = (void *)buf->data;
struct bt_le_per_adv_sync *per_adv_sync = NULL;
struct bt_le_per_adv_sync_cb *listener = NULL;
struct bt_iso_biginfo biginfo = {0};
/* LOG_DBG("BigInfoRecvListener[%u]", evt->sync_handle); */
per_adv_sync = per_adv_sync_find(evt->sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", evt->sync_handle);
return;
}
biginfo.addr = &per_adv_sync->addr;
biginfo.sid = per_adv_sync->sid;
biginfo.num_bis = evt->num_bis;
biginfo.sub_evt_count = evt->nse;
biginfo.iso_interval = evt->iso_interval;
biginfo.burst_number = evt->bn;
biginfo.offset = evt->pto;
biginfo.rep_count = evt->irc;
biginfo.max_pdu = evt->max_pdu;
biginfo.sdu_interval = sys_get_le24(evt->sdu_interval);
biginfo.max_sdu = evt->max_sdu;
biginfo.phy = evt->phy;
biginfo.framing = evt->framing;
biginfo.encryption = (evt->encryption ? true : false);
SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) {
if (listener->biginfo) {
listener->biginfo(per_adv_sync, &biginfo);
}
}
}
_LIB_ONLY
int bt_le_scan_start(const struct bt_le_scan_param *param, void *cb)
{
int err = 0;
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) == false) {
err = bt_le_nimble_scan_start(param, cb);
if (err == 0) {
atomic_set_bit(bt_dev.flags, BT_DEV_SCANNING);
}
} else {
err = -EALREADY;
}
return err;
}
_LIB_ONLY
int bt_le_scan_stop(void)
{
int err = 0;
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) {
err = bt_le_nimble_scan_stop();
if (err == 0) {
atomic_clear_bit(bt_dev.flags, BT_DEV_SCANNING);
}
}
return err;
}
@@ -0,0 +1,127 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <../host/conn_internal.h>
#include "common/host.h"
#include "common/iso.h"
#include "common/app/gap.h"
#include "common/app/gatt.h"
static QueueHandle_t iso_queue_handle;
static TaskHandle_t iso_task_handle;
extern void bt_le_timer_handle_event(void *arg);
static void iso_task(void *p)
{
struct iso_queue_item item = {0};
while (1) {
xQueueReceive(iso_queue_handle, &item, portMAX_DELAY);
switch (item.type) {
case ISO_QUEUE_ITEM_TYPE_TIMER_EVENT:
bt_le_timer_handle_event(item.data);
break;
case ISO_QUEUE_ITEM_TYPE_GAP_EVENT:
bt_le_gap_handle_event(item.data, item.data_len);
break;
case ISO_QUEUE_ITEM_TYPE_GATT_EVENT:
bt_le_gatt_handle_event(item.data, item.data_len);
break;
case ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT:
bt_le_iso_handle_hci_event(item.data, item.data_len);
break;
case ISO_QUEUE_ITEM_TYPE_ISO_TX_COMP:
bt_le_iso_handle_tx_comp(item.data, item.data_len);
break;
case ISO_QUEUE_ITEM_TYPE_ISO_RX_DATA:
bt_le_iso_handle_rx_data(item.data, item.data_len);
break;
default:
if (item.data) {
free(item.data);
}
assert(0);
break;
}
}
}
int bt_le_iso_task_post(enum iso_queue_item_type type,
void *data, size_t data_len)
{
struct iso_queue_item item = {0};
int ret;
item.type = type;
item.data = data;
item.data_len = data_len;
ret = xQueueSend(iso_queue_handle, &item, portMAX_DELAY);
if (ret != pdTRUE) {
LOG_ERR("IsoQPostFail[%d]", ret);
return -1;
}
return 0;
}
int bt_le_iso_task_init(void)
{
int ret;
LOG_DBG("IsoTaskInit");
iso_queue_handle = xQueueCreate(ISO_QUEUE_ITEM_COUNT, ISO_QUEUE_ITEM_SIZE);
if (iso_queue_handle == NULL) {
LOG_ERR("IsoQCreateFail");
return -EIO;
}
ret = xTaskCreatePinnedToCore(iso_task,
ISO_TASK_NAME,
ISO_TASK_STACK_SIZE,
NULL,
ISO_TASK_PRIO,
&iso_task_handle,
ISO_TASK_CORE);
if (ret != pdTRUE) {
LOG_ERR("IsoTaskCreateFail[%d]", ret);
vQueueDelete(iso_queue_handle);
iso_queue_handle = NULL;
return -EIO;
}
return 0;
}
void bt_le_iso_task_deinit(void)
{
LOG_DBG("IsoTaskDeinit");
if (iso_task_handle) {
vTaskDelete(iso_task_handle);
iso_task_handle = NULL;
}
if (iso_queue_handle) {
vQueueDelete(iso_queue_handle);
iso_queue_handle = NULL;
}
}