blob: f71625f22ac2ecc3a6871ab94c43e0b4c15dcaa1 [file] [log] [blame]
/* Bluetooth ISO */
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/iso.h>
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "iso_internal.h"
#include "common/assert.h"
#define LOG_LEVEL CONFIG_BT_ISO_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_iso);
#if defined(CONFIG_BT_DEBUG_ISO_DATA)
#define BT_ISO_DATA_DBG(fmt, ...) LOG_DBG(fmt, ##__VA_ARGS__)
#else
#define BT_ISO_DATA_DBG(fmt, ...)
#endif /* CONFIG_BT_DEBUG_ISO_DATA */
#define iso_chan(_iso) ((_iso)->iso.chan);
#if defined(CONFIG_BT_ISO_RX)
NET_BUF_POOL_FIXED_DEFINE(iso_rx_pool, CONFIG_BT_ISO_RX_BUF_COUNT,
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_RX_MTU),
sizeof(struct iso_data), NULL);
static struct bt_iso_recv_info iso_info_data[CONFIG_BT_ISO_RX_BUF_COUNT];
#define iso_info(buf) (&iso_info_data[net_buf_id(buf)])
#endif /* CONFIG_BT_ISO_RX */
#if defined(CONFIG_BT_ISO_UNICAST) || defined(CONFIG_BT_ISO_BROADCAST)
NET_BUF_POOL_FIXED_DEFINE(iso_tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT,
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
#if CONFIG_BT_ISO_TX_FRAG_COUNT > 0
NET_BUF_POOL_FIXED_DEFINE(iso_frag_pool, CONFIG_BT_ISO_TX_FRAG_COUNT,
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
#endif /* CONFIG_BT_ISO_TX_FRAG_COUNT > 0 */
#endif /* CONFIG_BT_ISO_UNICAST || CONFIG_BT_ISO_BROADCAST */
struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN];
/* TODO: Allow more than one server? */
#if defined(CONFIG_BT_ISO_CENTRAL)
struct bt_iso_cig cigs[CONFIG_BT_ISO_MAX_CIG];
static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan);
static void bt_iso_remove_data_path(struct bt_conn *iso);
static int hci_le_create_cis(const struct bt_iso_connect_param *param,
size_t count);
#endif /* CONFIG_BT_ISO_CENTRAL */
#if defined(CONFIG_BT_ISO_PERIPHERAL)
static struct bt_iso_server *iso_server;
static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl);
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#if defined(CONFIG_BT_ISO_BROADCAST)
struct bt_iso_big bigs[CONFIG_BT_ISO_MAX_BIG];
static struct bt_iso_big *lookup_big_by_handle(uint8_t big_handle);
#endif /* CONFIG_BT_ISO_BROADCAST */
#if defined(CONFIG_BT_ISO_TX)
static void bt_iso_send_cb(struct bt_conn *iso, void *user_data, int err)
{
struct bt_iso_chan *chan = iso->iso.chan;
struct bt_iso_chan_ops *ops;
__ASSERT(chan != NULL, "NULL chan for iso %p", iso);
ops = chan->ops;
if (!err && ops != NULL && ops->sent != NULL) {
ops->sent(chan);
}
}
#endif /* CONFIG_BT_ISO_TX */
void hci_iso(struct net_buf *buf)
{
struct bt_hci_iso_hdr *hdr;
uint16_t handle, len;
struct bt_conn *iso;
uint8_t flags;
BT_ISO_DATA_DBG("buf %p", buf);
if (buf->len < sizeof(*hdr)) {
LOG_ERR("Invalid HCI ISO packet size (%u)", buf->len);
net_buf_unref(buf);
return;
}
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
len = bt_iso_hdr_len(sys_le16_to_cpu(hdr->len));
handle = sys_le16_to_cpu(hdr->handle);
flags = bt_iso_flags(handle);
iso(buf)->handle = bt_iso_handle(handle);
iso(buf)->index = BT_CONN_INDEX_INVALID;
BT_ISO_DATA_DBG("handle %u len %u flags %u",
iso(buf)->handle, len, flags);
if (buf->len != len) {
LOG_ERR("ISO data length mismatch (%u != %u)", buf->len, len);
net_buf_unref(buf);
return;
}
iso = bt_conn_lookup_handle(iso(buf)->handle, BT_CONN_TYPE_ISO);
if (iso == NULL) {
LOG_ERR("Unable to find conn for handle %u", iso(buf)->handle);
net_buf_unref(buf);
return;
}
iso(buf)->index = bt_conn_index(iso);
bt_conn_recv(iso, buf, flags);
bt_conn_unref(iso);
}
static struct bt_conn *iso_new(void)
{
struct bt_conn *iso = bt_conn_new(iso_conns, ARRAY_SIZE(iso_conns));
if (iso) {
iso->type = BT_CONN_TYPE_ISO;
} else {
LOG_DBG("Could not create new ISO");
}
return iso;
}
#if defined(CONFIG_NET_BUF_LOG)
struct net_buf *bt_iso_create_pdu_timeout_debug(struct net_buf_pool *pool,
size_t reserve,
k_timeout_t timeout,
const char *func, int line)
#else
struct net_buf *bt_iso_create_pdu_timeout(struct net_buf_pool *pool,
size_t reserve, k_timeout_t timeout)
#endif
{
if (!pool) {
pool = &iso_tx_pool;
}
reserve += sizeof(struct bt_hci_iso_data_hdr);
#if defined(CONFIG_NET_BUF_LOG)
return bt_conn_create_pdu_timeout_debug(pool, reserve, timeout, func,
line);
#else
return bt_conn_create_pdu_timeout(pool, reserve, timeout);
#endif
}
#if defined(CONFIG_NET_BUF_LOG)
struct net_buf *bt_iso_create_frag_timeout_debug(size_t reserve,
k_timeout_t timeout,
const char *func, int line)
#else
struct net_buf *bt_iso_create_frag_timeout(size_t reserve, k_timeout_t timeout)
#endif
{
struct net_buf_pool *pool = NULL;
#if CONFIG_BT_ISO_TX_FRAG_COUNT > 0
pool = &iso_frag_pool;
#endif /* CONFIG_BT_ISO_TX_FRAG_COUNT > 0 */
#if defined(CONFIG_NET_BUF_LOG)
return bt_conn_create_pdu_timeout_debug(pool, reserve, timeout, func,
line);
#else
return bt_conn_create_pdu_timeout(pool, reserve, timeout);
#endif
}
static int hci_le_setup_iso_data_path(const struct bt_conn *iso, uint8_t dir,
const struct bt_iso_chan_path *path)
{
struct bt_hci_cp_le_setup_iso_path *cp;
struct bt_hci_rp_le_setup_iso_path *rp;
struct net_buf *buf, *rsp;
uint8_t *cc;
int err;
__ASSERT(dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR ||
dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST,
"invalid ISO data path dir: %u", dir);
if ((path->cc == NULL && path->cc_len != 0)) {
LOG_DBG("Invalid ISO data path CC: %p %u", path->cc, path->cc_len);
return -EINVAL;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SETUP_ISO_PATH, sizeof(*cp) + path->cc_len);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(iso->handle);
cp->path_dir = dir;
cp->path_id = path->pid;
cp->codec_id.coding_format = path->format;
cp->codec_id.company_id = sys_cpu_to_le16(path->cid);
cp->codec_id.vs_codec_id = sys_cpu_to_le16(path->vid);
sys_put_le24(path->delay, cp->controller_delay);
cp->codec_config_len = path->cc_len;
cc = net_buf_add(buf, path->cc_len);
if (path->cc_len) {
memcpy(cc, path->cc, path->cc_len);
}
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SETUP_ISO_PATH, buf, &rsp);
if (err) {
return err;
}
rp = (void *)rsp->data;
if (rp->status || (sys_le16_to_cpu(rp->handle) != iso->handle)) {
err = -EIO;
}
net_buf_unref(rsp);
return err;
}
static void bt_iso_chan_add(struct bt_conn *iso, struct bt_iso_chan *chan)
{
/* Attach ISO channel to the connection */
chan->iso = iso;
iso->iso.chan = chan;
LOG_DBG("iso %p chan %p", iso, chan);
}
static int bt_iso_setup_data_path(struct bt_iso_chan *chan)
{
int err;
struct bt_iso_chan_path default_hci_path = { .pid = BT_ISO_DATA_PATH_HCI,
.format = BT_HCI_CODING_FORMAT_TRANSPARENT,
.cc_len = 0x00 };
struct bt_iso_chan_path *out_path = NULL;
struct bt_iso_chan_path *in_path = NULL;
struct bt_iso_chan_io_qos *tx_qos;
struct bt_iso_chan_io_qos *rx_qos;
struct bt_conn *iso;
uint8_t dir;
iso = chan->iso;
tx_qos = chan->qos->tx;
rx_qos = chan->qos->rx;
/* The following code sets the in and out paths for ISO data.
* If the application provides a path for a direction (tx/rx) we use
* that, otherwise we simply fall back to HCI.
*
* If the direction is not set (by whether tx_qos or rx_qos is NULL),
* then we fallback to the HCI path object, but we disable the direction
* in the controller.
*/
if (tx_qos != NULL && iso->iso.info.can_send) {
if (tx_qos->path != NULL) { /* Use application path */
in_path = tx_qos->path;
} else { /* else fallback to HCI path */
in_path = &default_hci_path;
}
}
if (rx_qos != NULL && iso->iso.info.can_recv) {
if (rx_qos->path != NULL) { /* Use application path */
out_path = rx_qos->path;
} else { /* else fallback to HCI path */
out_path = &default_hci_path;
}
}
__ASSERT(in_path || out_path,
"At least one path shall be shell: in %p out %p",
in_path, out_path);
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER && in_path) {
dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR;
err = hci_le_setup_iso_data_path(iso, dir, in_path);
if (err != 0) {
LOG_DBG("Failed to set broadcaster data path: %d", err);
}
return err;
} else if (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) &&
iso->iso.info.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER &&
out_path) {
dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST;
err = hci_le_setup_iso_data_path(iso, dir, out_path);
if (err != 0) {
LOG_DBG("Failed to set sync receiver data path: %d", err);
}
return err;
} else if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
iso->iso.info.type == BT_ISO_CHAN_TYPE_CONNECTED) {
if (in_path != NULL) {
/* Enable TX */
dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR;
err = hci_le_setup_iso_data_path(iso, dir, in_path);
if (err) {
LOG_DBG("Failed to setup host-to-ctrl path: %d", err);
return err;
}
}
if (out_path != NULL) {
/* Enable RX */
dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST;
err = hci_le_setup_iso_data_path(iso, dir, out_path);
if (err) {
LOG_DBG("Failed to setup ctlr-to-host path: %d", err);
return err;
}
}
return 0;
} else {
__ASSERT(false, "Invalid iso.info.type: %u",
iso->iso.info.type);
return -EINVAL;
}
}
void bt_iso_connected(struct bt_conn *iso)
{
struct bt_iso_chan *chan;
int err;
if (iso == NULL || iso->type != BT_CONN_TYPE_ISO) {
LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, iso ? iso->type : 0);
return;
}
LOG_DBG("%p", iso);
chan = iso_chan(iso);
if (chan == NULL) {
LOG_ERR("Could not lookup chan from connected ISO");
return;
}
err = bt_iso_setup_data_path(chan);
if (err != 0) {
if (false) {
#if defined(CONFIG_BT_ISO_BROADCAST)
} else if (iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER ||
iso->iso.info.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER) {
struct bt_iso_big *big;
big = lookup_big_by_handle(iso->iso.big_handle);
err = bt_iso_big_terminate(big);
if (err != 0) {
LOG_ERR("Could not terminate BIG: %d", err);
}
#endif /* CONFIG_BT_ISO_BROADCAST */
} else if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
iso->iso.info.type == BT_ISO_CHAN_TYPE_CONNECTED) {
bt_conn_disconnect(iso,
BT_HCI_ERR_REMOTE_USER_TERM_CONN);
} else {
__ASSERT(false, "Invalid iso.info.type: %u",
iso->iso.info.type);
}
return;
}
bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED);
if (chan->ops->connected) {
chan->ops->connected(chan);
}
}
static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
LOG_DBG("%p, reason 0x%02x", chan, reason);
__ASSERT(chan->iso != NULL, "NULL conn for iso chan %p", chan);
bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTED);
/* The peripheral does not have the concept of a CIG, so once a CIS
* disconnects it is completely freed by unref'ing it
*/
if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
chan->iso->iso.info.type == BT_ISO_CHAN_TYPE_CONNECTED) {
bt_iso_cleanup_acl(chan->iso);
if (chan->iso->role == BT_HCI_ROLE_PERIPHERAL) {
bt_conn_unref(chan->iso);
chan->iso = NULL;
#if defined(CONFIG_BT_ISO_CENTRAL)
} else {
/* ISO data paths are automatically removed when the
* peripheral disconnects, so we only need to
* move it for the central
*/
bt_iso_remove_data_path(chan->iso);
bool is_chan_connected;
struct bt_iso_cig *cig;
struct bt_iso_chan *cis_chan;
/* Update CIG state */
cig = get_cig(chan);
__ASSERT(cig != NULL, "CIG was NULL");
is_chan_connected = false;
SYS_SLIST_FOR_EACH_CONTAINER(&cig->cis_channels, cis_chan, node) {
if (cis_chan->state == BT_ISO_STATE_CONNECTED ||
cis_chan->state == BT_ISO_STATE_CONNECTING) {
is_chan_connected = true;
break;
}
}
if (!is_chan_connected) {
cig->state = BT_ISO_CIG_STATE_INACTIVE;
}
#endif /* CONFIG_BT_ISO_CENTRAL */
}
}
if (chan->ops->disconnected) {
chan->ops->disconnected(chan, reason);
}
}
void bt_iso_disconnected(struct bt_conn *iso)
{
struct bt_iso_chan *chan;
if (iso == NULL || iso->type != BT_CONN_TYPE_ISO) {
LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, iso ? iso->type : 0);
return;
}
LOG_DBG("%p", iso);
chan = iso_chan(iso);
if (chan == NULL) {
LOG_ERR("Could not lookup chan from disconnected ISO");
return;
}
bt_iso_chan_disconnected(chan, iso->err);
}
#if defined(CONFIG_BT_ISO_LOG_LEVEL_DBG)
const char *bt_iso_chan_state_str(uint8_t state)
{
switch (state) {
case BT_ISO_STATE_DISCONNECTED:
return "disconnected";
case BT_ISO_STATE_CONNECTING:
return "connecting";
case BT_ISO_STATE_ENCRYPT_PENDING:
return "encryption pending";
case BT_ISO_STATE_CONNECTED:
return "connected";
case BT_ISO_STATE_DISCONNECTING:
return "disconnecting";
default:
return "unknown";
}
}
void bt_iso_chan_set_state_debug(struct bt_iso_chan *chan,
enum bt_iso_state state,
const char *func, int line)
{
LOG_DBG("chan %p iso %p %s -> %s", chan, chan->iso, bt_iso_chan_state_str(chan->state),
bt_iso_chan_state_str(state));
/* check transitions validness */
switch (state) {
case BT_ISO_STATE_DISCONNECTED:
/* regardless of old state always allows this states */
break;
case BT_ISO_STATE_ENCRYPT_PENDING:
__fallthrough;
case BT_ISO_STATE_CONNECTING:
if (chan->state != BT_ISO_STATE_DISCONNECTED) {
LOG_WRN("%s()%d: invalid transition", func, line);
}
break;
case BT_ISO_STATE_CONNECTED:
if (chan->state != BT_ISO_STATE_CONNECTING) {
LOG_WRN("%s()%d: invalid transition", func, line);
}
break;
case BT_ISO_STATE_DISCONNECTING:
if (chan->state != BT_ISO_STATE_CONNECTING &&
chan->state != BT_ISO_STATE_CONNECTED) {
LOG_WRN("%s()%d: invalid transition", func, line);
}
break;
default:
LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state);
return;
}
chan->state = state;
}
#else
void bt_iso_chan_set_state(struct bt_iso_chan *chan, enum bt_iso_state state)
{
chan->state = state;
}
#endif /* CONFIG_BT_ISO_LOG_LEVEL_DBG */
int bt_iso_chan_get_info(const struct bt_iso_chan *chan,
struct bt_iso_info *info)
{
CHECKIF(chan == NULL) {
LOG_DBG("chan is NULL");
return -EINVAL;
}
CHECKIF(chan->iso == NULL) {
LOG_DBG("chan->iso is NULL");
return -EINVAL;
}
CHECKIF(info == NULL) {
LOG_DBG("info is NULL");
return -EINVAL;
}
(void)memcpy(info, &chan->iso->iso.info, sizeof(*info));
return 0;
}
#if defined(CONFIG_BT_ISO_RX)
struct net_buf *bt_iso_get_rx(k_timeout_t timeout)
{
struct net_buf *buf = net_buf_alloc(&iso_rx_pool, timeout);
if (buf) {
net_buf_reserve(buf, BT_BUF_RESERVE);
bt_buf_set_type(buf, BT_BUF_ISO_IN);
}
return buf;
}
void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags)
{
struct bt_hci_iso_data_hdr *hdr;
struct bt_iso_chan *chan;
uint8_t pb, ts;
uint16_t len, pkt_seq_no;
pb = bt_iso_flags_pb(flags);
ts = bt_iso_flags_ts(flags);
BT_ISO_DATA_DBG("handle %u len %u flags 0x%02x pb 0x%02x ts 0x%02x",
iso->handle, buf->len, flags, pb, ts);
/* When the PB_Flag does not equal BT_ISO_START or BT_ISO_SINGLE,
* the fields Time_Stamp, Packet_Sequence_Number, Packet_Status_Flag
* and ISO_SDU_Length are omitted from the HCI ISO Data packet.
*/
switch (pb) {
case BT_ISO_START:
case BT_ISO_SINGLE:
iso_info(buf)->flags = 0;
/* The ISO_Data_Load field contains either the first fragment
* of an SDU or a complete SDU.
*/
if (ts) {
struct bt_hci_iso_ts_data_hdr *ts_hdr;
ts_hdr = net_buf_pull_mem(buf, sizeof(*ts_hdr));
iso_info(buf)->ts = sys_le32_to_cpu(ts_hdr->ts);
hdr = &ts_hdr->data;
iso_info(buf)->flags |= BT_ISO_FLAGS_TS;
} else {
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
/* TODO: Generate a timestamp? */
iso_info(buf)->ts = 0x00000000;
}
len = sys_le16_to_cpu(hdr->slen);
flags = bt_iso_pkt_flags(len);
len = bt_iso_pkt_len(len);
pkt_seq_no = sys_le16_to_cpu(hdr->sn);
iso_info(buf)->seq_num = pkt_seq_no;
if (flags == BT_ISO_DATA_VALID) {
iso_info(buf)->flags |= BT_ISO_FLAGS_VALID;
} else if (flags == BT_ISO_DATA_INVALID) {
iso_info(buf)->flags |= BT_ISO_FLAGS_ERROR;
} else if (flags == BT_ISO_DATA_NOP) {
iso_info(buf)->flags |= BT_ISO_FLAGS_LOST;
} else {
LOG_WRN("Invalid ISO packet status flag: %u", flags);
iso_info(buf)->flags = 0;
}
BT_ISO_DATA_DBG("%s, len %u total %u flags 0x%02x timestamp %u",
pb == BT_ISO_START ? "Start" : "Single",
buf->len, len, flags, iso_info(buf)->ts);
if (iso->rx) {
LOG_ERR("Unexpected ISO %s fragment",
pb == BT_ISO_START ? "Start" : "Single");
bt_conn_reset_rx_state(iso);
}
iso->rx = buf;
iso->rx_len = len - buf->len;
if (iso->rx_len) {
/* if iso->rx_len then package is longer than the
* buf->len and cannot fit in a SINGLE package
*/
if (pb == BT_ISO_SINGLE) {
LOG_ERR("Unexpected ISO single fragment");
bt_conn_reset_rx_state(iso);
}
return;
}
break;
case BT_ISO_CONT:
/* The ISO_Data_Load field contains a continuation fragment of
* an SDU.
*/
if (!iso->rx) {
LOG_ERR("Unexpected ISO continuation fragment");
net_buf_unref(buf);
return;
}
BT_ISO_DATA_DBG("Cont, len %u rx_len %u",
buf->len, iso->rx_len);
if (buf->len > net_buf_tailroom(iso->rx)) {
LOG_ERR("Not enough buffer space for ISO data");
bt_conn_reset_rx_state(iso);
net_buf_unref(buf);
return;
}
net_buf_add_mem(iso->rx, buf->data, buf->len);
iso->rx_len -= buf->len;
net_buf_unref(buf);
return;
case BT_ISO_END:
/* The ISO_Data_Load field contains the last fragment of an
* SDU.
*/
BT_ISO_DATA_DBG("End, len %u rx_len %u", buf->len, iso->rx_len);
if (iso->rx == NULL) {
LOG_ERR("Unexpected ISO end fragment");
net_buf_unref(buf);
return;
}
if (buf->len > net_buf_tailroom(iso->rx)) {
LOG_ERR("Not enough buffer space for ISO data");
bt_conn_reset_rx_state(iso);
net_buf_unref(buf);
return;
}
(void)net_buf_add_mem(iso->rx, buf->data, buf->len);
iso->rx_len -= buf->len;
net_buf_unref(buf);
break;
default:
LOG_ERR("Unexpected ISO pb flags (0x%02x)", pb);
bt_conn_reset_rx_state(iso);
net_buf_unref(buf);
return;
}
chan = iso_chan(iso);
if (chan == NULL) {
LOG_ERR("Could not lookup chan from receiving ISO");
} else if (chan->ops->recv != NULL) {
chan->ops->recv(chan, iso_info(iso->rx), iso->rx);
}
bt_conn_reset_rx_state(iso);
}
#endif /* CONFIG_BT_ISO_RX */
#if defined(CONFIG_BT_ISO_TX)
static uint16_t iso_chan_max_data_len(const struct bt_iso_chan *chan)
{
size_t max_controller_data_len;
uint16_t max_data_len;
if (chan->qos->tx == NULL) {
return 0;
}
max_data_len = chan->qos->tx->sdu;
/* Ensure that the SDU fits when using all the buffers */
max_controller_data_len = bt_dev.le.iso_mtu * bt_dev.le.iso_limit;
/* Update the max_data_len to take the max_controller_data_len into account */
max_data_len = MIN(max_data_len, max_controller_data_len);
return max_data_len;
}
static int validate_send(const struct bt_iso_chan *chan, const struct net_buf *buf,
uint8_t hdr_size)
{
const struct bt_conn *iso_conn;
uint16_t max_data_len;
CHECKIF(!chan || !buf) {
LOG_DBG("Invalid parameters: chan %p buf %p", chan, buf);
return -EINVAL;
}
BT_ISO_DATA_DBG("chan %p len %zu", chan, net_buf_frags_len(buf));
if (chan->state != BT_ISO_STATE_CONNECTED) {
LOG_DBG("Not connected");
return -ENOTCONN;
}
iso_conn = chan->iso;
if (!iso_conn->iso.info.can_send) {
LOG_DBG("Channel not able to send");
return -EINVAL;
}
if (buf->size < hdr_size) {
LOG_DBG("Cannot send ISO packet with buffer size %u", buf->size);
return -EMSGSIZE;
}
max_data_len = iso_chan_max_data_len(chan);
if (buf->len > max_data_len) {
LOG_DBG("Cannot send %u octets, maximum %u", buf->len, max_data_len);
return -EMSGSIZE;
}
return 0;
}
int bt_iso_chan_send(struct bt_iso_chan *chan, struct net_buf *buf, uint16_t seq_num)
{
struct bt_hci_iso_data_hdr *hdr;
struct bt_conn *iso_conn;
int err;
err = validate_send(chan, buf, BT_HCI_ISO_DATA_HDR_SIZE);
if (err != 0) {
return err;
}
BT_ISO_DATA_DBG("chan %p len %zu", chan, net_buf_frags_len(buf));
hdr = net_buf_push(buf, sizeof(*hdr));
hdr->sn = sys_cpu_to_le16(seq_num);
hdr->slen = sys_cpu_to_le16(
bt_iso_pkt_len_pack(net_buf_frags_len(buf) - sizeof(*hdr), BT_ISO_DATA_VALID));
iso_conn = chan->iso;
return bt_conn_send_iso_cb(iso_conn, buf, bt_iso_send_cb, false);
}
int bt_iso_chan_send_ts(struct bt_iso_chan *chan, struct net_buf *buf, uint16_t seq_num,
uint32_t ts)
{
struct bt_hci_iso_ts_data_hdr *hdr;
struct bt_conn *iso_conn;
int err;
err = validate_send(chan, buf, BT_HCI_ISO_TS_DATA_HDR_SIZE);
if (err != 0) {
return err;
}
BT_ISO_DATA_DBG("chan %p len %zu", chan, net_buf_frags_len(buf));
hdr = net_buf_push(buf, sizeof(*hdr));
hdr->ts = ts;
hdr->data.sn = sys_cpu_to_le16(seq_num);
hdr->data.slen = sys_cpu_to_le16(
bt_iso_pkt_len_pack(net_buf_frags_len(buf) - sizeof(*hdr), BT_ISO_DATA_VALID));
iso_conn = chan->iso;
return bt_conn_send_iso_cb(iso_conn, buf, bt_iso_send_cb, true);
}
#if defined(CONFIG_BT_ISO_CENTRAL) || defined(CONFIG_BT_ISO_BROADCASTER)
static bool valid_chan_io_qos(const struct bt_iso_chan_io_qos *io_qos,
bool is_tx, bool is_broadcast, bool advanced)
{
const size_t max_mtu = (is_tx ? CONFIG_BT_ISO_TX_MTU : CONFIG_BT_ISO_RX_MTU);
const size_t max_sdu = MIN(max_mtu, BT_ISO_MAX_SDU);
if (io_qos->sdu > max_sdu) {
LOG_DBG("sdu (%u) shall be smaller or equal to %zu", io_qos->sdu, max_sdu);
return false;
}
if (!IN_RANGE(io_qos->phy, BT_GAP_LE_PHY_1M, BT_GAP_LE_PHY_CODED)) {
LOG_DBG("Invalid PHY %u", io_qos->phy);
return false;
}
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
is_broadcast &&
io_qos->rtn > BT_ISO_BROADCAST_RTN_MAX) {
LOG_DBG("Invalid RTN %u", io_qos->phy);
return false;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
if (advanced) {
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && is_broadcast) {
if (!IN_RANGE(io_qos->max_pdu,
BT_ISO_BROADCAST_PDU_MIN,
BT_ISO_PDU_MAX)) {
LOG_DBG("Invalid broadcast PDU %u", io_qos->max_pdu);
return false;
}
} else if (IS_ENABLED(CONFIG_BT_ISO_CENTRAL)) {
if (!IN_RANGE(io_qos->max_pdu,
BT_ISO_CONNECTED_PDU_MIN,
BT_ISO_PDU_MAX)) {
LOG_DBG("Invalid unicast PDU %u", io_qos->max_pdu);
return false;
}
}
if (!IN_RANGE(io_qos->burst_number,
BT_ISO_BN_MIN,
BT_ISO_BN_MAX)) {
LOG_DBG("Invalid BN %u", io_qos->burst_number);
return false;
}
}
#else
ARG_UNUSED(advanced);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
return true;
}
#endif /* CONFIG_BT_ISO_CENTRAL || CONFIG_BT_ISO_BROADCASTER */
int bt_iso_chan_get_tx_sync(const struct bt_iso_chan *chan, struct bt_iso_tx_info *info)
{
struct bt_hci_cp_le_read_iso_tx_sync *cp;
struct bt_hci_rp_le_read_iso_tx_sync *rp;
struct net_buf *buf;
struct net_buf *rsp = NULL;
int err;
CHECKIF(chan == NULL) {
LOG_DBG("chan is NULL");
return -EINVAL;
}
CHECKIF(chan->iso == NULL) {
LOG_DBG("chan->iso is NULL");
return -EINVAL;
}
CHECKIF(info == NULL) {
LOG_DBG("info is NULL");
return -EINVAL;
}
CHECKIF(chan->state != BT_ISO_STATE_CONNECTED) {
return -ENOTCONN;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_READ_ISO_TX_SYNC, sizeof(*cp));
if (!buf) {
return -ENOMEM;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(chan->iso->handle);
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_ISO_TX_SYNC, buf, &rsp);
if (err) {
return err;
}
if (rsp) {
rp = (struct bt_hci_rp_le_read_iso_tx_sync *)rsp->data;
info->ts = sys_le32_to_cpu(rp->timestamp);
info->seq_num = sys_le16_to_cpu(rp->seq);
info->offset = sys_get_le24(rp->offset);
net_buf_unref(rsp);
} else {
return -ENOTSUP;
}
return 0;
}
#endif /* CONFIG_BT_ISO_TX */
#if defined(CONFIG_BT_ISO_UNICAST)
int bt_iso_chan_disconnect(struct bt_iso_chan *chan)
{
int err;
CHECKIF(!chan) {
LOG_DBG("Invalid parameter: chan %p", chan);
return -EINVAL;
}
CHECKIF(chan->iso == NULL) {
LOG_DBG("Channel has not been initialized in a CIG");
return -EINVAL;
}
if (chan->iso->iso.acl == NULL ||
chan->state == BT_ISO_STATE_DISCONNECTED) {
LOG_DBG("Channel is not connected");
return -ENOTCONN;
}
if (chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
LOG_DBG("Channel already disconnected");
bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTED);
if (chan->ops->disconnected) {
chan->ops->disconnected(chan,
BT_HCI_ERR_LOCALHOST_TERM_CONN);
}
return 0;
}
if (chan->state == BT_ISO_STATE_DISCONNECTING) {
LOG_DBG("Already disconnecting");
return -EALREADY;
}
if (IS_ENABLED(CONFIG_BT_ISO_PERIPHERAL) && chan->iso->role == BT_HCI_ROLE_PERIPHERAL &&
chan->state == BT_ISO_STATE_CONNECTING) {
/* A CIS peripheral is not allowed to disconnect a CIS in the connecting state - It
* has to wait for a CIS Established event
*/
return -EAGAIN;
}
err = bt_conn_disconnect(chan->iso, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err == 0) {
bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTING);
}
return err;
}
void bt_iso_cleanup_acl(struct bt_conn *iso)
{
LOG_DBG("%p", iso);
if (iso->iso.acl) {
bt_conn_unref(iso->iso.acl);
iso->iso.acl = NULL;
}
}
static void store_cis_info(const struct bt_hci_evt_le_cis_established *evt,
struct bt_iso_info *info)
{
struct bt_iso_unicast_info *unicast_info = &info->unicast;
struct bt_iso_unicast_tx_info *central = &unicast_info->central;
struct bt_iso_unicast_tx_info *peripheral = &unicast_info->peripheral;
info->iso_interval = sys_le16_to_cpu(evt->interval);
info->max_subevent = evt->nse;
unicast_info->cig_sync_delay = sys_get_le24(evt->cig_sync_delay);
unicast_info->cis_sync_delay = sys_get_le24(evt->cis_sync_delay);
central->bn = evt->c_bn;
central->phy = bt_get_phy(evt->c_phy);
central->latency = sys_get_le16(evt->c_latency);
central->max_pdu = sys_le16_to_cpu(evt->c_max_pdu);
/* Transform to n * 1.25ms */
central->flush_timeout = info->iso_interval * evt->c_ft;
peripheral->bn = evt->p_bn;
peripheral->phy = bt_get_phy(evt->p_phy);
peripheral->latency = sys_get_le16(evt->p_latency);
peripheral->max_pdu = sys_le16_to_cpu(evt->p_max_pdu);
/* Transform to n * 1.25ms */
peripheral->flush_timeout = info->iso_interval * evt->p_ft;
}
void hci_le_cis_established(struct net_buf *buf)
{
struct bt_hci_evt_le_cis_established *evt = (void *)buf->data;
uint16_t handle = sys_le16_to_cpu(evt->conn_handle);
struct bt_conn *iso;
LOG_DBG("status 0x%02x handle %u", evt->status, handle);
/* ISO connection handles are already assigned at this point */
iso = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ISO);
if (!iso) {
/* If it is a local disconnect, then we may have received the disconnect complete
* event before this event, and in which case we do not expect to find the CIS
* object
*/
if (evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) {
LOG_ERR("No connection found for handle %u", handle);
}
return;
}
if (!evt->status) {
struct bt_iso_chan_io_qos *tx;
struct bt_iso_chan_io_qos *rx;
struct bt_conn_iso *iso_conn;
struct bt_iso_chan *chan;
iso_conn = &iso->iso;
chan = iso_conn->chan;
__ASSERT(chan != NULL && chan->qos != NULL, "Invalid ISO chan");
tx = chan->qos->tx;
rx = chan->qos->rx;
LOG_DBG("iso_chan %p tx %p rx %p", chan, tx, rx);
if (iso->role == BT_HCI_ROLE_PERIPHERAL) {
rx = chan->qos->rx;
tx = chan->qos->tx;
/* As of BT Core 5.4, there is no way for the peripheral to get the actual
* SDU size or SDU interval without the use of higher layer profiles such as
* the Basic Audio Profile (BAP). The best we can do is use the PDU size
* until https://bluetooth.atlassian.net/browse/ES-18552 has been resolved
* and incorporated
*/
if (rx != NULL) {
rx->phy = bt_get_phy(evt->c_phy);
rx->sdu = sys_le16_to_cpu(evt->c_max_pdu);
}
if (tx != NULL) {
tx->phy = bt_get_phy(evt->p_phy);
tx->sdu = sys_le16_to_cpu(evt->p_max_pdu);
}
iso_conn->info.type = BT_ISO_CHAN_TYPE_CONNECTED;
} /* values are already set for central */
/* Verify if device can send */
iso_conn->info.can_send = false;
if (tx != NULL) {
if (iso->role == BT_HCI_ROLE_PERIPHERAL &&
evt->p_bn > 0) {
iso_conn->info.can_send = true;
} else if (iso->role == BT_HCI_ROLE_CENTRAL &&
evt->c_bn > 0) {
iso_conn->info.can_send = true;
}
}
/* Verify if device can recv */
iso_conn->info.can_recv = false;
if (rx != NULL) {
if (iso->role == BT_HCI_ROLE_PERIPHERAL &&
evt->c_bn > 0) {
iso_conn->info.can_recv = true;
} else if (iso->role == BT_HCI_ROLE_CENTRAL &&
evt->p_bn > 0) {
iso_conn->info.can_recv = true;
}
}
store_cis_info(evt, &iso_conn->info);
bt_conn_set_state(iso, BT_CONN_CONNECTED);
bt_conn_unref(iso);
return;
} else if (iso->role == BT_HCI_ROLE_PERIPHERAL ||
evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) {
iso->err = evt->status;
bt_iso_disconnected(iso);
} /* else we wait for disconnect event */
bt_conn_unref(iso);
}
#if defined(CONFIG_BT_ISO_PERIPHERAL)
int bt_iso_server_register(struct bt_iso_server *server)
{
CHECKIF(!server) {
LOG_DBG("Invalid parameter: server %p", server);
return -EINVAL;
}
/* Check if controller is ISO capable */
if (!BT_FEAT_LE_CIS_PERIPHERAL(bt_dev.le.features)) {
return -ENOTSUP;
}
if (iso_server) {
return -EADDRINUSE;
}
if (!server->accept) {
return -EINVAL;
}
#if defined(CONFIG_BT_SMP)
if (server->sec_level > BT_SECURITY_L3) {
return -EINVAL;
} else if (server->sec_level < BT_SECURITY_L1) {
/* Level 0 is only applicable for BR/EDR */
server->sec_level = BT_SECURITY_L1;
}
#endif /* CONFIG_BT_SMP */
LOG_DBG("%p", server);
iso_server = server;
return 0;
}
int bt_iso_server_unregister(struct bt_iso_server *server)
{
CHECKIF(!server) {
LOG_DBG("Invalid parameter: server %p", server);
return -EINVAL;
}
if (iso_server != server) {
return -EINVAL;
}
iso_server = NULL;
return 0;
}
static int iso_accept(struct bt_conn *acl, struct bt_conn *iso)
{
struct bt_iso_accept_info accept_info;
struct bt_iso_chan *chan;
int err;
CHECKIF(!iso || iso->type != BT_CONN_TYPE_ISO) {
LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, iso ? iso->type : 0);
return -EINVAL;
}
LOG_DBG("%p", iso);
accept_info.acl = acl;
accept_info.cig_id = iso->iso.cig_id;
accept_info.cis_id = iso->iso.cis_id;
err = iso_server->accept(&accept_info, &chan);
if (err < 0) {
LOG_ERR("Server failed to accept: %d", err);
return err;
}
#if defined(CONFIG_BT_SMP)
chan->required_sec_level = iso_server->sec_level;
#endif /* CONFIG_BT_SMP */
bt_iso_chan_add(iso, chan);
bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTING);
return 0;
}
static int hci_le_reject_cis(uint16_t handle, uint8_t reason)
{
struct bt_hci_cp_le_reject_cis *cp;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_REJECT_CIS, sizeof(*cp));
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
cp->reason = reason;
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REJECT_CIS, buf, NULL);
if (err) {
return err;
}
return 0;
}
static int hci_le_accept_cis(uint16_t handle)
{
struct bt_hci_cp_le_accept_cis *cp;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_ACCEPT_CIS, sizeof(*cp));
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ACCEPT_CIS, buf, NULL);
if (err) {
return err;
}
return 0;
}
static uint8_t iso_server_check_security(struct bt_conn *conn)
{
if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) {
return BT_HCI_ERR_SUCCESS;
}
#if defined(CONFIG_BT_SMP)
if (conn->sec_level >= iso_server->sec_level) {
return BT_HCI_ERR_SUCCESS;
}
return BT_HCI_ERR_INSUFFICIENT_SECURITY;
#else
return BT_HCI_ERR_SUCCESS;
#endif /* CONFIG_BT_SMP */
}
void hci_le_cis_req(struct net_buf *buf)
{
struct bt_hci_evt_le_cis_req *evt = (void *)buf->data;
uint16_t acl_handle = sys_le16_to_cpu(evt->acl_handle);
uint16_t cis_handle = sys_le16_to_cpu(evt->cis_handle);
struct bt_conn *acl, *iso;
uint8_t sec_err;
int err;
LOG_DBG("acl_handle %u cis_handle %u cig_id %u cis %u", acl_handle, cis_handle, evt->cig_id,
evt->cis_id);
if (iso_server == NULL) {
LOG_DBG("No ISO server registered");
hci_le_reject_cis(cis_handle, BT_HCI_ERR_UNSPECIFIED);
return;
}
/* Lookup existing connection with same handle */
iso = bt_conn_lookup_handle(cis_handle, BT_CONN_TYPE_ISO);
if (iso) {
LOG_ERR("Invalid ISO handle %u", cis_handle);
hci_le_reject_cis(cis_handle, BT_HCI_ERR_CONN_LIMIT_EXCEEDED);
bt_conn_unref(iso);
return;
}
/* Lookup ACL connection to attach */
acl = bt_conn_lookup_handle(acl_handle, BT_CONN_TYPE_LE);
if (!acl) {
LOG_ERR("Invalid ACL handle %u", acl_handle);
hci_le_reject_cis(cis_handle, BT_HCI_ERR_UNKNOWN_CONN_ID);
return;
}
sec_err = iso_server_check_security(acl);
if (sec_err != BT_HCI_ERR_SUCCESS) {
LOG_DBG("Insufficient security %u", sec_err);
err = hci_le_reject_cis(cis_handle, sec_err);
if (err != 0) {
LOG_ERR("Failed to reject CIS");
}
bt_conn_unref(acl);
return;
}
/* Add ISO connection */
iso = bt_conn_add_iso(acl);
bt_conn_unref(acl);
if (!iso) {
LOG_ERR("Could not create and add ISO to ACL %u", acl_handle);
hci_le_reject_cis(cis_handle,
BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
iso->iso.info.type = BT_ISO_CHAN_TYPE_CONNECTED;
iso->iso.cig_id = evt->cig_id;
iso->iso.cis_id = evt->cis_id;
/* Request application to accept */
err = iso_accept(acl, iso);
if (err) {
LOG_DBG("App rejected ISO %d", err);
bt_iso_cleanup_acl(iso);
bt_conn_unref(iso);
hci_le_reject_cis(cis_handle,
BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
iso->handle = cis_handle;
iso->role = BT_HCI_ROLE_PERIPHERAL;
bt_conn_set_state(iso, BT_CONN_CONNECTING);
err = hci_le_accept_cis(cis_handle);
if (err) {
bt_iso_cleanup_acl(iso);
bt_conn_unref(iso);
hci_le_reject_cis(cis_handle,
BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
}
static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl)
{
struct bt_conn *iso = iso_new();
if (iso == NULL) {
LOG_ERR("Unable to allocate ISO connection");
return NULL;
}
iso->iso.acl = bt_conn_ref(acl);
return iso;
}
#endif /* CONFIG_BT_ISO_PERIPHERAL */
#if defined(CONFIG_BT_ISO_CENTRAL)
static int hci_le_remove_iso_data_path(struct bt_conn *iso, uint8_t dir)
{
struct bt_hci_cp_le_remove_iso_path *cp;
struct bt_hci_rp_le_remove_iso_path *rp;
struct net_buf *buf, *rsp;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_REMOVE_ISO_PATH, sizeof(*cp));
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(iso->handle);
cp->path_dir = dir;
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_ISO_PATH, buf, &rsp);
if (err) {
return err;
}
rp = (void *)rsp->data;
if (rp->status || (sys_le16_to_cpu(rp->handle) != iso->handle)) {
err = -EIO;
}
net_buf_unref(rsp);
return err;
}
static void bt_iso_remove_data_path(struct bt_conn *iso)
{
enum bt_iso_chan_type type = iso->iso.info.type;
LOG_DBG("%p", iso);
/* TODO: Removing the ISO data path is never used for broadcast:
* Remove the following broadcast implementation?
*/
if ((IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
type == BT_ISO_CHAN_TYPE_BROADCASTER) ||
(IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) &&
type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER)) {
struct bt_iso_chan *chan;
struct bt_iso_chan_io_qos *tx_qos;
uint8_t dir;
chan = iso_chan(iso);
if (chan == NULL) {
return;
}
tx_qos = chan->qos->tx;
/* Only remove one data path for BIS as per the spec */
if (tx_qos) {
dir = BIT(BT_HCI_DATAPATH_DIR_HOST_TO_CTLR);
} else {
dir = BIT(BT_HCI_DATAPATH_DIR_CTLR_TO_HOST);
}
(void)hci_le_remove_iso_data_path(iso, dir);
} else if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
type == BT_ISO_CHAN_TYPE_CONNECTED) {
/* Remove both directions for CIS*/
/* TODO: Check which has been setup first to avoid removing
* data paths that are not setup
*/
(void)hci_le_remove_iso_data_path(iso, BIT(BT_HCI_DATAPATH_DIR_HOST_TO_CTLR));
(void)hci_le_remove_iso_data_path(iso, BIT(BT_HCI_DATAPATH_DIR_CTLR_TO_HOST));
} else {
__ASSERT(false, "Invalid iso.type: %u", type);
}
}
static bool valid_chan_qos(const struct bt_iso_chan_qos *qos, bool advanced)
{
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
if (advanced &&
!IN_RANGE(qos->num_subevents, BT_ISO_NSE_MIN, BT_ISO_NSE_MAX)) {
LOG_DBG("Invalid NSE: %u", qos->num_subevents);
return false;
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
if (qos->rx != NULL) {
if (!valid_chan_io_qos(qos->rx, false, false, advanced)) {
LOG_DBG("Invalid rx qos");
return false;
}
} else if (qos->tx == NULL) {
LOG_DBG("Both rx and tx qos are NULL");
return false;
}
if (qos->tx != NULL) {
if (!valid_chan_io_qos(qos->tx, true, false, advanced)) {
LOG_DBG("Invalid tx qos");
return false;
}
}
return true;
}
static int hci_le_remove_cig(uint8_t cig_id)
{
struct bt_hci_cp_le_remove_cig *req;
struct net_buf *buf;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_REMOVE_CIG, sizeof(*req));
if (!buf) {
return -ENOBUFS;
}
req = net_buf_add(buf, sizeof(*req));
memset(req, 0, sizeof(*req));
req->cig_id = cig_id;
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_CIG, buf, NULL);
}
static struct net_buf *hci_le_set_cig_params(const struct bt_iso_cig *cig,
const struct bt_iso_cig_param *param)
{
struct bt_hci_cp_le_set_cig_params *req;
struct bt_hci_cis_params *cis_param;
struct net_buf *buf;
struct net_buf *rsp;
int i, err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CIG_PARAMS,
sizeof(*req) + sizeof(*cis_param) * param->num_cis);
if (!buf) {
return NULL;
}
req = net_buf_add(buf, sizeof(*req));
memset(req, 0, sizeof(*req));
req->cig_id = cig->id;
req->c_latency = sys_cpu_to_le16(param->c_to_p_latency);
req->p_latency = sys_cpu_to_le16(param->p_to_c_latency);
sys_put_le24(param->c_to_p_interval, req->c_interval);
sys_put_le24(param->p_to_c_interval, req->p_interval);
req->sca = param->sca;
req->packing = param->packing;
req->framing = param->framing;
req->num_cis = param->num_cis;
LOG_DBG("id %u, latency C to P %u, latency P to C %u, "
"interval C to P %u, interval P to C %u, "
"sca %u, packing %u, framing %u, num_cis %u",
cig->id, param->c_to_p_latency, param->p_to_c_latency,
param->c_to_p_interval, param->p_to_c_interval,
param->sca, param->packing,
param->framing, param->num_cis);
/* Program the cis parameters */
for (i = 0; i < param->num_cis; i++) {
struct bt_iso_chan *cis = param->cis_channels[i];
struct bt_iso_chan_qos *qos = cis->qos;
cis_param = net_buf_add(buf, sizeof(*cis_param));
memset(cis_param, 0, sizeof(*cis_param));
cis_param->cis_id = cis->iso->iso.cis_id;
if (!qos->tx && !qos->rx) {
LOG_ERR("Both TX and RX QoS are disabled");
net_buf_unref(buf);
return NULL;
}
if (!qos->tx) {
/* Use RX PHY if TX is not set (disabled)
* to avoid setting invalid values
*/
cis_param->c_phy = qos->rx->phy;
} else {
cis_param->c_sdu = sys_cpu_to_le16(qos->tx->sdu);
cis_param->c_phy = qos->tx->phy;
cis_param->c_rtn = qos->tx->rtn;
}
if (!qos->rx) {
/* Use TX PHY if RX is not set (disabled)
* to avoid setting invalid values
*/
cis_param->p_phy = qos->tx->phy;
} else {
cis_param->p_sdu = sys_cpu_to_le16(qos->rx->sdu);
cis_param->p_phy = qos->rx->phy;
cis_param->p_rtn = qos->rx->rtn;
}
LOG_DBG("[%d]: id %u, c_phy %u, c_sdu %u, c_rtn %u, p_phy %u, p_sdu %u, p_rtn %u",
i, cis_param->cis_id, cis_param->c_phy, cis_param->c_sdu, cis_param->c_rtn,
cis_param->p_phy, cis_param->p_sdu, cis_param->p_rtn);
}
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CIG_PARAMS, buf, &rsp);
if (err) {
return NULL;
}
return rsp;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
static struct net_buf *hci_le_set_cig_test_params(const struct bt_iso_cig *cig,
const struct bt_iso_cig_param *param)
{
struct bt_hci_cp_le_set_cig_params_test *req;
struct bt_hci_cis_params_test *cis_param;
struct net_buf *buf;
struct net_buf *rsp;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CIG_PARAMS_TEST,
sizeof(*req) + sizeof(*cis_param) * param->num_cis);
if (!buf) {
return NULL;
}
req = net_buf_add(buf, sizeof(*req));
memset(req, 0, sizeof(*req));
req->cig_id = cig->id;
sys_put_le24(param->c_to_p_interval, req->c_interval);
sys_put_le24(param->p_to_c_interval, req->p_interval);
req->c_ft = param->c_to_p_ft;
req->p_ft = param->p_to_c_ft;
req->iso_interval = sys_cpu_to_le16(param->iso_interval);
req->sca = param->sca;
req->packing = param->packing;
req->framing = param->framing;
req->num_cis = param->num_cis;
LOG_DBG("id %u, SDU interval C to P %u, SDU interval P to C %u, c_ft %u, p_ft %u, "
"iso_interval %u, sca %u, packing %u, framing %u, num_cis %u",
cig->id, param->c_to_p_interval, param->p_to_c_interval, param->c_to_p_ft,
param->p_to_c_ft, param->iso_interval, param->sca, param->packing,
param->framing, param->num_cis);
/* Program the cis parameters */
for (uint8_t i = 0U; i < param->num_cis; i++) {
const struct bt_iso_chan *cis = param->cis_channels[i];
const struct bt_iso_chan_qos *qos = cis->qos;
cis_param = net_buf_add(buf, sizeof(*cis_param));
memset(cis_param, 0, sizeof(*cis_param));
cis_param->cis_id = cis->iso->iso.cis_id;
cis_param->nse = qos->num_subevents;
if (!qos->tx && !qos->rx) {
LOG_ERR("Both TX and RX QoS are disabled");
net_buf_unref(buf);
return NULL;
}
if (!qos->tx) {
/* Use RX PHY if TX is not set (disabled)
* to avoid setting invalid values
*/
cis_param->c_phy = qos->rx->phy;
} else {
cis_param->c_sdu = sys_cpu_to_le16(qos->tx->sdu);
cis_param->c_pdu = sys_cpu_to_le16(qos->tx->max_pdu);
cis_param->c_phy = qos->tx->phy;
cis_param->c_bn = qos->tx->burst_number;
}
if (!qos->rx) {
/* Use TX PHY if RX is not set (disabled)
* to avoid setting invalid values
*/
cis_param->p_phy = qos->tx->phy;
} else {
cis_param->p_sdu = sys_cpu_to_le16(qos->rx->sdu);
cis_param->p_pdu = sys_cpu_to_le16(qos->rx->max_pdu);
cis_param->p_phy = qos->rx->phy;
cis_param->p_bn = qos->rx->burst_number;
}
LOG_DBG("[%d]: id %u, nse %u c_sdu %u, p_sdu %u, c_pdu %u, "
"p_pdu %u, c_phy %u, p_phy %u, c_bn %u, p_bn %u",
i, cis_param->cis_id, cis_param->nse, cis_param->c_sdu,
cis_param->p_sdu, cis_param->c_pdu, cis_param->p_pdu,
cis_param->c_phy, cis_param->p_phy, cis_param->c_bn,
cis_param->p_bn);
}
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CIG_PARAMS_TEST, buf, &rsp);
if (err) {
return NULL;
}
return rsp;
}
static bool is_advanced_cig_param(const struct bt_iso_cig_param *param)
{
if (param->c_to_p_ft != 0U ||
param->p_to_c_ft != 0U ||
param->iso_interval != 0U) {
return true;
}
/* Check if any of the CIS contain any test-param-only values */
for (uint8_t i = 0U; i < param->num_cis; i++) {
const struct bt_iso_chan *cis = param->cis_channels[i];
const struct bt_iso_chan_qos *qos = cis->qos;
if (qos->num_subevents > 0U) {
return true;
}
if (qos->tx != NULL) {
if (qos->tx->max_pdu > 0U ||
qos->tx->burst_number > 0U) {
return true;
}
}
if (qos->rx != NULL) {
if (qos->rx->max_pdu > 0U ||
qos->rx->burst_number > 0U) {
return true;
}
}
}
return false;
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan)
{
if (iso_chan == NULL || iso_chan->iso == NULL) {
return NULL;
}
__ASSERT(iso_chan->iso->iso.cig_id < ARRAY_SIZE(cigs),
"Invalid cig_id %u", iso_chan->iso->iso.cig_id);
return &cigs[iso_chan->iso->iso.cig_id];
}
static struct bt_iso_cig *get_free_cig(void)
{
/* We can use the index in the `cigs` array as CIG ID */
for (size_t i = 0; i < ARRAY_SIZE(cigs); i++) {
if (cigs[i].state == BT_ISO_CIG_STATE_IDLE) {
cigs[i].state = BT_ISO_CIG_STATE_CONFIGURED;
cigs[i].id = i;
sys_slist_init(&cigs[i].cis_channels);
return &cigs[i];
}
}
LOG_DBG("Could not allocate any more CIGs");
return NULL;
}
static bool cis_is_in_cig(const struct bt_iso_cig *cig,
const struct bt_iso_chan *cis)
{
if (cig == NULL || cis == NULL || cis->iso == NULL) {
return false;
}
return cig->id == cis->iso->iso.cig_id;
}
static int cig_init_cis(struct bt_iso_cig *cig,
const struct bt_iso_cig_param *param)
{
for (uint8_t i = 0; i < param->num_cis; i++) {
struct bt_iso_chan *cis = param->cis_channels[i];
if (cis->iso == NULL) {
struct bt_conn_iso *iso_conn;
cis->iso = iso_new();
if (cis->iso == NULL) {
LOG_ERR("Unable to allocate CIS connection");
return -ENOMEM;
}
iso_conn = &cis->iso->iso;
iso_conn->cig_id = cig->id;
iso_conn->info.type = BT_ISO_CHAN_TYPE_CONNECTED;
iso_conn->cis_id = cig->num_cis++;
bt_iso_chan_add(cis->iso, cis);
sys_slist_append(&cig->cis_channels, &cis->node);
} /* else already initialized */
}
return 0;
}
static void cleanup_cig(struct bt_iso_cig *cig)
{
struct bt_iso_chan *cis, *tmp;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&cig->cis_channels, cis, tmp, node) {
if (cis->iso != NULL) {
bt_conn_unref(cis->iso);
cis->iso = NULL;
}
sys_slist_remove(&cig->cis_channels, NULL, &cis->node);
}
memset(cig, 0, sizeof(*cig));
}
static bool valid_cig_param(const struct bt_iso_cig_param *param, bool advanced,
const struct bt_iso_cig *cig)
{
if (param == NULL) {
return false;
}
for (uint8_t i = 0; i < param->num_cis; i++) {
struct bt_iso_chan *cis = param->cis_channels[i];
if (cis == NULL) {
LOG_DBG("cis_channels[%d]: NULL channel", i);
return false;
}
if (cis->iso != NULL && !cis_is_in_cig(cig, cis)) {
LOG_DBG("cis_channels[%d]: already allocated to CIG %p", i, get_cig(cis));
return false;
}
if (!valid_chan_qos(cis->qos, advanced)) {
LOG_DBG("cis_channels[%d]: Invalid QOS", i);
return false;
}
for (uint8_t j = 0; j < i; j++) {
if (cis == param->cis_channels[j]) {
LOG_DBG("ISO %p duplicated at index %u and %u", cis, i, j);
return false;
}
}
}
if (param->framing != BT_ISO_FRAMING_UNFRAMED &&
param->framing != BT_ISO_FRAMING_FRAMED) {
LOG_DBG("Invalid framing parameter: %u", param->framing);
return false;
}
if (param->packing != BT_ISO_PACKING_SEQUENTIAL &&
param->packing != BT_ISO_PACKING_INTERLEAVED) {
LOG_DBG("Invalid packing parameter: %u", param->packing);
return false;
}
if (param->num_cis > BT_ISO_MAX_GROUP_ISO_COUNT ||
param->num_cis > CONFIG_BT_ISO_MAX_CHAN) {
LOG_DBG("num_cis (%u) shall be lower than: %u", param->num_cis,
MAX(CONFIG_BT_ISO_MAX_CHAN, BT_ISO_MAX_GROUP_ISO_COUNT));
return false;
}
if (!IN_RANGE(param->c_to_p_interval, BT_ISO_SDU_INTERVAL_MIN, BT_ISO_SDU_INTERVAL_MAX)) {
LOG_DBG("Invalid C to P interval: %u", param->c_to_p_interval);
return false;
}
if (!IN_RANGE(param->p_to_c_interval, BT_ISO_SDU_INTERVAL_MIN, BT_ISO_SDU_INTERVAL_MAX)) {
LOG_DBG("Invalid P to C interval: %u", param->p_to_c_interval);
return false;
}
if (!advanced &&
!IN_RANGE(param->c_to_p_latency, BT_ISO_LATENCY_MIN, BT_ISO_LATENCY_MAX)) {
LOG_DBG("Invalid C to P latency: %u", param->c_to_p_latency);
return false;
}
if (!advanced &&
!IN_RANGE(param->p_to_c_latency, BT_ISO_LATENCY_MIN, BT_ISO_LATENCY_MAX)) {
LOG_DBG("Invalid P to C latency: %u", param->p_to_c_latency);
return false;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
if (advanced) {
if (!IN_RANGE(param->c_to_p_ft, BT_ISO_FT_MIN, BT_ISO_FT_MAX)) {
LOG_DBG("Invalid Central to Peripheral FT %u",
param->c_to_p_ft);
return false;
}
if (!IN_RANGE(param->p_to_c_ft, BT_ISO_FT_MIN, BT_ISO_FT_MAX)) {
LOG_DBG("Invalid Peripheral to Central FT %u",
param->p_to_c_ft);
return false;
}
if (!IN_RANGE(param->iso_interval,
BT_ISO_ISO_INTERVAL_MIN,
BT_ISO_ISO_INTERVAL_MAX)) {
LOG_DBG("Invalid ISO interval %u", param->iso_interval);
return false;
}
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
return true;
}
int bt_iso_cig_create(const struct bt_iso_cig_param *param,
struct bt_iso_cig **out_cig)
{
int err;
struct net_buf *rsp;
struct bt_iso_cig *cig;
struct bt_hci_rp_le_set_cig_params *cig_rsp;
struct bt_iso_chan *cis;
bool advanced = false;
int i;
CHECKIF(out_cig == NULL) {
LOG_DBG("out_cig is NULL");
return -EINVAL;
}
*out_cig = NULL;
/* Check if controller is ISO capable as a central */
if (!BT_FEAT_LE_CIS_CENTRAL(bt_dev.le.features)) {
return -ENOTSUP;
}
/* TBD: Should we allow creating empty CIGs? */
CHECKIF(param->cis_channels == NULL) {
LOG_DBG("NULL CIS channels");
return -EINVAL;
}
CHECKIF(param->num_cis == 0) {
LOG_DBG("Invalid number of CIS %u", param->num_cis);
return -EINVAL;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
advanced = is_advanced_cig_param(param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
CHECKIF(!valid_cig_param(param, advanced, NULL)) {
LOG_DBG("Invalid CIG params");
return -EINVAL;
}
cig = get_free_cig();
if (!cig) {
return -ENOMEM;
}
err = cig_init_cis(cig, param);
if (err) {
LOG_DBG("Could not init CIS %d", err);
cleanup_cig(cig);
return err;
}
if (!advanced) {
rsp = hci_le_set_cig_params(cig, param);
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
} else {
rsp = hci_le_set_cig_test_params(cig, param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
}
if (rsp == NULL) {
LOG_WRN("Unexpected response to hci_le_set_cig_params");
err = -EIO;
cleanup_cig(cig);
return err;
}
cig_rsp = (void *)rsp->data;
if (rsp->len < sizeof(cig_rsp) ||
cig_rsp->num_handles != param->num_cis) {
LOG_WRN("Unexpected response to hci_le_set_cig_params");
err = -EIO;
net_buf_unref(rsp);
cleanup_cig(cig);
return err;
}
i = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&cig->cis_channels, cis, node) {
const uint16_t handle = cig_rsp->handle[i++];
/* Assign the connection handle */
cis->iso->handle = sys_le16_to_cpu(handle);
}
net_buf_unref(rsp);
*out_cig = cig;
return err;
}
static void restore_cig(struct bt_iso_cig *cig, uint8_t existing_num_cis)
{
struct bt_iso_chan *cis, *tmp;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&cig->cis_channels, cis, tmp, node) {
/* Remove all newly added by comparing the cis_id to the number
* of CIS that was previously added before
* bt_iso_cig_reconfigure was called
*/
if (cis->iso != NULL &&
cis->iso->iso.cis_id >= existing_num_cis) {
bt_conn_unref(cis->iso);
cis->iso = NULL;
sys_slist_remove(&cig->cis_channels, NULL, &cis->node);
cig->num_cis--;
}
}
}
int bt_iso_cig_reconfigure(struct bt_iso_cig *cig,
const struct bt_iso_cig_param *param)
{
struct bt_hci_rp_le_set_cig_params *cig_rsp;
uint8_t existing_num_cis;
bool advanced = false;
struct net_buf *rsp;
int err;
CHECKIF(cig == NULL) {
LOG_DBG("cig is NULL");
return -EINVAL;
}
if (cig->state != BT_ISO_CIG_STATE_CONFIGURED) {
LOG_DBG("Invalid CIG state: %u", cig->state);
return -EINVAL;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
advanced = is_advanced_cig_param(param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
CHECKIF(!valid_cig_param(param, advanced, cig)) {
LOG_DBG("Invalid CIG params");
return -EINVAL;
}
/* Used to restore CIG in case of error */
existing_num_cis = cig->num_cis;
err = cig_init_cis(cig, param);
if (err != 0) {
LOG_DBG("Could not init CIS %d", err);
restore_cig(cig, existing_num_cis);
return err;
}
if (!advanced) {
rsp = hci_le_set_cig_params(cig, param);
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
} else {
rsp = hci_le_set_cig_test_params(cig, param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
}
if (rsp == NULL) {
LOG_WRN("Unexpected response to hci_le_set_cig_params");
err = -EIO;
restore_cig(cig, existing_num_cis);
return err;
}
cig_rsp = (void *)rsp->data;
if (rsp->len < sizeof(*cig_rsp)) {
LOG_WRN("Unexpected response len to hci_le_set_cig_params %u != %zu", rsp->len,
sizeof(*cig_rsp));
err = -EIO;
net_buf_unref(rsp);
restore_cig(cig, existing_num_cis);
return err;
}
if (cig_rsp->num_handles != param->num_cis) {
LOG_WRN("Unexpected response num_handles to hci_le_set_cig_params %u != %u",
cig_rsp->num_handles, param->num_cis);
err = -EIO;
net_buf_unref(rsp);
restore_cig(cig, existing_num_cis);
return err;
}
for (uint8_t i = 0u; i < param->num_cis; i++) {
const uint16_t handle = cig_rsp->handle[i];
struct bt_iso_chan *cis = param->cis_channels[i];
/* Assign the connection handle */
cis->iso->handle = sys_le16_to_cpu(handle);
}
net_buf_unref(rsp);
return err;
}
int bt_iso_cig_terminate(struct bt_iso_cig *cig)
{
int err;
CHECKIF(cig == NULL) {
LOG_DBG("cig is NULL");
return -EINVAL;
}
if (cig->state != BT_ISO_CIG_STATE_INACTIVE &&
cig->state != BT_ISO_CIG_STATE_CONFIGURED) {
LOG_DBG("Invalid CIG state: %u", cig->state);
return -EINVAL;
}
err = hci_le_remove_cig(cig->id);
if (err != 0) {
LOG_DBG("Failed to terminate CIG: %d", err);
return err;
}
cleanup_cig(cig);
return 0;
}
void bt_iso_security_changed(struct bt_conn *acl, uint8_t hci_status)
{
struct bt_iso_connect_param param[CONFIG_BT_ISO_MAX_CHAN];
size_t param_count;
int err;
/* The peripheral does not accept any ISO requests if security is
* insufficient, so we only need to handle central here.
* BT_ISO_STATE_ENCRYPT_PENDING is only set by the central.
*/
if (!IS_ENABLED(CONFIG_BT_CENTRAL) ||
acl->role != BT_CONN_ROLE_CENTRAL) {
return;
}
param_count = 0;
for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
struct bt_conn *iso = &iso_conns[i];
struct bt_iso_chan *iso_chan;
if (iso == NULL || iso->iso.acl != acl) {
continue;
}
iso_chan = iso_chan(iso);
if (iso_chan->state != BT_ISO_STATE_ENCRYPT_PENDING) {
continue;
}
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_DISCONNECTED);
if (hci_status == BT_HCI_ERR_SUCCESS) {
param[param_count].acl = acl;
param[param_count].iso_chan = iso_chan;
param_count++;
} else {
LOG_DBG("Failed to encrypt ACL %p for ISO %p: %u", acl, iso, hci_status);
/* We utilize the disconnected callback to make the
* upper layers aware of the error
*/
if (iso_chan->ops->disconnected) {
iso_chan->ops->disconnected(iso_chan,
hci_status);
}
}
}
if (param_count == 0) {
/* Nothing to do for ISO. This happens if security is changed,
* but no ISO channels were pending encryption.
*/
return;
}
err = hci_le_create_cis(param, param_count);
if (err != 0) {
LOG_ERR("Failed to connect CISes: %d", err);
for (size_t i = 0; i < param_count; i++) {
struct bt_iso_chan *iso_chan = param[i].iso_chan;
/* We utilize the disconnected callback to make the
* upper layers aware of the error
*/
if (iso_chan->ops->disconnected) {
iso_chan->ops->disconnected(iso_chan,
hci_status);
}
}
return;
}
/* Set connection states */
for (size_t i = 0; i < param_count; i++) {
struct bt_iso_chan *iso_chan = param[i].iso_chan;
struct bt_iso_cig *cig = get_cig(iso_chan);
__ASSERT(cig != NULL, "CIG was NULL");
cig->state = BT_ISO_CIG_STATE_ACTIVE;
bt_conn_set_state(iso_chan->iso, BT_CONN_CONNECTING);
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
}
}
static int hci_le_create_cis(const struct bt_iso_connect_param *param,
size_t count)
{
struct bt_hci_cis *cis;
struct bt_hci_cp_le_create_cis *req;
struct net_buf *buf;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_CIS,
sizeof(*req) + sizeof(*cis) * count);
if (!buf) {
return -ENOBUFS;
}
req = net_buf_add(buf, sizeof(*req));
memset(req, 0, sizeof(*req));
/* Program the cis parameters */
for (size_t i = 0; i < count; i++) {
struct bt_iso_chan *iso_chan = param[i].iso_chan;
if (iso_chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
continue;
}
cis = net_buf_add(buf, sizeof(*cis));
memset(cis, 0, sizeof(*cis));
cis->cis_handle = sys_cpu_to_le16(param[i].iso_chan->iso->handle);
cis->acl_handle = sys_cpu_to_le16(param[i].acl->handle);
req->num_cis++;
}
/* If all CIS are pending for security, do nothing,
* but return a recognizable return value
*/
if (req->num_cis == 0) {
net_buf_unref(buf);
return -ECANCELED;
}
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CIS, buf, NULL);
}
#if defined(CONFIG_BT_SMP)
static int iso_chan_connect_security(const struct bt_iso_connect_param *param,
size_t count)
{
/* conn_idx_handled is an array of booleans for which conn indexes
* already have been used to call bt_conn_set_security.
* Using indexes avoids looping the array when checking if it has been
* handled.
*/
bool conn_idx_handled[CONFIG_BT_MAX_CONN];
memset(conn_idx_handled, false, sizeof(conn_idx_handled));
for (size_t i = 0; i < count; i++) {
struct bt_iso_chan *iso_chan = param[i].iso_chan;
struct bt_conn *acl = param[i].acl;
uint8_t conn_idx = bt_conn_index(acl);
if (acl->sec_level < iso_chan->required_sec_level) {
if (!conn_idx_handled[conn_idx]) {
int err;
err = bt_conn_set_security(acl,
iso_chan->required_sec_level);
if (err != 0) {
LOG_DBG("[%zu]: Failed to set security: %d", i, err);
/* Restore states */
for (size_t j = 0; j < i; j++) {
iso_chan = param[j].iso_chan;
bt_iso_cleanup_acl(iso_chan->iso);
bt_iso_chan_set_state(iso_chan,
BT_ISO_STATE_DISCONNECTED);
}
return err;
}
conn_idx_handled[conn_idx] = true;
}
iso_chan->iso->iso.acl = bt_conn_ref(acl);
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_ENCRYPT_PENDING);
}
}
return 0;
}
#endif /* CONFIG_BT_SMP */
static bool iso_chans_connecting(void)
{
for (size_t i = 0U; i < ARRAY_SIZE(iso_conns); i++) {
const struct bt_conn *iso = &iso_conns[i];
const struct bt_iso_chan *iso_chan;
if (iso == NULL ||
iso->iso.info.type != BT_ISO_CHAN_TYPE_CONNECTED) {
continue;
}
iso_chan = iso_chan(iso);
if (iso_chan->state == BT_ISO_STATE_CONNECTING ||
iso_chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
return true;
}
}
return false;
}
int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count)
{
int err;
CHECKIF(param == NULL) {
LOG_DBG("param is NULL");
return -EINVAL;
}
CHECKIF(count == 0) {
LOG_DBG("Invalid count %zu", count);
return -EINVAL;
}
CHECKIF(count > CONFIG_BT_ISO_MAX_CHAN) {
return -EINVAL;
}
/* Validate input */
for (size_t i = 0; i < count; i++) {
CHECKIF(param[i].iso_chan == NULL) {
LOG_DBG("[%zu]: Invalid iso (%p)", i, param[i].iso_chan);
return -EINVAL;
}
CHECKIF(param[i].acl == NULL) {
LOG_DBG("[%zu]: Invalid acl (%p)", i, param[i].acl);
return -EINVAL;
}
CHECKIF((param[i].acl->type & BT_CONN_TYPE_LE) == 0) {
LOG_DBG("[%zu]: acl type (%u) shall be an LE connection", i,
param[i].acl->type);
return -EINVAL;
}
if (param[i].iso_chan->iso == NULL) {
LOG_DBG("[%zu]: ISO has not been initialized in a CIG", i);
return -EINVAL;
}
if (param[i].iso_chan->state != BT_ISO_STATE_DISCONNECTED) {
LOG_DBG("[%zu]: ISO is not in the BT_ISO_STATE_DISCONNECTED state: %u", i,
param[i].iso_chan->state);
return -EINVAL;
}
}
if (iso_chans_connecting()) {
LOG_DBG("There are pending ISO connections");
return -EBUSY;
}
#if defined(CONFIG_BT_SMP)
/* Check for and initiate security for all channels that have
* requested encryption if the ACL link is not already secured
*/
err = iso_chan_connect_security(param, count);
if (err != 0) {
LOG_DBG("Failed to initate security for all CIS: %d", err);
return err;
}
#endif /* CONFIG_BT_SMP */
err = hci_le_create_cis(param, count);
if (err == -ECANCELED) {
LOG_DBG("All channels are pending on security");
return 0;
} else if (err != 0) {
LOG_DBG("Failed to connect CISes: %d", err);
return err;
}
/* Set connection states */
for (size_t i = 0; i < count; i++) {
struct bt_iso_chan *iso_chan = param[i].iso_chan;
struct bt_iso_cig *cig;
if (iso_chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
continue;
}
iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl);
bt_conn_set_state(iso_chan->iso, BT_CONN_CONNECTING);
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
cig = get_cig(iso_chan);
__ASSERT(cig != NULL, "CIG was NULL");
cig->state = BT_ISO_CIG_STATE_ACTIVE;
}
return 0;
}
#endif /* CONFIG_BT_ISO_CENTRAL */
#endif /* CONFIG_BT_ISO_UNICAST */
#if defined(CONFIG_BT_ISO_BROADCAST)
static struct bt_iso_big *lookup_big_by_handle(uint8_t big_handle)
{
return &bigs[big_handle];
}
static struct bt_iso_big *get_free_big(void)
{
/* We can use the index in the `bigs` array as BIG handles, for both
* broadcaster and receiver (even if the device is both!)
*/
for (size_t i = 0; i < ARRAY_SIZE(bigs); i++) {
if (!atomic_test_and_set_bit(bigs[i].flags, BT_BIG_INITIALIZED)) {
bigs[i].handle = i;
sys_slist_init(&bigs[i].bis_channels);
return &bigs[i];
}
}
LOG_DBG("Could not allocate any more BIGs");
return NULL;
}
static struct bt_iso_big *big_lookup_flag(int bit)
{
for (size_t i = 0; i < ARRAY_SIZE(bigs); i++) {
if (atomic_test_bit(bigs[i].flags, bit)) {
return &bigs[i];
}
}
LOG_DBG("No BIG with flag bit %d set", bit);
return NULL;
}
static void cleanup_big(struct bt_iso_big *big)
{
struct bt_iso_chan *bis, *tmp;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&big->bis_channels, bis, tmp, node) {
if (bis->iso != NULL) {
bt_conn_unref(bis->iso);
bis->iso = NULL;
}
sys_slist_remove(&big->bis_channels, NULL, &bis->node);
}
memset(big, 0, sizeof(*big));
}
static void big_disconnect(struct bt_iso_big *big, uint8_t reason)
{
struct bt_iso_chan *bis;
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
bis->iso->err = reason;
bt_iso_disconnected(bis->iso);
}
}
static int big_init_bis(struct bt_iso_big *big,
struct bt_iso_chan **bis_channels,
uint8_t num_bis,
bool broadcaster)
{
for (uint8_t i = 0; i < num_bis; i++) {
struct bt_iso_chan *bis = bis_channels[i];
struct bt_conn_iso *iso_conn;
bis->iso = iso_new();
if (!bis->iso) {
LOG_ERR("Unable to allocate BIS connection");
return -ENOMEM;
}
iso_conn = &bis->iso->iso;
iso_conn->big_handle = big->handle;
iso_conn->info.type = broadcaster ? BT_ISO_CHAN_TYPE_BROADCASTER
: BT_ISO_CHAN_TYPE_SYNC_RECEIVER;
iso_conn->bis_id = bt_conn_index(bis->iso);
bt_iso_chan_add(bis->iso, bis);
sys_slist_append(&big->bis_channels, &bis->node);
}
return 0;
}
#if defined(CONFIG_BT_ISO_BROADCASTER)
static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big,
struct bt_iso_big_create_param *param)
{
struct bt_hci_cp_le_create_big *req;
struct bt_hci_cmd_state_set state;
struct net_buf *buf;
int err;
static struct bt_iso_chan_qos *qos;
struct bt_iso_chan *bis;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_BIG, sizeof(*req));
if (!buf) {
return -ENOBUFS;
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
__ASSERT(bis != NULL, "bis was NULL");
/* All BIS will share the same QOS */
qos = bis->qos;
req = net_buf_add(buf, sizeof(*req));
req->big_handle = big->handle;
req->adv_handle = padv->handle;
req->num_bis = big->num_bis;
sys_put_le24(param->interval, req->sdu_interval);
req->max_sdu = sys_cpu_to_le16(qos->tx->sdu);
req->max_latency = sys_cpu_to_le16(param->latency);
req->rtn = qos->tx->rtn;
req->phy = qos->tx->phy;
req->packing = param->packing;
req->framing = param->framing;
req->encryption = param->encryption;
if (req->encryption) {
memcpy(req->bcode, param->bcode, sizeof(req->bcode));
} else {
memset(req->bcode, 0, sizeof(req->bcode));
}
bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_PENDING, true);
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_BIG, buf, NULL);
if (err) {
return err;
}
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING);
}
return err;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
static int hci_le_create_big_test(const struct bt_le_ext_adv *padv,
struct bt_iso_big *big,
const struct bt_iso_big_create_param *param)
{
struct bt_hci_cp_le_create_big_test *req;
struct bt_hci_cmd_state_set state;
const struct bt_iso_chan_qos *qos;
struct bt_iso_chan *bis;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_BIG_TEST, sizeof(*req));
if (!buf) {
return -ENOBUFS;
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
__ASSERT(bis != NULL, "bis was NULL");
/* All BIS will share the same QOS */
qos = bis->qos;
req = net_buf_add(buf, sizeof(*req));
req->big_handle = big->handle;
req->adv_handle = padv->handle;
req->num_bis = big->num_bis;
sys_put_le24(param->interval, req->sdu_interval);
req->iso_interval = sys_cpu_to_le16(param->iso_interval);
req->nse = qos->num_subevents;
req->max_sdu = sys_cpu_to_le16(qos->tx->sdu);
req->max_pdu = sys_cpu_to_le16(qos->tx->max_pdu);
req->phy = qos->tx->phy;
req->packing = param->packing;
req->framing = param->framing;
req->bn = qos->tx->burst_number;
req->irc = param->irc;
req->pto = param->pto;
req->encryption = param->encryption;
if (req->encryption) {
memcpy(req->bcode, param->bcode, sizeof(req->bcode));
} else {
memset(req->bcode, 0, sizeof(req->bcode));
}
LOG_DBG("BIG handle %u, adv handle %u, num_bis %u, SDU interval %u, "
"ISO interval %u, NSE %u, SDU %u, PDU %u, PHY %u, packing %u, "
"framing %u, BN %u, IRC %u, PTO %u, encryption %u",
req->big_handle, req->adv_handle, req->num_bis, param->interval,
param->iso_interval, req->nse, req->max_sdu, req->max_pdu,
req->phy, req->packing, req->framing, req->bn, req->irc,
req->pto, req->encryption);
bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_PENDING, true);
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_BIG_TEST, buf, NULL);
if (err) {
return err;
}
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING);
}
return err;
}
static bool is_advanced_big_param(const struct bt_iso_big_create_param *param)
{
if (param->irc != 0U ||
param->iso_interval != 0U) {
return true;
}
/* Check if any of the CIS contain any test-param-only values */
for (uint8_t i = 0U; i < param->num_bis; i++) {
const struct bt_iso_chan *bis = param->bis_channels[i];
const struct bt_iso_chan_qos *qos = bis->qos;
if (qos->num_subevents > 0U) {
return true;
}
__ASSERT(qos->tx != NULL, "TX cannot be NULL for broadcaster");
if (qos->tx->max_pdu > 0U || qos->tx->burst_number > 0U) {
return true;
}
}
return false;
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
static bool valid_big_param(const struct bt_iso_big_create_param *param,
bool advanced)
{
CHECKIF(!param->bis_channels) {
LOG_DBG("NULL BIS channels");
return false;
}
CHECKIF(!param->num_bis) {
LOG_DBG("Invalid number of BIS %u", param->num_bis);
return false;
}
for (uint8_t i = 0; i < param->num_bis; i++) {
struct bt_iso_chan *bis = param->bis_channels[i];
CHECKIF(bis == NULL) {
LOG_DBG("bis_channels[%u]: NULL channel", i);
return false;
}
if (bis->iso) {
LOG_DBG("bis_channels[%u]: already allocated", i);
return false;
}
CHECKIF(bis->qos == NULL) {
LOG_DBG("bis_channels[%u]: qos is NULL", i);
return false;
}
CHECKIF(bis->qos->tx == NULL ||
!valid_chan_io_qos(bis->qos->tx, true, true,
advanced)) {
LOG_DBG("bis_channels[%u]: Invalid QOS", i);
return false;
}
}
CHECKIF(param->framing != BT_ISO_FRAMING_UNFRAMED &&
param->framing != BT_ISO_FRAMING_FRAMED) {
LOG_DBG("Invalid framing parameter: %u", param->framing);
return false;
}
CHECKIF(param->packing != BT_ISO_PACKING_SEQUENTIAL &&
param->packing != BT_ISO_PACKING_INTERLEAVED) {
LOG_DBG("Invalid packing parameter: %u", param->packing);
return false;
}
CHECKIF(param->num_bis > BT_ISO_MAX_GROUP_ISO_COUNT ||
param->num_bis > CONFIG_BT_ISO_MAX_CHAN) {
LOG_DBG("num_bis (%u) shall be lower than: %u", param->num_bis,
MAX(CONFIG_BT_ISO_MAX_CHAN, BT_ISO_MAX_GROUP_ISO_COUNT));
return false;
}
CHECKIF(!IN_RANGE(param->interval,
BT_ISO_SDU_INTERVAL_MIN,
BT_ISO_SDU_INTERVAL_MAX)) {
LOG_DBG("Invalid interval: %u", param->interval);
return false;
}
CHECKIF(!advanced &&
!IN_RANGE(param->latency,
BT_ISO_LATENCY_MIN,
BT_ISO_LATENCY_MAX)) {
LOG_DBG("Invalid latency: %u", param->latency);
return false;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
if (advanced) {
CHECKIF(!IN_RANGE(param->irc, BT_ISO_IRC_MIN, BT_ISO_IRC_MAX)) {
LOG_DBG("Invalid IRC %u", param->irc);
return false;
}
CHECKIF(!IN_RANGE(param->pto, BT_ISO_PTO_MIN, BT_ISO_PTO_MAX)) {
LOG_DBG("Invalid PTO %u", param->pto);
return false;
}
CHECKIF(!IN_RANGE(param->iso_interval,
BT_ISO_ISO_INTERVAL_MIN,
BT_ISO_ISO_INTERVAL_MAX)) {
LOG_DBG("Invalid ISO interval %u", param->iso_interval);
return false;
}
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
return true;
}
int bt_iso_big_create(struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *param,
struct bt_iso_big **out_big)
{
int err;
struct bt_iso_big *big;
bool advanced = false;
if (!atomic_test_bit(padv->flags, BT_PER_ADV_PARAMS_SET)) {
LOG_DBG("PA params not set; invalid adv object");
return -EINVAL;
}
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
advanced = is_advanced_big_param(param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
if (!valid_big_param(param, advanced)) {
LOG_DBG("Invalid BIG parameters");
return -EINVAL;
}
big = get_free_big();
if (!big) {
return -ENOMEM;
}
err = big_init_bis(big, param->bis_channels, param->num_bis, true);
if (err) {
LOG_DBG("Could not init BIG %d", err);
cleanup_big(big);
return err;
}
big->num_bis = param->num_bis;
if (!advanced) {
err = hci_le_create_big(padv, big, param);
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
} else {
err = hci_le_create_big_test(padv, big, param);
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
}
if (err) {
LOG_DBG("Could not create BIG %d", err);
cleanup_big(big);
return err;
}
*out_big = big;
return err;
}
static void store_bis_broadcaster_info(const struct bt_hci_evt_le_big_complete *evt,
struct bt_iso_info *info)
{
struct bt_iso_broadcaster_info *broadcaster_info = &info->broadcaster;
info->iso_interval = sys_le16_to_cpu(evt->iso_interval);
info->max_subevent = evt->nse;
broadcaster_info->sync_delay = sys_get_le24(evt->sync_delay);
broadcaster_info->latency = sys_get_le24(evt->latency);
broadcaster_info->phy = bt_get_phy(evt->phy);
broadcaster_info->bn = evt->bn;
broadcaster_info->irc = evt->irc;
/* Transform to n * 1.25ms */
broadcaster_info->pto = info->iso_interval * evt->pto;
broadcaster_info->max_pdu = sys_le16_to_cpu(evt->max_pdu);
info->can_send = true;
info->can_recv = false;
}
void hci_le_big_complete(struct net_buf *buf)
{
struct bt_hci_evt_le_big_complete *evt = (void *)buf->data;
struct bt_iso_chan *bis;
struct bt_iso_big *big;
int i;
if (evt->big_handle >= ARRAY_SIZE(bigs)) {
LOG_WRN("Invalid BIG handle");
big = big_lookup_flag(BT_BIG_PENDING);
if (big) {
big_disconnect(big, evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED);
cleanup_big(big);
}
return;
}
big = lookup_big_by_handle(evt->big_handle);
atomic_clear_bit(big->flags, BT_BIG_PENDING);
LOG_DBG("BIG[%u] %p completed, status 0x%02x", big->handle, big, evt->status);
if (evt->status || evt->num_bis != big->num_bis) {
if (evt->status == BT_HCI_ERR_SUCCESS && evt->num_bis != big->num_bis) {
LOG_ERR("Invalid number of BIS created, was %u expected %u", evt->num_bis,
big->num_bis);
}
big_disconnect(big, evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED);
cleanup_big(big);
return;
}
i = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
const uint16_t handle = evt->handle[i++];
struct bt_conn *iso_conn = bis->iso;
iso_conn->handle = sys_le16_to_cpu(handle);
store_bis_broadcaster_info(evt, &iso_conn->iso.info);
bt_conn_set_state(iso_conn, BT_CONN_CONNECTED);
}
}
void hci_le_big_terminate(struct net_buf *buf)
{
struct bt_hci_evt_le_big_terminate *evt = (void *)buf->data;
struct bt_iso_big *big;
if (evt->big_handle >= ARRAY_SIZE(bigs)) {
LOG_WRN("Invalid BIG handle");
return;
}
big = lookup_big_by_handle(evt->big_handle);
LOG_DBG("BIG[%u] %p terminated", big->handle, big);
big_disconnect(big, evt->reason);
cleanup_big(big);
}
#endif /* CONFIG_BT_ISO_BROADCASTER */
static int hci_le_terminate_big(struct bt_iso_big *big)
{
struct bt_hci_cp_le_terminate_big *req;
struct net_buf *buf;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_TERMINATE_BIG, sizeof(*req));
if (!buf) {
return -ENOBUFS;
}
req = net_buf_add(buf, sizeof(*req));
req->big_handle = big->handle;
req->reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN;
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_TERMINATE_BIG, buf, NULL);
}
static int hci_le_big_sync_term(struct bt_iso_big *big)
{
struct bt_hci_cp_le_big_terminate_sync *req;
struct bt_hci_rp_le_big_terminate_sync *evt;
struct net_buf *buf;
struct net_buf *rsp;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_BIG_TERMINATE_SYNC, sizeof(*req));
if (!buf) {
return -ENOBUFS;
}
req = net_buf_add(buf, sizeof(*req));
req->big_handle = big->handle;
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_BIG_TERMINATE_SYNC, buf, &rsp);
if (err) {
return err;
}
evt = (struct bt_hci_rp_le_big_terminate_sync *)rsp->data;
if (evt->status || (evt->big_handle != big->handle)) {
err = -EIO;
}
net_buf_unref(rsp);
return err;
}
int bt_iso_big_terminate(struct bt_iso_big *big)
{
struct bt_iso_chan *bis;
int err;
if (!atomic_test_bit(big->flags, BT_BIG_INITIALIZED) || !big->num_bis) {
LOG_DBG("BIG not initialized");
return -EINVAL;
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
__ASSERT(bis != NULL, "bis was NULL");
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER) {
err = hci_le_terminate_big(big);
/* Wait for BT_HCI_EVT_LE_BIG_TERMINATE before cleaning up
* the BIG in hci_le_big_terminate
*/
if (!err) {
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
bt_iso_chan_set_state(bis, BT_ISO_STATE_DISCONNECTING);
}
}
} else if (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) &&
bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER) {
err = hci_le_big_sync_term(big);
if (!err) {
big_disconnect(big, BT_HCI_ERR_LOCALHOST_TERM_CONN);
cleanup_big(big);
}
} else {
err = -EINVAL;
}
if (err) {
LOG_DBG("Could not terminate BIG %d", err);
}
return err;
}
#if defined(CONFIG_BT_ISO_SYNC_RECEIVER)
static void store_bis_sync_receiver_info(const struct bt_hci_evt_le_big_sync_established *evt,
struct bt_iso_info *info)
{
struct bt_iso_sync_receiver_info *receiver_info = &info->sync_receiver;
info->max_subevent = evt->nse;
info->iso_interval = sys_le16_to_cpu(evt->iso_interval);
receiver_info->latency = sys_get_le24(evt->latency);
receiver_info->bn = evt->bn;
receiver_info->irc = evt->irc;
/* Transform to n * 1.25ms */
receiver_info->pto = info->iso_interval * evt->pto;
receiver_info->max_pdu = sys_le16_to_cpu(evt->max_pdu);
info->can_send = false;
info->can_recv = true;
}
void hci_le_big_sync_established(struct net_buf *buf)
{
struct bt_hci_evt_le_big_sync_established *evt = (void *)buf->data;
struct bt_iso_chan *bis;
struct bt_iso_big *big;
int i;
if (evt->big_handle >= ARRAY_SIZE(bigs)) {
LOG_WRN("Invalid BIG handle");
big = big_lookup_flag(BT_BIG_SYNCING);
if (big) {
big_disconnect(big, evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED);
cleanup_big(big);
}
return;
}
big = lookup_big_by_handle(evt->big_handle);
atomic_clear_bit(big->flags, BT_BIG_SYNCING);
LOG_DBG("BIG[%u] %p sync established, status 0x%02x", big->handle, big, evt->status);
if (evt->status || evt->num_bis != big->num_bis) {
if (evt->status == BT_HCI_ERR_SUCCESS && evt->num_bis != big->num_bis) {
LOG_ERR("Invalid number of BIS synced, was %u expected %u", evt->num_bis,
big->num_bis);
}
big_disconnect(big, evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED);
cleanup_big(big);
return;
}
i = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
const uint16_t handle = evt->handle[i++];
struct bt_conn *iso_conn = bis->iso;
iso_conn->handle = sys_le16_to_cpu(handle);
store_bis_sync_receiver_info(evt, &iso_conn->iso.info);
bt_conn_set_state(iso_conn, BT_CONN_CONNECTED);
}
}
void hci_le_big_sync_lost(struct net_buf *buf)
{
struct bt_hci_evt_le_big_sync_lost *evt = (void *)buf->data;
struct bt_iso_big *big;
if (evt->big_handle >= ARRAY_SIZE(bigs)) {
LOG_WRN("Invalid BIG handle");
return;
}
big = lookup_big_by_handle(evt->big_handle);
LOG_DBG("BIG[%u] %p sync lost", big->handle, big);
big_disconnect(big, evt->reason);
cleanup_big(big);
}
static int hci_le_big_create_sync(const struct bt_le_per_adv_sync *sync, struct bt_iso_big *big,
const struct bt_iso_big_sync_param *param)
{
struct bt_hci_cp_le_big_create_sync *req;
struct bt_hci_cmd_state_set state;
struct net_buf *buf;
int err;
uint8_t bit_idx = 0;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_BIG_CREATE_SYNC, sizeof(*req) + big->num_bis);
if (!buf) {
return -ENOBUFS;
}
req = net_buf_add(buf, sizeof(*req) + big->num_bis);
req->big_handle = big->handle;
req->sync_handle = sys_cpu_to_le16(sync->handle);
req->encryption = param->encryption;
if (req->encryption) {
memcpy(req->bcode, param->bcode, sizeof(req->bcode));
} else {
memset(req->bcode, 0, sizeof(req->bcode));
}
req->mse = param->mse;
req->sync_timeout = sys_cpu_to_le16(param->sync_timeout);
req->num_bis = big->num_bis;
/* Transform from bitfield to array */
for (int i = 1; i <= BT_ISO_MAX_GROUP_ISO_COUNT; i++) {
if (param->bis_bitfield & BIT(i)) {
if (bit_idx == big->num_bis) {
LOG_DBG("BIG cannot contain %u BISes", bit_idx + 1);
return -EINVAL;
}
req->bis[bit_idx++] = i;
}
}
if (bit_idx != big->num_bis) {
LOG_DBG("Number of bits in bis_bitfield (%u) doesn't match num_bis (%u)", bit_idx,
big->num_bis);
return -EINVAL;
}
bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_SYNCING, true);
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_BIG_CREATE_SYNC, buf, NULL);
return err;
}
int bt_iso_big_sync(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param,
struct bt_iso_big **out_big)
{
int err;
struct bt_iso_chan *bis;
struct bt_iso_big *big;
if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_SYNCED)) {
LOG_DBG("PA sync not synced");
return -EINVAL;
}
CHECKIF(param->mse > BT_ISO_SYNC_MSE_MAX) {
LOG_DBG("Invalid MSE 0x%02x", param->mse);
return -EINVAL;
}
CHECKIF(param->sync_timeout < BT_ISO_SYNC_TIMEOUT_MIN ||
param->sync_timeout > BT_ISO_SYNC_TIMEOUT_MAX) {
LOG_DBG("Invalid sync timeout 0x%04x", param->sync_timeout);
return -EINVAL;
}
CHECKIF(param->bis_bitfield <= BIT(0)) {
LOG_DBG("Invalid BIS bitfield 0x%08x", param->bis_bitfield);
return -EINVAL;
}
CHECKIF(!param->bis_channels) {
LOG_DBG("NULL BIS channels");
return -EINVAL;
}
CHECKIF(!param->num_bis) {
LOG_DBG("Invalid number of BIS %u", param->num_bis);
return -EINVAL;
}
for (uint8_t i = 0; i < param->num_bis; i++) {
struct bt_iso_chan *param_bis = param->bis_channels[i];
CHECKIF(param_bis == NULL) {
LOG_DBG("bis_channels[%u]: NULL channel", i);
return -EINVAL;
}
if (param_bis->iso) {
LOG_DBG("bis_channels[%u]: already allocated", i);
return -EALREADY;
}
CHECKIF(param_bis->qos == NULL) {
LOG_DBG("bis_channels[%u]: qos is NULL", i);
return -EINVAL;
}
CHECKIF(param_bis->qos->rx == NULL) {
LOG_DBG("bis_channels[%u]: qos->rx is NULL", i);
return -EINVAL;
}
}
big = get_free_big();
if (!big) {
return -ENOMEM;
}
err = big_init_bis(big, param->bis_channels, param->num_bis, false);
if (err) {
LOG_DBG("Could not init BIG %d", err);
cleanup_big(big);
return err;
}
big->num_bis = param->num_bis;
err = hci_le_big_create_sync(sync, big, param);
if (err) {
LOG_DBG("Could not create BIG sync %d", err);
cleanup_big(big);
return err;
}
SYS_SLIST_FOR_EACH_CONTAINER(&big->bis_channels, bis, node) {
bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING);
}
*out_big = big;
return 0;
}
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
#endif /* CONFIG_BT_ISO_BROADCAST */