mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
933 lines
25 KiB
C
933 lines
25 KiB
C
/*
|
|
* 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"
|
|
|
|
LOG_MODULE_REGISTER(ISO_SHIM, CONFIG_BT_ISO_LOG_LEVEL);
|
|
|
|
#if CONFIG_BT_BLUEDROID_ENABLED
|
|
#ifdef CONFIG_BT_BLE_ISO_STD_FLOW_CTRL
|
|
#define ISO_STD_FLOW_CTRL true
|
|
#else /* CONFIG_BT_BLE_ISO_STD_FLOW_CTRL */
|
|
#define ISO_STD_FLOW_CTRL false
|
|
#endif /* CONFIG_BT_BLE_ISO_STD_FLOW_CTRL */
|
|
#else /* CONFIG_BT_BLUEDROID_ENABLED */
|
|
#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 */
|
|
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
|
|
|
#define ISO_PKT_FIRST_FRAG (0b00)
|
|
#define ISO_PKT_CONT_FRAG (0b01)
|
|
#define ISO_PKT_COMP_SDU (0b10)
|
|
#define ISO_PKT_LAST_FRAG (0b11)
|
|
|
|
/* Both callbacks that feed the iso task from the controller task (iso_tx_comp_cb,
|
|
* bt_le_iso_rx) can fail to hand off an item, and neither may log there:
|
|
* esp_log_write is a blocking UART write (~3 ms/line at 115200), so reporting at
|
|
* the failure rate would spend most of a second inside it and deepen the very
|
|
* congestion it reports. They only count; the matching iso-task handler reports
|
|
* once every ISO_DROP_REPORT_STEP items, by which point the queue has room again
|
|
* so the report never competes with the burst that caused it. Causes are counted
|
|
* apart because they need different fixes: a full queue means the iso task is
|
|
* behind, a failed alloc means the heap is exhausted. */
|
|
#define ISO_DROP_REPORT_STEP 100
|
|
|
|
static BT_ISO_EXT_RAM_BSS_ATTR sys_slist_t 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,
|
|
.cis_est_v2 = hci_le_cis_established_v2,
|
|
#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(struct bt_le_iso_cb *cb)
|
|
{
|
|
LOG_DBG("IsoCbReg");
|
|
|
|
if (cb == NULL) {
|
|
LOG_ERR("IsoCbRegNull");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sys_slist_find(&iso_cbs, &cb->node, NULL)) {
|
|
LOG_WRN("IsoCbRegExist");
|
|
return -EEXIST;
|
|
}
|
|
|
|
sys_slist_append(&iso_cbs, &cb->node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
_IDF_ONLY
|
|
void bt_le_iso_cb_unregister(struct bt_le_iso_cb *cb)
|
|
{
|
|
LOG_DBG("IsoCbUnreg");
|
|
|
|
if (cb == NULL) {
|
|
LOG_ERR("IsoCbUnregNull");
|
|
return;
|
|
}
|
|
|
|
sys_slist_find_and_remove(&iso_cbs, &cb->node);
|
|
}
|
|
|
|
#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);
|
|
BT_LE_ASSERT(iso);
|
|
|
|
iso->err = evt->reason;
|
|
|
|
bt_iso_disconnected(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;
|
|
}
|
|
|
|
static int iso_cis_est_evt_v2_listener_safe(uint8_t *data)
|
|
{
|
|
struct bt_le_iso_cb *listener = NULL;
|
|
struct net_buf buf = {0};
|
|
|
|
LOG_DBG("CisEstEvtV2Listener");
|
|
|
|
bt_le_host_lock();
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
|
|
if (listener->cis_est_v2) {
|
|
buf.data = data + 1; /* The first octet is subev_code */
|
|
listener->cis_est_v2(&buf);
|
|
}
|
|
}
|
|
|
|
bt_le_host_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_BT_ISO_PERIPHERAL
|
|
static int iso_cis_req_evt_listener_safe(uint8_t *data)
|
|
{
|
|
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) {
|
|
BT_LE_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;
|
|
case BT_HCI_EVT_LE_CIS_ESTABLISHED_V2:
|
|
iso_cis_est_evt_v2_listener_safe(data + 2);
|
|
break;
|
|
#if CONFIG_BT_ISO_PERIPHERAL
|
|
case BT_HCI_EVT_LE_CIS_REQ:
|
|
iso_cis_req_evt_listener_safe(data + 2);
|
|
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:
|
|
LOG_ERR("HandleIsoEvtUnknown[%u]", event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len)
|
|
{
|
|
BT_LE_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 BT_ISO_EXT_RAM_BSS_ATTR sys_slist_t 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 = bt_le_int_calloc(1, sizeof(*sdu_node));
|
|
if (sdu_node == NULL) {
|
|
LOG_ERR("IsoTxSduInsertNoMem[%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;
|
|
|
|
/* Peripheral CIS clears chan->iso during disconnect (before this runs);
|
|
* the SDU is orphaned — drop it instead of dereferencing NULL. */
|
|
if (iso == NULL) {
|
|
sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node);
|
|
free((void *)sdu_node->sdu);
|
|
free(sdu_node);
|
|
continue;
|
|
}
|
|
|
|
if ((is_big == false && iso->handle == handle) ||
|
|
(is_big && iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER &&
|
|
iso->iso.info.broadcaster.big_handle == handle)) {
|
|
LOG_DBG("IsoTxSduClearRem[%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("IsoSendInvParam[%p][%p]", chan, buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chan->iso == NULL) {
|
|
LOG_ERR("IsoSendChanNoIso");
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (chan->state != BT_ISO_STATE_CONNECTED) {
|
|
LOG_ERR("IsoSendChanNotConn[%u]", chan->iso->handle);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (chan->iso->iso.info.can_send == false) {
|
|
LOG_ERR("IsoSendChanNotSend[%u]", chan->iso->handle);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (buf->len > chan->qos->tx->sdu) {
|
|
LOG_ERR("IsoSendChanCannotSend[%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 = bt_le_int_malloc(4 + data_total_len);
|
|
if (pkt == NULL) {
|
|
LOG_ERR("IsoTxNowNoMem[%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("IsoTxNowFail[%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;
|
|
};
|
|
|
|
/* See ISO_DROP_REPORT_STEP. */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_tx_comp_drop_cnt; /* task queue full */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_tx_comp_nomem_cnt; /* evt alloc failed */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_tx_comp_reported;
|
|
|
|
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;
|
|
uint32_t dropped;
|
|
void *ud;
|
|
int err;
|
|
|
|
BT_LE_ASSERT(data && data_len == sizeof(*evt));
|
|
|
|
dropped = iso_tx_comp_drop_cnt + iso_tx_comp_nomem_cnt;
|
|
if (dropped - iso_tx_comp_reported >= ISO_DROP_REPORT_STEP) {
|
|
iso_tx_comp_reported = dropped;
|
|
LOG_WRN("IsoTxCompDrop[q=%u][nomem=%u]",
|
|
iso_tx_comp_drop_cnt, iso_tx_comp_nomem_cnt);
|
|
}
|
|
|
|
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("IsoTxCompFail[%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;
|
|
BT_LE_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.
|
|
*/
|
|
|
|
BT_LE_ASSERT(size == sizeof(struct bt_iso_tx_cb_info));
|
|
|
|
evt = bt_le_int_calloc(1, sizeof(*evt));
|
|
if (evt == NULL) {
|
|
iso_tx_comp_nomem_cnt++;
|
|
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) {
|
|
iso_tx_comp_drop_cnt++;
|
|
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
|
|
/* See ISO_DROP_REPORT_STEP. */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_rx_drop_cnt; /* task queue full */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_rx_nomem_cnt; /* rx_data alloc failed */
|
|
static BT_ISO_CTRL_BSS_ATTR uint32_t iso_rx_reported;
|
|
|
|
void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len)
|
|
{
|
|
struct net_buf buf = {0};
|
|
uint32_t dropped;
|
|
|
|
BT_LE_ASSERT(data && data_len);
|
|
|
|
dropped = iso_rx_drop_cnt + iso_rx_nomem_cnt;
|
|
if (dropped - iso_rx_reported >= ISO_DROP_REPORT_STEP) {
|
|
iso_rx_reported = dropped;
|
|
LOG_WRN("IsoRxDrop[q=%u][nomem=%u]", iso_rx_drop_cnt, iso_rx_nomem_cnt);
|
|
}
|
|
|
|
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 = bt_le_int_calloc(1, len);
|
|
if (rx_data == NULL) {
|
|
iso_rx_nomem_cnt++;
|
|
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) {
|
|
iso_rx_drop_cnt++;
|
|
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)
|
|
{
|
|
#if CONFIG_BT_BLUEDROID_ENABLED
|
|
return bt_le_bluedroid_iso_disconnect(conn_handle, reason);
|
|
#else
|
|
return bt_le_nimble_iso_disconnect(conn_handle, reason);
|
|
#endif
|
|
}
|
|
|
|
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();
|
|
|
|
bt_le_host_lock();
|
|
err = bt_le_iso_cb_register(&iso_cb);
|
|
bt_le_host_unlock();
|
|
|
|
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 */
|
|
|
|
#if CONFIG_BT_BLUEDROID_ENABLED
|
|
err = bt_le_bluedroid_iso_init();
|
|
#else
|
|
err = bt_le_nimble_iso_init();
|
|
#endif
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_le_iso_deinit(void)
|
|
{
|
|
LOG_DBG("IsoDeinit");
|
|
|
|
iso_features_unset();
|
|
|
|
bt_le_host_lock();
|
|
bt_le_iso_cb_unregister(&iso_cb);
|
|
bt_le_host_unlock();
|
|
|
|
#if CONFIG_BT_ISO_TX
|
|
r_ble_ll_isoal_tx_comp_cb_set(NULL);
|
|
#endif /* CONFIG_BT_ISO_TX */
|
|
|
|
#if CONFIG_BT_BLUEDROID_ENABLED
|
|
bt_le_bluedroid_iso_deinit();
|
|
#else
|
|
bt_le_nimble_iso_deinit();
|
|
#endif
|
|
}
|