| /* Bluetooth ISO */ |
| |
| /* |
| * Copyright (c) 2020 Intel Corporation |
| * Copyright (c) 2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <sys/byteorder.h> |
| #include <sys/check.h> |
| |
| #include <bluetooth/hci.h> |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/conn.h> |
| #include <bluetooth/iso.h> |
| |
| #include "host/hci_core.h" |
| #include "host/conn_internal.h" |
| #include "iso_internal.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_ISO) |
| #define LOG_MODULE_NAME bt_iso |
| #include "common/log.h" |
| |
| #define iso_chan(_iso) ((_iso)->iso.chan); |
| |
| #if defined(CONFIG_BT_ISO_UNICAST) || defined(CONFIG_BT_ISO_SYNC_RECEIVER) |
| NET_BUF_POOL_FIXED_DEFINE(iso_rx_pool, CONFIG_BT_ISO_RX_BUF_COUNT, |
| BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_RX_MTU), 8, 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_UNICAST || CONFIG_BT_ISO_SYNC_RECEIVER */ |
| |
| #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), 8, 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), 8, 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_UNICAST) |
| struct bt_iso_cig cigs[CONFIG_BT_ISO_MAX_CIG]; |
| static struct bt_iso_server *iso_server; |
| |
| static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan); |
| #endif /* CONFIG_BT_ISO_UNICAST */ |
| #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_UNICAST) || defined(CONFIG_BT_ISO_BROADCASTER) |
| static void bt_iso_send_cb(struct bt_conn *iso, void *user_data) |
| { |
| 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 (ops != NULL && ops->sent != NULL) { |
| ops->sent(chan); |
| } |
| } |
| #endif /* CONFIG_BT_ISO_UNICAST || CONFIG_BT_ISO_BROADCASTER */ |
| |
| 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_DBG("buf %p", buf); |
| |
| BT_ASSERT(buf->len >= sizeof(*hdr)); |
| |
| 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_DBG("handle %u len %u flags %u", iso(buf)->handle, len, flags); |
| |
| if (buf->len != len) { |
| BT_ERR("ISO data length mismatch (%u != %u)", buf->len, len); |
| net_buf_unref(buf); |
| return; |
| } |
| |
| iso = bt_conn_lookup_handle(iso(buf)->handle); |
| if (iso == NULL) { |
| BT_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 { |
| BT_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; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_SETUP_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; |
| 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, cp->codec_config_len); |
| memcpy(cc, path->cc, cp->codec_config_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 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 = 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_chan_add(struct bt_conn *iso, struct bt_iso_chan *chan) |
| { |
| /* Attach ISO channel to the connection */ |
| chan->iso = iso; |
| iso->iso.chan = chan; |
| |
| BT_DBG("iso %p chan %p", iso, chan); |
| } |
| |
| static int bt_iso_setup_data_path(struct bt_conn *iso) |
| { |
| int err; |
| struct bt_iso_chan *chan; |
| struct bt_iso_chan_path default_hci_path = { .pid = BT_ISO_DATA_PATH_HCI }; |
| 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; |
| uint8_t dir; |
| |
| chan = iso_chan(iso); |
| if (chan == NULL) { |
| return -EINVAL; |
| } |
| |
| 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) { |
| 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) { |
| if (rx_qos->path != NULL) { /* Use application path */ |
| out_path = rx_qos->path; |
| } else { /* else fallback to HCI path */ |
| out_path = &default_hci_path; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && |
| iso->iso.type == BT_ISO_CHAN_TYPE_BROADCASTER && in_path) { |
| dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR; |
| return hci_le_setup_iso_data_path(iso, dir, in_path); |
| } else if (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) && |
| iso->iso.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER && out_path) { |
| dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST; |
| return hci_le_setup_iso_data_path(iso, dir, out_path); |
| } else if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) && |
| iso->iso.type == BT_ISO_CHAN_TYPE_CONNECTED) { |
| if (in_path != NULL && tx_qos->sdu > 0) { |
| /* Enable TX */ |
| dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR; |
| err = hci_le_setup_iso_data_path(iso, dir, in_path); |
| if (err) { |
| return err; |
| } |
| } |
| |
| if (out_path != NULL && rx_qos->sdu > 0) { |
| /* Enable RX */ |
| dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST; |
| err = hci_le_setup_iso_data_path(iso, dir, out_path); |
| if (err) { |
| return err; |
| } |
| } |
| |
| return 0; |
| } else { |
| __ASSERT(false, "Invalid iso.type: %u", iso->iso.type); |
| return -EINVAL; |
| } |
| } |
| |
| void bt_iso_connected(struct bt_conn *iso) |
| { |
| struct bt_iso_chan *chan; |
| |
| if (iso == NULL || iso->type != BT_CONN_TYPE_ISO) { |
| BT_DBG("Invalid parameters: iso %p iso->type %u", iso, |
| iso ? iso->type : 0); |
| return; |
| } |
| |
| BT_DBG("%p", iso); |
| |
| if (bt_iso_setup_data_path(iso)) { |
| BT_ERR("Unable to setup data path"); |
| #if defined(CONFIG_BT_ISO_BROADCAST) |
| if (iso->iso.type == BT_ISO_CHAN_TYPE_BROADCASTER || |
| iso->iso.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER) { |
| struct bt_iso_big *big; |
| int err; |
| |
| big = lookup_big_by_handle(iso->iso.big_handle); |
| |
| err = bt_iso_big_terminate(big); |
| if (err != 0) { |
| BT_ERR("Could not terminate BIG: %d", err); |
| } |
| } |
| #endif /* CONFIG_BT_ISO_BROADCAST */ |
| if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) && |
| iso->iso.type == BT_ISO_CHAN_TYPE_CONNECTED) { |
| bt_conn_disconnect(iso, |
| BT_HCI_ERR_REMOTE_USER_TERM_CONN); |
| } else { |
| __ASSERT(false, "Invalid iso.type: %u", iso->iso.type); |
| } |
| return; |
| } |
| |
| chan = iso_chan(iso); |
| if (chan == NULL) { |
| BT_ERR("Could not lookup chan from connected ISO"); |
| return; |
| } |
| |
| bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED); |
| |
| if (chan->ops->connected) { |
| chan->ops->connected(chan); |
| } |
| } |
| |
| static void bt_iso_remove_data_path(struct bt_conn *iso) |
| { |
| BT_DBG("%p", iso); |
| |
| if ((IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && |
| iso->iso.type == BT_ISO_CHAN_TYPE_BROADCASTER) || |
| (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) && |
| iso->iso.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 = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR; |
| } else { |
| dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST; |
| } |
| |
| (void)hci_le_remove_iso_data_path(iso, dir); |
| } else if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) && |
| iso->iso.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, |
| BT_HCI_DATAPATH_DIR_CTLR_TO_HOST); |
| (void)hci_le_remove_iso_data_path(iso, |
| BT_HCI_DATAPATH_DIR_HOST_TO_CTLR); |
| } else { |
| __ASSERT(false, "Invalid iso.type: %u", iso->iso.type); |
| } |
| } |
| |
| static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason) |
| { |
| BT_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.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; |
| } 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); |
| |
| #if defined(CONFIG_BT_ISO_UNICAST) |
| 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_UNICAST */ |
| } |
| } |
| |
| 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) { |
| BT_DBG("Invalid parameters: iso %p iso->type %u", iso, |
| iso ? iso->type : 0); |
| return; |
| } |
| |
| BT_DBG("%p", iso); |
| |
| chan = iso_chan(iso); |
| if (chan == NULL) { |
| BT_ERR("Could not lookup chan from disconnected ISO"); |
| return; |
| } |
| |
| bt_iso_chan_disconnected(chan, iso->err); |
| } |
| |
| #if defined(CONFIG_BT_DEBUG_ISO) |
| 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_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) |
| { |
| BT_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_CONNECTING: |
| if (chan->state != BT_ISO_STATE_DISCONNECTED) { |
| BT_WARN("%s()%d: invalid transition", func, line); |
| } |
| break; |
| case BT_ISO_STATE_CONNECTED: |
| if (chan->state != BT_ISO_STATE_CONNECTING) { |
| BT_WARN("%s()%d: invalid transition", func, line); |
| } |
| break; |
| case BT_ISO_STATE_DISCONNECTING: |
| if (chan->state != BT_ISO_STATE_CONNECTED) { |
| BT_WARN("%s()%d: invalid transition", func, line); |
| } |
| break; |
| default: |
| BT_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_DEBUG_ISO */ |
| |
| int bt_iso_chan_get_info(const struct bt_iso_chan *chan, |
| struct bt_iso_info *info) |
| { |
| CHECKIF(chan == NULL) { |
| BT_DBG("chan is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(chan->iso == NULL) { |
| BT_DBG("chan->iso is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(info == NULL) { |
| BT_DBG("info is NULL"); |
| return -EINVAL; |
| } |
| |
| info->type = chan->iso->iso.type; |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_BT_ISO_UNICAST) || defined(CONFIG_BT_ISO_SYNC_RECEIVER) |
| 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_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 0b00, 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)->sn = 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 { |
| BT_WARN("Invalid ISO packet status flag: %u", flags); |
| iso_info(buf)->flags = 0; |
| } |
| |
| BT_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) { |
| BT_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) { |
| BT_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) { |
| BT_ERR("Unexpected ISO continuation fragment"); |
| net_buf_unref(buf); |
| return; |
| } |
| |
| BT_DBG("Cont, len %u rx_len %u", buf->len, iso->rx_len); |
| |
| if (buf->len > net_buf_tailroom(iso->rx)) { |
| BT_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_DBG("End, len %u rx_len %u", buf->len, iso->rx_len); |
| |
| if (iso->rx == NULL) { |
| BT_ERR("Unexpected ISO end fragment"); |
| net_buf_unref(buf); |
| return; |
| } |
| |
| if (buf->len > net_buf_tailroom(iso->rx)) { |
| BT_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: |
| BT_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) { |
| BT_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_UNICAST) || defined(CONFIG_BT_ISO_SYNC_RECEIVER */ |
| |
| #if defined(CONFIG_BT_ISO_UNICAST) || defined(CONFIG_BT_ISO_BROADCASTER) |
| int bt_iso_chan_send(struct bt_iso_chan *chan, struct net_buf *buf) |
| { |
| struct bt_hci_iso_data_hdr *hdr; |
| struct bt_conn *iso_conn; |
| |
| CHECKIF(!chan || !buf) { |
| BT_DBG("Invalid parameters: chan %p buf %p", chan, buf); |
| return -EINVAL; |
| } |
| |
| BT_DBG("chan %p len %zu", chan, net_buf_frags_len(buf)); |
| |
| if (chan->state != BT_ISO_STATE_CONNECTED) { |
| BT_DBG("Not connected"); |
| return -ENOTCONN; |
| } |
| |
| iso_conn = chan->iso; |
| |
| hdr = net_buf_push(buf, sizeof(*hdr)); |
| hdr->sn = sys_cpu_to_le16(iso_conn->iso.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->iso.seq_num++; |
| |
| return bt_conn_send_cb(iso_conn, buf, bt_iso_send_cb, NULL); |
| } |
| |
| static bool valid_chan_io_qos(const struct bt_iso_chan_io_qos *io_qos, |
| bool is_tx) |
| { |
| 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) { |
| BT_DBG("sdu (%u) shall be smaller than %zu", |
| io_qos->sdu, max_sdu); |
| return false; |
| } |
| |
| if (io_qos->phy > BT_GAP_LE_PHY_CODED) { |
| BT_DBG("Invalid phy %u", io_qos->phy); |
| return false; |
| } |
| |
| return true; |
| } |
| #endif /* CONFIG_BT_ISO_UNICAST) || CONFIG_BT_ISO_BROADCASTER */ |
| |
| #if defined(CONFIG_BT_ISO_UNICAST) |
| 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) { |
| BT_DBG("Invalid parameters: iso %p iso->type %u", iso, |
| iso ? iso->type : 0); |
| return -EINVAL; |
| } |
| |
| BT_DBG("%p", iso); |
| |
| if (!iso_server) { |
| return -ENOMEM; |
| } |
| |
| 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) { |
| BT_ERR("Server failed to accept: %d", err); |
| return err; |
| } |
| |
| bt_iso_chan_add(iso, chan); |
| bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTING); |
| |
| return 0; |
| } |
| |
| static bool valid_chan_qos(const struct bt_iso_chan_qos *qos) |
| { |
| if (qos->rx != NULL) { |
| if (!valid_chan_io_qos(qos->rx, false)) { |
| BT_DBG("Invalid rx qos"); |
| return false; |
| } |
| } else if (qos->tx == NULL) { |
| BT_DBG("Both rx and tx qos are NULL"); |
| return false; |
| } |
| |
| if (qos->tx != NULL) { |
| if (!valid_chan_io_qos(qos->tx, true)) { |
| BT_DBG("Invalid tx qos"); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void bt_iso_cleanup_acl(struct bt_conn *iso) |
| { |
| BT_DBG("%p", iso); |
| |
| if (iso->iso.acl) { |
| bt_conn_unref(iso->iso.acl); |
| iso->iso.acl = NULL; |
| } |
| } |
| |
| 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; |
| |
| BT_DBG("status %u handle %u", evt->status, handle); |
| |
| /* ISO connection handles are already assigned at this point */ |
| iso = bt_conn_lookup_handle(handle); |
| if (!iso) { |
| BT_ERR("No connection found for handle %u", handle); |
| return; |
| } |
| |
| CHECKIF(iso->type != BT_CONN_TYPE_ISO) { |
| BT_DBG("Invalid connection type %u", iso->type); |
| return; |
| } |
| |
| if (!evt->status) { |
| /* Reset sequence number */ |
| iso->iso.seq_num = 0; |
| |
| if (iso->role == BT_HCI_ROLE_PERIPHERAL) { |
| struct bt_iso_chan_io_qos *rx; |
| struct bt_iso_chan_io_qos *tx; |
| 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"); |
| |
| rx = chan->qos->rx; |
| tx = chan->qos->tx; |
| |
| if (rx != NULL) { |
| rx->phy = evt->c_phy; |
| rx->sdu = evt->c_max_pdu; |
| } |
| |
| if (tx != NULL) { |
| tx->phy = evt->p_phy; |
| tx->sdu = evt->p_max_pdu; |
| } |
| |
| iso_conn->type = BT_ISO_CHAN_TYPE_CONNECTED; |
| } /* values are already set for central */ |
| |
| /* TODO: Add CIG sync delay */ |
| bt_conn_set_state(iso, BT_CONN_CONNECTED); |
| bt_conn_unref(iso); |
| return; |
| } |
| |
| iso->err = evt->status; |
| bt_iso_disconnected(iso); |
| bt_conn_unref(iso); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| int err; |
| |
| BT_DBG("acl_handle %u cis_handle %u cig_id %u cis %u", |
| acl_handle, cis_handle, evt->cig_id, evt->cis_id); |
| |
| /* Lookup existing connection with same handle */ |
| iso = bt_conn_lookup_handle(cis_handle); |
| if (iso) { |
| BT_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); |
| if (!acl) { |
| BT_ERR("Invalid ACL handle %u", acl_handle); |
| hci_le_reject_cis(cis_handle, BT_HCI_ERR_UNKNOWN_CONN_ID); |
| return; |
| } |
| |
| /* Add ISO connection */ |
| iso = bt_conn_add_iso(acl); |
| |
| bt_conn_unref(acl); |
| |
| if (!iso) { |
| BT_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.cig_id = evt->cig_id; |
| iso->iso.cis_id = evt->cis_id; |
| |
| /* Request application to accept */ |
| err = iso_accept(acl, iso); |
| if (err) { |
| BT_DBG("App rejected ISO %d", err); |
| 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_conn_unref(iso); |
| hci_le_reject_cis(cis_handle, |
| BT_HCI_ERR_INSUFFICIENT_RESOURCES); |
| return; |
| } |
| } |
| |
| 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); |
| } |
| |
| struct bt_conn *bt_conn_add_iso(struct bt_conn *acl) |
| { |
| struct bt_conn *iso = iso_new(); |
| |
| if (iso == NULL) { |
| BT_ERR("Unable to allocate ISO connection"); |
| return NULL; |
| } |
| |
| iso->iso.acl = bt_conn_ref(acl); |
| |
| return iso; |
| } |
| |
| 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->latency); |
| req->p_latency = sys_cpu_to_le16(param->latency); |
| sys_put_le24(param->interval, req->c_interval); |
| sys_put_le24(param->interval, req->p_interval); |
| |
| req->sca = param->sca; |
| req->packing = param->packing; |
| req->framing = param->framing; |
| req->num_cis = 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) { |
| BT_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; |
| } |
| } |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CIG_PARAMS, buf, &rsp); |
| if (err) { |
| return NULL; |
| } |
| |
| return rsp; |
| } |
| |
| static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan) |
| { |
| if (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]; |
| } |
| } |
| |
| BT_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) |
| { |
| 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) { |
| BT_ERR("Unable to allocate CIS connection"); |
| return -ENOMEM; |
| } |
| iso_conn = &cis->iso->iso; |
| |
| iso_conn->cig_id = cig->id; |
| iso_conn->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) |
| { |
| 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) { |
| BT_DBG("cis_channels[%d]: NULL channel", i); |
| return false; |
| } |
| |
| if (!valid_chan_qos(cis->qos)) { |
| BT_DBG("cis_channels[%d]: Invalid QOS", i); |
| return false; |
| } |
| } |
| |
| if (param->framing != BT_ISO_FRAMING_UNFRAMED && |
| param->framing != BT_ISO_FRAMING_FRAMED) { |
| BT_DBG("Invalid framing parameter: %u", param->framing); |
| return false; |
| } |
| |
| if (param->packing != BT_ISO_PACKING_SEQUENTIAL && |
| param->packing != BT_ISO_PACKING_INTERLEAVED) { |
| BT_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) { |
| BT_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 (param->interval < BT_ISO_INTERVAL_MIN || |
| param->interval > BT_ISO_INTERVAL_MAX) { |
| BT_DBG("Invalid interval: %u", param->interval); |
| return false; |
| } |
| |
| if (param->latency < BT_ISO_LATENCY_MIN || |
| param->latency > BT_ISO_LATENCY_MAX) { |
| BT_DBG("Invalid latency: %u", param->latency); |
| return false; |
| } |
| |
| 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; |
| int i; |
| |
| CHECKIF(out_cig == NULL) { |
| BT_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) { |
| BT_DBG("NULL CIS channels"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->num_cis == 0) { |
| BT_DBG("Invalid number of CIS %u", param->num_cis); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!valid_cig_param(param)) { |
| BT_DBG("Invalid CIG params"); |
| return -EINVAL; |
| } |
| |
| for (uint8_t i = 0; i < param->num_cis; i++) { |
| struct bt_iso_chan *cis = param->cis_channels[i]; |
| |
| if (cis->iso != NULL) { |
| BT_DBG("cis_channels[%d]: already allocated", i); |
| return false; |
| } |
| } |
| |
| cig = get_free_cig(); |
| |
| if (!cig) { |
| return -ENOMEM; |
| } |
| |
| err = cig_init_cis(cig, param); |
| if (err) { |
| BT_DBG("Could not init CIS %d", err); |
| cleanup_cig(cig); |
| return err; |
| } |
| |
| rsp = hci_le_set_cig_params(cig, param); |
| if (rsp == NULL) { |
| BT_WARN("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) { |
| BT_WARN("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; |
| struct bt_iso_chan *cis; |
| struct net_buf *rsp; |
| int err; |
| int i; |
| |
| CHECKIF(cig == NULL) { |
| BT_DBG("cig is NULL"); |
| return -EINVAL; |
| } |
| |
| if (cig->state != BT_ISO_CIG_STATE_CONFIGURED) { |
| BT_DBG("Invalid CIG state: %u", cig->state); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!valid_cig_param(param)) { |
| BT_DBG("Invalid CIG params"); |
| return -EINVAL; |
| } |
| |
| for (uint8_t i = 0; i < param->num_cis; i++) { |
| struct bt_iso_chan *cis = param->cis_channels[i]; |
| |
| if (cis->iso != NULL && !cis_is_in_cig(cig, cis)) { |
| BT_DBG("Cannot reconfigure other CIG's (id 0x%02X) CIS " |
| "with this CIG (id 0x%02X)", |
| cis->iso->iso.cig_id, cig->id); |
| 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) { |
| BT_DBG("Could not init CIS %d", err); |
| restore_cig(cig, existing_num_cis); |
| return err; |
| } |
| |
| rsp = hci_le_set_cig_params(cig, param); |
| if (rsp == NULL) { |
| BT_WARN("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) || |
| cig_rsp->num_handles != param->num_cis) { |
| BT_WARN("Unexpected response to hci_le_set_cig_params"); |
| err = -EIO; |
| net_buf_unref(rsp); |
| restore_cig(cig, existing_num_cis); |
| 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); |
| |
| return err; |
| } |
| |
| int bt_iso_cig_terminate(struct bt_iso_cig *cig) |
| { |
| int err; |
| |
| CHECKIF(cig == NULL) { |
| BT_DBG("cig is NULL"); |
| return -EINVAL; |
| } |
| |
| if (cig->state != BT_ISO_CIG_STATE_INACTIVE && |
| cig->state != BT_ISO_CIG_STATE_CONFIGURED) { |
| BT_DBG("Invalid CIG state: %u", cig->state); |
| return -EINVAL; |
| } |
| |
| err = hci_le_remove_cig(cig->id); |
| if (err != 0) { |
| BT_DBG("Failed to terminate CIG: %d", err); |
| return err; |
| } |
| |
| cleanup_cig(cig); |
| |
| return 0; |
| } |
| |
| 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)); |
| |
| req->num_cis = count; |
| |
| /* Program the cis parameters */ |
| for (size_t i = 0; i < count; i++) { |
| 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); |
| } |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CIS, buf, NULL); |
| } |
| |
| int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count) |
| { |
| int err; |
| |
| CHECKIF(param == NULL || count == 0) { |
| BT_DBG("param is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(count == 0) { |
| BT_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) { |
| BT_DBG("[%zu]: Invalid iso (%p)", i, param[i].iso_chan); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param[i].acl == NULL) { |
| BT_DBG("[%zu]: Invalid acl (%p)", i, param[i].acl); |
| return -EINVAL; |
| } |
| |
| CHECKIF((param[i].acl->type & BT_CONN_TYPE_LE) == 0) { |
| BT_DBG("[%zu]: acl type (%u) shall be an LE connection", |
| i, param[i].acl->type); |
| return -EINVAL; |
| } |
| |
| if (param[i].iso_chan->iso == NULL) { |
| BT_DBG("[%zu]: ISO has not been initialized in a CIG", |
| i); |
| return -EINVAL; |
| } |
| |
| if (param[i].iso_chan->state != BT_ISO_STATE_DISCONNECTED) { |
| BT_DBG("[%zu]: ISO is not in the BT_ISO_STATE_DISCONNECTED state: %u", |
| i, param[i].iso_chan->state); |
| return -EINVAL; |
| } |
| } |
| |
| err = hci_le_create_cis(param, count); |
| if (err) { |
| BT_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; |
| |
| 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; |
| } |
| |
| int bt_iso_chan_disconnect(struct bt_iso_chan *chan) |
| { |
| CHECKIF(!chan) { |
| BT_DBG("Invalid parameter: chan %p", chan); |
| return -EINVAL; |
| } |
| |
| CHECKIF(chan->iso == NULL) { |
| BT_DBG("Channel has not been initialized in a CIG"); |
| return -EINVAL; |
| } |
| |
| if (chan->iso->iso.acl == NULL) { |
| BT_DBG("Channel is not connected"); |
| return -ENOTCONN; |
| } |
| |
| return bt_conn_disconnect(chan->iso, BT_HCI_ERR_REMOTE_USER_TERM_CONN); |
| } |
| |
| int bt_iso_server_register(struct bt_iso_server *server) |
| { |
| CHECKIF(!server) { |
| BT_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 (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; |
| } |
| |
| BT_DBG("%p", server); |
| |
| iso_server = server; |
| |
| return 0; |
| } |
| #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]; |
| } |
| } |
| |
| BT_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]; |
| } |
| } |
| |
| BT_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) { |
| BT_ERR("Unable to allocate BIS connection"); |
| return -ENOMEM; |
| } |
| iso_conn = &bis->iso->iso; |
| |
| iso_conn->big_handle = big->handle; |
| iso_conn->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; |
| } |
| |
| 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; |
| |
| if (!atomic_test_bit(padv->flags, BT_PER_ADV_PARAMS_SET)) { |
| BT_DBG("PA params not set; invalid adv object"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!param->bis_channels) { |
| BT_DBG("NULL BIS channels"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!param->num_bis) { |
| BT_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 *bis = param->bis_channels[i]; |
| |
| CHECKIF(bis == NULL) { |
| BT_DBG("bis_channels[%u]: NULL channel", i); |
| return -EINVAL; |
| } |
| |
| if (bis->iso) { |
| BT_DBG("bis_channels[%u]: already allocated", i); |
| return -EALREADY; |
| } |
| |
| CHECKIF(bis->qos == NULL) { |
| BT_DBG("bis_channels[%u]: qos is NULL", i); |
| return -EINVAL; |
| } |
| |
| CHECKIF(bis->qos->tx == NULL || |
| !valid_chan_io_qos(bis->qos->tx, true)) { |
| BT_DBG("bis_channels[%u]: Invalid QOS", i); |
| return -EINVAL; |
| } |
| } |
| |
| CHECKIF(param->framing != BT_ISO_FRAMING_UNFRAMED && |
| param->framing != BT_ISO_FRAMING_FRAMED) { |
| BT_DBG("Invalid framing parameter: %u", param->framing); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->packing != BT_ISO_PACKING_SEQUENTIAL && |
| param->packing != BT_ISO_PACKING_INTERLEAVED) { |
| BT_DBG("Invalid packing parameter: %u", param->packing); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->num_bis > BT_ISO_MAX_GROUP_ISO_COUNT || |
| param->num_bis > CONFIG_BT_ISO_MAX_CHAN) { |
| BT_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 -EINVAL; |
| } |
| |
| CHECKIF(param->interval < BT_ISO_INTERVAL_MIN || |
| param->interval > BT_ISO_INTERVAL_MAX) { |
| BT_DBG("Invalid interval: %u", param->interval); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->latency < BT_ISO_LATENCY_MIN || |
| param->latency > BT_ISO_LATENCY_MAX) { |
| BT_DBG("Invalid latency: %u", param->latency); |
| return -EINVAL; |
| } |
| |
| big = get_free_big(); |
| |
| if (!big) { |
| return -ENOMEM; |
| } |
| |
| err = big_init_bis(big, param->bis_channels, param->num_bis, true); |
| if (err) { |
| BT_DBG("Could not init BIG %d", err); |
| cleanup_big(big); |
| return err; |
| } |
| big->num_bis = param->num_bis; |
| |
| err = hci_le_create_big(padv, big, param); |
| if (err) { |
| BT_DBG("Could not create BIG %d", err); |
| cleanup_big(big); |
| return err; |
| } |
| |
| *out_big = big; |
| |
| return err; |
| } |
| |
| 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)) { |
| BT_WARN("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); |
| |
| BT_DBG("BIG[%u] %p completed, status %u", 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) { |
| BT_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->iso.seq_num = 0; |
| iso_conn->handle = sys_le16_to_cpu(handle); |
| 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)) { |
| BT_WARN("Invalid BIG handle"); |
| return; |
| } |
| |
| big = lookup_big_by_handle(evt->big_handle); |
| |
| BT_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; |
| bool broadcaster; |
| |
| if (!atomic_test_bit(big->flags, BT_BIG_INITIALIZED) || !big->num_bis) { |
| BT_DBG("BIG not initialized"); |
| return -EINVAL; |
| } |
| |
| bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node); |
| __ASSERT(bis != NULL, "bis was NULL"); |
| |
| /* They all have the same QOS dir so we can just check the first */ |
| broadcaster = bis->qos->tx ? true : false; |
| |
| if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && 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)) { |
| 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) { |
| BT_DBG("Could not terminate BIG %d", err); |
| } |
| |
| return err; |
| } |
| |
| #if defined(CONFIG_BT_ISO_SYNC_RECEIVER) |
| 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)) { |
| BT_WARN("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); |
| |
| BT_DBG("BIG[%u] %p sync established, status %u", 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) { |
| BT_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++]; |
| |
| bis->iso->handle = sys_le16_to_cpu(handle); |
| bt_conn_set_state(bis->iso, BT_CONN_CONNECTED); |
| } |
| |
| /* TODO: Deal with the rest of the fields in the event, |
| * if it makes sense |
| */ |
| } |
| |
| 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)) { |
| BT_WARN("Invalid BIG handle"); |
| return; |
| } |
| |
| big = lookup_big_by_handle(evt->big_handle); |
| |
| BT_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) { |
| BT_DBG("BIG cannot contain %u BISes", bit_idx + 1); |
| return -EINVAL; |
| } |
| req->bis[bit_idx++] = i; |
| } |
| } |
| |
| if (bit_idx != big->num_bis) { |
| BT_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)) { |
| BT_DBG("PA sync not synced"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->mse > BT_ISO_SYNC_MSE_MAX) { |
| BT_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) { |
| BT_DBG("Invalid sync timeout 0x%04x", param->sync_timeout); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->bis_bitfield <= BIT(0)) { |
| BT_DBG("Invalid BIS bitfield 0x%08x", param->bis_bitfield); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!param->bis_channels) { |
| BT_DBG("NULL BIS channels"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!param->num_bis) { |
| BT_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 *bis = param->bis_channels[i]; |
| |
| CHECKIF(bis == NULL) { |
| BT_DBG("bis_channels[%u]: NULL channel", i); |
| return -EINVAL; |
| } |
| |
| if (bis->iso) { |
| BT_DBG("bis_channels[%u]: already allocated", i); |
| return -EALREADY; |
| } |
| |
| CHECKIF(bis->qos == NULL) { |
| BT_DBG("bis_channels[%u]: qos is NULL", i); |
| return -EINVAL; |
| } |
| |
| CHECKIF(bis->qos->rx == NULL) { |
| BT_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) { |
| BT_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) { |
| BT_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 */ |