/* Bluetooth ISO */ /* * Copyright (c) 2020 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #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" NET_BUF_POOL_FIXED_DEFINE(iso_tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT, CONFIG_BT_ISO_TX_MTU, NULL); NET_BUF_POOL_FIXED_DEFINE(iso_rx_pool, CONFIG_BT_ISO_RX_BUF_COUNT, CONFIG_BT_ISO_RX_MTU, NULL); #if CONFIG_BT_ISO_TX_FRAG_COUNT > 0 NET_BUF_POOL_FIXED_DEFINE(iso_frag_pool, CONFIG_BT_ISO_TX_FRAG_COUNT, CONFIG_BT_ISO_TX_MTU, NULL); #endif /* CONFIG_BT_ISO_TX_FRAG_COUNT > 0 */ /* TODO: Allow more than one server? */ static struct bt_iso_server *iso_server; struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN]; #if defined(CONFIG_BT_ISO_BROADCAST) struct bt_iso_big bigs[CONFIG_BT_ISO_MAX_BIG]; #endif /* defined(CONFIG_BT_ISO_BROADCAST) */ struct bt_iso_data_path { /* Data Path direction */ uint8_t dir; /* Data Path ID */ uint8_t pid; /* Data Path param reference */ struct bt_iso_chan_path *path; }; 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 hci_iso(struct net_buf *buf) { struct bt_hci_iso_hdr *hdr; uint16_t handle, len; struct bt_conn *conn; uint8_t flags; BT_DBG("buf %p", buf); BT_ASSERT(buf->len >= sizeof(*hdr)); hdr = net_buf_pull_mem(buf, sizeof(*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; } conn = bt_conn_lookup_handle(iso(buf)->handle); if (!conn) { BT_ERR("Unable to find conn for handle %u", iso(buf)->handle); net_buf_unref(buf); return; } iso(buf)->index = bt_conn_index(conn); bt_conn_recv(conn, buf, flags); bt_conn_unref(conn); } void hci_le_cis_estabilished(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 *conn; BT_DBG("status %u handle %u", evt->status, handle); /* ISO connection handles are already assigned at this point */ conn = bt_conn_lookup_handle(handle); if (!conn) { BT_ERR("No connection found for handle %u", handle); return; } CHECKIF(conn->type != BT_CONN_TYPE_ISO) { BT_DBG("Invalid connection type %u", conn->type); return; } if (!evt->status) { /* TODO: Add CIG sync delay */ bt_conn_set_state(conn, BT_CONN_CONNECTED); bt_conn_unref(conn); return; } conn->err = evt->status; bt_iso_disconnected(conn); bt_conn_unref(conn); } 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 *conn, *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 */ conn = bt_conn_lookup_handle(acl_handle); if (!conn) { 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(conn); bt_conn_unref(conn); if (!iso) { BT_ERR("Could not create and add ISO to conn %u", acl_handle); hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); return; } /* Request application to accept */ err = bt_iso_accept(iso); if (err) { BT_DBG("App rejected ISO %d", err); bt_iso_cleanup(iso); hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); return; } iso->handle = cis_handle; iso->role = BT_HCI_ROLE_SLAVE; bt_conn_set_state(iso, BT_CONN_CONNECT); err = hci_le_accept_cis(cis_handle); if (err) { bt_iso_cleanup(iso); hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); return; } } 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); } void bt_iso_cleanup(struct bt_conn *conn) { struct bt_conn_iso *iso = bt_conn_iso(conn); int i; BT_DBG("%p", conn); /* Check if ISO connection is in fact a BIS */ if (!iso->acl) { goto done; } /* If ACL is still connected there are channels to serve that means the * connection is still in use. */ if (iso->acl->state == BT_CONN_CONNECTED && !sys_slist_is_empty(&conn->channels)) { goto done; } bt_conn_unref(iso->acl); iso->acl = NULL; /* Check if conn is last of CIG */ for (i = 0; i < CONFIG_BT_ISO_MAX_CHAN; i++) { if (conn == &iso_conns[i]) { continue; } if (atomic_get(&iso_conns[i].ref) && iso_conns[i].iso.cig_id == conn->iso.cig_id) { break; } } if (i == CONFIG_BT_ISO_MAX_CHAN) { hci_le_remove_cig(conn->iso.cig_id); } done: bt_conn_unref(conn); } 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; sys_slist_init(&iso->channels); } else { BT_DBG("Could not create new ISO"); } return iso; } struct bt_conn *bt_conn_add_iso(struct bt_conn *acl) { struct bt_conn *conn = iso_new(); if (!conn) { return NULL; } conn->iso.acl = bt_conn_ref(acl); return conn; } #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 struct net_buf *hci_le_set_cig_params(struct bt_iso_create_param *param) { struct bt_hci_cis_params *cis; struct bt_hci_cp_le_set_cig_params *req; 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->num_conns); if (!buf) { return NULL; } req = net_buf_add(buf, sizeof(*req)); memset(req, 0, sizeof(*req)); req->cig_id = param->conns[0]->iso.cig_id; sys_put_le24(param->chans[0]->qos->tx->interval, req->m_interval); sys_put_le24(param->chans[0]->qos->rx->interval, req->s_interval); req->sca = param->chans[0]->qos->sca; req->packing = param->chans[0]->qos->packing; req->framing = param->chans[0]->qos->framing; req->m_latency = sys_cpu_to_le16(param->chans[0]->qos->tx->latency); req->s_latency = sys_cpu_to_le16(param->chans[0]->qos->rx->latency); req->num_cis = param->num_conns; /* Program the cis parameters */ for (i = 0; i < param->num_conns; i++) { struct bt_iso_chan_qos *qos = param->chans[i]->qos; cis = net_buf_add(buf, sizeof(*cis)); memset(cis, 0, sizeof(*cis)); cis->cis_id = param->conns[i]->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) */ cis->m_phy = qos->rx->phy; } else { cis->m_sdu = sys_cpu_to_le16(qos->tx->sdu); cis->m_phy = qos->tx->phy; cis->m_rtn = qos->tx->rtn; } if (!qos->rx) { /* Use TX PHY if RX is not set (disabled) */ cis->s_phy = qos->tx->phy; } else { cis->s_sdu = sys_cpu_to_le16(qos->rx->sdu); cis->s_phy = qos->rx->phy; cis->s_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; } int bt_conn_bind_iso(struct bt_iso_create_param *param) { struct bt_conn *conn; struct net_buf *rsp; struct bt_hci_rp_le_set_cig_params *cig_rsp; int i, err; /* Check if controller is ISO capable */ if (!BT_FEAT_LE_CIS_MASTER(bt_dev.le.features)) { return -ENOTSUP; } if (!param->num_conns || param->num_conns > CONFIG_BT_ISO_MAX_CHAN) { return -EINVAL; } /* Assign ISO connections to each LE connection */ for (i = 0; i < param->num_conns; i++) { conn = param->conns[i]; if (conn->type != BT_CONN_TYPE_LE) { err = -EINVAL; goto failed; } conn = bt_conn_add_iso(conn); if (!conn) { err = -ENOMEM; goto failed; } conn->iso.cig_id = param->id; conn->iso.cis_id = bt_conn_index(conn); param->conns[i] = conn; } rsp = hci_le_set_cig_params(param); if (!rsp) { err = -EIO; goto failed; } cig_rsp = (void *)rsp->data; if (rsp->len < sizeof(cig_rsp) || cig_rsp->num_handles != param->num_conns) { BT_WARN("Unexpected response to hci_le_set_cig_params"); err = -EIO; net_buf_unref(rsp); goto failed; } for (i = 0; i < cig_rsp->num_handles; i++) { /* Assign the connection handle */ param->conns[i]->handle = cig_rsp->handle[i]; } net_buf_unref(rsp); return 0; failed: for (i = 0; i < param->num_conns; i++) { conn = param->conns[i]; if (conn->type == BT_CONN_TYPE_ISO) { bt_iso_cleanup(conn); } } return err; } static int hci_le_create_cis(struct bt_conn **conn, uint8_t num_conns) { struct bt_hci_cis *cis; struct bt_hci_cp_le_create_cis *req; struct net_buf *buf; int i; buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_CIS, sizeof(*req) + sizeof(*cis) * num_conns); if (!buf) { return -ENOBUFS; } req = net_buf_add(buf, sizeof(*req)); memset(req, 0, sizeof(*req)); req->num_cis = num_conns; /* Program the cis parameters */ for (i = 0; i < num_conns; i++) { cis = net_buf_add(buf, sizeof(*cis)); memset(cis, 0, sizeof(*cis)); cis->cis_handle = sys_cpu_to_le16(conn[i]->handle); cis->acl_handle = sys_cpu_to_le16(conn[i]->iso.acl->handle); } return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CIS, buf, NULL); } int bt_conn_connect_iso(struct bt_conn **conns, uint8_t num_conns) { int i, err; /* Check if controller is ISO capable */ if (!BT_FEAT_LE_CIS_MASTER(bt_dev.le.features)) { return -ENOTSUP; } if (num_conns > CONFIG_BT_ISO_MAX_CHAN) { return -EINVAL; } for (i = 0; i < num_conns; i++) { if (conns[i]->type != BT_CONN_TYPE_ISO) { return -EINVAL; } } err = hci_le_create_cis(conns, num_conns); if (err) { return err; } /* Set connection state */ for (i = 0; i < num_conns; i++) { bt_conn_set_state(conns[i], BT_CONN_CONNECT); } return 0; } static int hci_le_setup_iso_data_path(struct bt_conn *conn, struct bt_iso_data_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(conn->handle); cp->path_dir = path->dir; cp->path_id = path->pid; cp->codec_id.coding_format = path->path->format; cp->codec_id.company_id = sys_cpu_to_le16(path->path->cid); cp->codec_id.vs_codec_id = sys_cpu_to_le16(path->path->vid); sys_put_le24(path->path->delay, cp->controller_delay); cp->codec_config_len = path->path->cc_len; cc = net_buf_add(buf, cp->codec_config_len); memcpy(cc, path->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 || (rp->handle != conn->handle)) { err = -EIO; } net_buf_unref(rsp); return err; } static int hci_le_remove_iso_data_path(struct bt_conn *conn, 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 = conn->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 || (rp->handle != conn->handle)) { err = -EIO; } net_buf_unref(rsp); return err; } static void bt_iso_chan_add(struct bt_conn *conn, struct bt_iso_chan *chan) { /* Attach channel to the connection */ sys_slist_append(&conn->channels, &chan->node); chan->conn = conn; BT_DBG("conn %p chan %p", conn, chan); } int bt_iso_accept(struct bt_conn *conn) { struct bt_iso_chan *chan; int err; CHECKIF(!conn || conn->type != BT_CONN_TYPE_ISO) { BT_DBG("Invalid parameters: conn %p conn->type %u", conn, conn ? conn->type : 0); return -EINVAL; } BT_DBG("%p", conn); if (!iso_server) { return -ENOMEM; } err = iso_server->accept(conn, &chan); if (err < 0) { BT_ERR("err %d", err); return err; } bt_iso_chan_add(conn, chan); bt_iso_chan_set_state(chan, BT_ISO_BOUND); return 0; } static int bt_iso_setup_data_path(struct bt_conn *conn) { int err; struct bt_iso_chan *chan; struct bt_iso_chan_path path = {}; struct bt_iso_data_path out_path = { .dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST }; struct bt_iso_data_path in_path = { .dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR }; chan = SYS_SLIST_PEEK_HEAD_CONTAINER(&conn->channels, chan, node); if (!chan) { return -EINVAL; } in_path.path = chan->qos->tx->path ? chan->qos->tx->path : &path; out_path.path = chan->qos->rx->path ? chan->qos->rx->path : &path; if (!chan->qos->tx) { in_path.pid = BT_ISO_DATA_PATH_DISABLED; } if (!chan->qos->rx) { out_path.pid = BT_ISO_DATA_PATH_DISABLED; } err = hci_le_setup_iso_data_path(conn, &in_path); if (err) { return err; } return hci_le_setup_iso_data_path(conn, &out_path); } void bt_iso_connected(struct bt_conn *conn) { struct bt_iso_chan *chan; CHECKIF(!conn || conn->type != BT_CONN_TYPE_ISO) { BT_DBG("Invalid parameters: conn %p conn->type %u", conn, conn ? conn->type : 0); return; } BT_DBG("%p", conn); if (bt_iso_setup_data_path(conn)) { BT_ERR("Unable to setup data path"); bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); return; } SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { if (chan->ops->connected) { chan->ops->connected(chan); } bt_iso_chan_set_state(chan, BT_ISO_CONNECTED); } } static void bt_iso_remove_data_path(struct bt_conn *conn) { BT_DBG("%p", conn); /* Remove both directions */ hci_le_remove_iso_data_path(conn, BT_HCI_DATAPATH_DIR_CTLR_TO_HOST); hci_le_remove_iso_data_path(conn, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR); } static void bt_iso_chan_disconnected(struct bt_iso_chan *chan) { BT_DBG("%p", chan); if (!chan->conn) { bt_iso_chan_set_state(chan, BT_ISO_DISCONNECTED); return; } bt_iso_chan_set_state(chan, BT_ISO_BOUND); /* Unbind if acting as slave or ACL has been disconnected */ if (chan->conn->role == BT_HCI_ROLE_SLAVE || chan->conn->iso.acl->state == BT_CONN_DISCONNECTED) { bt_iso_chan_unbind(chan); } if (chan->ops->disconnected) { chan->ops->disconnected(chan); } } void bt_iso_disconnected(struct bt_conn *conn) { struct bt_iso_chan *chan, *next; CHECKIF(!conn || conn->type != BT_CONN_TYPE_ISO) { BT_DBG("Invalid parameters: conn %p conn->type %u", conn, conn ? conn->type : 0); return; } BT_DBG("%p", conn); if (sys_slist_is_empty(&conn->channels)) { return; } bt_iso_remove_data_path(conn); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&conn->channels, chan, next, node) { bt_iso_chan_disconnected(chan); } } 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_SLAVE(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; } #if defined(CONFIG_BT_DEBUG_ISO) const char *bt_iso_chan_state_str(uint8_t state) { switch (state) { case BT_ISO_DISCONNECTED: return "disconnected"; case BT_ISO_BOUND: return "bound"; case BT_ISO_CONNECT: return "connect"; case BT_ISO_CONNECTED: return "connected"; case BT_ISO_DISCONNECT: return "disconnect"; default: return "unknown"; } } void bt_iso_chan_set_state_debug(struct bt_iso_chan *chan, uint8_t state, const char *func, int line) { BT_DBG("chan %p conn %p %s -> %s", chan, chan->conn, bt_iso_chan_state_str(chan->state), bt_iso_chan_state_str(state)); /* check transitions validness */ switch (state) { case BT_ISO_DISCONNECTED: case BT_ISO_BOUND: /* regardless of old state always allows these states */ break; case BT_ISO_CONNECT: if (chan->state != BT_ISO_BOUND) { BT_WARN("%s()%d: invalid transition", func, line); } break; case BT_ISO_CONNECTED: if (chan->state != BT_ISO_BOUND && chan->state != BT_ISO_CONNECT) { BT_WARN("%s()%d: invalid transition", func, line); } break; case BT_ISO_DISCONNECT: if (chan->state != BT_ISO_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, uint8_t state) { chan->state = state; } #endif /* CONFIG_BT_DEBUG_ISO */ void bt_iso_chan_remove(struct bt_conn *conn, struct bt_iso_chan *chan) { struct bt_iso_chan *c; sys_snode_t *prev = NULL; SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, c, node) { if (c == chan) { sys_slist_remove(&conn->channels, prev, &chan->node); return; } prev = &chan->node; } } int bt_iso_chan_bind(struct bt_conn **conns, uint8_t num_conns, struct bt_iso_chan **chans) { struct bt_iso_create_param param; int i, err; static uint8_t id; CHECKIF(!conns || !num_conns || !chans) { BT_DBG("Invalid parameters: conns %p num_conns %u chans %p", conns, num_conns, chans); return -EINVAL; } memset(¶m, 0, sizeof(param)); param.id = id++; param.num_conns = num_conns; param.conns = conns; param.chans = chans; err = bt_conn_bind_iso(¶m); if (err) { return err; } /* Bind respective connection to channel */ for (i = 0; i < num_conns; i++) { bt_iso_chan_add(conns[i], chans[i]); bt_iso_chan_set_state(chans[i], BT_ISO_BOUND); } return 0; } int bt_iso_chan_unbind(struct bt_iso_chan *chan) { CHECKIF(!chan) { BT_DBG("Invalid parameter: chan %p", chan); return -EINVAL; } if (!chan->conn || chan->state != BT_ISO_BOUND) { return -EINVAL; } bt_iso_chan_remove(chan->conn, chan); bt_conn_unref(chan->conn); chan->conn = NULL; bt_iso_chan_set_state(chan, BT_ISO_DISCONNECTED); return 0; } int bt_iso_chan_connect(struct bt_iso_chan **chans, uint8_t num_chans) { struct bt_conn *conns[CONFIG_BT_ISO_MAX_CHAN]; int i, err; CHECKIF(!chans || !num_chans) { BT_DBG("Invalid parameters: chans %p num_chans %u", chans, num_chans); return -EINVAL; } for (i = 0; i < num_chans; i++) { if (!chans[i]->conn) { return -ENOTCONN; } conns[i] = chans[i]->conn; } err = bt_conn_connect_iso(conns, num_chans); if (err) { return err; } for (i = 0; i < num_chans; i++) { bt_iso_chan_set_state(chans[i], BT_ISO_CONNECT); } return 0; } int bt_iso_chan_disconnect(struct bt_iso_chan *chan) { CHECKIF(!chan) { BT_DBG("Invalid parameter: chan %p", chan); return -EINVAL; } if (!chan->conn) { return -ENOTCONN; } if (chan->state == BT_ISO_BOUND) { bt_iso_chan_remove(chan->conn, chan); bt_iso_chan_disconnected(chan); return 0; } return bt_conn_disconnect(chan->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); } void bt_iso_recv(struct bt_conn *conn, 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; 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", conn->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: /* 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(buf)->ts = sys_le32_to_cpu(ts_hdr->ts); hdr = &ts_hdr->data; } else { hdr = net_buf_pull_mem(buf, sizeof(*hdr)); /* TODO: Generate a timestamp? */ iso(buf)->ts = 0x00000000; } len = sys_le16_to_cpu(hdr->slen); flags = bt_iso_pkt_flags(len); len = bt_iso_pkt_len(len); /* TODO: Drop the packet if NOP? */ BT_DBG("%s, len %u total %u flags 0x%02x timestamp %u", pb == BT_ISO_START ? "Start" : "Single", buf->len, len, flags, iso(buf)->ts); if (conn->rx) { BT_ERR("Unexpected ISO %s fragment", pb == BT_ISO_START ? "Start" : "Single"); bt_conn_reset_rx_state(conn); } conn->rx = buf; conn->rx_len = len - buf->len; if (conn->rx_len) { /* if conn->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(conn); } return; } break; case BT_ISO_CONT: /* The ISO_Data_Load field contains a continuation fragment of * an SDU. */ if (!conn->rx) { BT_ERR("Unexpected ISO continuation fragment"); net_buf_unref(buf); return; } BT_DBG("Cont, len %u rx_len %u", buf->len, conn->rx_len); if (buf->len > net_buf_tailroom(conn->rx)) { BT_ERR("Not enough buffer space for ISO data"); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } net_buf_add_mem(conn->rx, buf->data, buf->len); conn->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, conn->rx_len); if (!conn->rx) { BT_ERR("Unexpected ISO end fragment"); net_buf_unref(buf); return; } if (buf->len > net_buf_tailroom(conn->rx)) { BT_ERR("Not enough buffer space for ISO data"); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } net_buf_add_mem(conn->rx, buf->data, buf->len); conn->rx_len -= buf->len; net_buf_unref(buf); break; default: BT_ERR("Unexpected ISO pb flags (0x%02x)", pb); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) { if (chan->ops->recv) { chan->ops->recv(chan, conn->rx); } } bt_conn_reset_rx_state(conn); } int bt_iso_chan_send(struct bt_iso_chan *chan, struct net_buf *buf) { struct bt_hci_iso_data_hdr *hdr; static uint16_t sn; 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->conn) { BT_DBG("Not connected"); return -ENOTCONN; } hdr = net_buf_push(buf, sizeof(*hdr)); hdr->sn = sys_cpu_to_le16(sn++); hdr->slen = sys_cpu_to_le16(bt_iso_pkt_len_pack(net_buf_frags_len(buf) - sizeof(*hdr), BT_ISO_DATA_VALID)); return bt_conn_send(chan->conn, buf); } struct bt_conn_iso *bt_conn_iso(struct bt_conn *conn) { CHECKIF(!conn || conn->type != BT_CONN_TYPE_ISO) { BT_DBG("Invalid parameters: conn %p conn->type %u", conn, conn ? conn->type : 0); return NULL; } return &conn->iso; } #if defined(CONFIG_BT_ISO_BROADCAST) 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 (int i = 0; i < ARRAY_SIZE(bigs); i++) { if (!atomic_test_and_set_bit(bigs[i].flags, BT_BIG_INITIALIZED)) { bigs[i].handle = i; return &bigs[i]; } } BT_DBG("Could not allocate any more BIGs"); return NULL; } static struct bt_iso_big *big_lookup_flag(int bit) { for (int 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) { for (int i = 0; i < big->num_bis; i++) { struct bt_iso_chan *bis = big->bis[i]; if (bis->conn) { bt_iso_cleanup(bis->conn); bis->conn = NULL; } } memset(big, 0, sizeof(*big)); } static void big_disconnect(struct bt_iso_big *big) { for (int i = 0; i < big->num_bis; i++) { bt_iso_disconnected(big->bis[i]->conn); } } static int big_init_bis(struct bt_iso_big *big, bool broadcaster) { for (int i = 0; i < big->num_bis; i++) { struct bt_iso_chan *bis = big->bis[i]; if (!bis) { BT_DBG("BIS was NULL"); return -EINVAL; } if (bis->conn) { BT_DBG("BIS conn was already allocated"); return -EALREADY; } if (!bis->qos || (!bis->qos->tx && broadcaster)) { BT_DBG("BIS QOS was invalid"); return -EINVAL; } bis->conn = iso_new(); if (!bis->conn) { return -ENOMEM; } bis->conn->iso.big_handle = big->handle; bis->conn->iso.is_bis = true; bis->conn->iso.bis_id = bt_conn_index(bis->conn); bt_iso_chan_add(bis->conn, bis); bt_iso_chan_set_state(bis, BT_ISO_BOUND); } return 0; } 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; buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_BIG, sizeof(*req)); if (!buf) { return -ENOBUFS; } /* All BIS will share the same QOS */ qos = big->bis[0]->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(qos->tx->interval, req->sdu_interval); req->max_sdu = sys_cpu_to_le16(qos->tx->sdu); req->max_latency = sys_cpu_to_le16(qos->tx->latency); req->rtn = qos->tx->rtn; req->phy = qos->tx->phy; req->packing = qos->packing; req->framing = qos->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; } for (int i = 0; i < big->num_bis; i++) { bt_iso_chan_set_state(big->bis[i], BT_ISO_CONNECT); } 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; } big = get_free_big(); if (!big) { return -ENOMEM; } big->bis = param->bis_channels; big->num_bis = param->num_bis; err = big_init_bis(big, true); if (err) { BT_DBG("Could not init BIG %d", err); cleanup_big(big); return err; } 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; } 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) { int err; bool broadcaster; if (!atomic_test_bit(big->flags, BT_BIG_INITIALIZED) || !big->num_bis || !big->bis) { BT_DBG("BIG not initialized"); return -EINVAL; } for (int i = 0; i < big->num_bis; i++) { if (!big->bis[i]) { BT_DBG("BIG BIS[%d] not initialized", i); return -EINVAL; } } /* They all have the same QOS dir so we can just check the first */ broadcaster = big->bis[0]->qos->tx ? true : false; if (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) { for (int i = 0; i < big->num_bis; i++) { bt_iso_chan_set_state(big->bis[i], BT_ISO_DISCONNECT); } } } else { err = hci_le_big_sync_term(big); if (!err) { big_disconnect(big); cleanup_big(big); } } if (err) { BT_DBG("Could not terminate BIG %d", err); } 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_big *big; if (evt->big_handle >= ARRAY_SIZE(bigs)) { BT_WARN("Invalid BIG handle"); big = big_lookup_flag(BT_BIG_PENDING); if (big) { big_disconnect(big); cleanup_big(big); } return; } big = &bigs[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->num_bis != big->num_bis) { BT_ERR("Invalid number of BIS, was %u expected %u", evt->num_bis, big->num_bis); big_disconnect(big); cleanup_big(big); return; } for (int i = 0; i < big->num_bis; i++) { struct bt_iso_chan *bis = big->bis[i]; bis->conn->handle = sys_le16_to_cpu(evt->handle[i]); bt_conn_set_state(bis->conn, BT_CONN_CONNECTED); bt_conn_unref(bis->conn); } } 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 = &bigs[evt->big_handle]; BT_DBG("BIG[%u] %p terminated", big->handle, big); big_disconnect(big); cleanup_big(big); } 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_big *big; if (evt->big_handle >= ARRAY_SIZE(bigs)) { BT_WARN("Invalid BIG handle"); big = big_lookup_flag(BT_BIG_SYNCING); if (big) { big_disconnect(big); cleanup_big(big); } return; } big = &bigs[evt->big_handle]; atomic_clear_bit(big->flags, BT_BIG_SYNCING); BT_DBG("BIG[%u] %p sync established", big->handle, big); if (evt->status || evt->num_bis != big->num_bis) { big_disconnect(big); cleanup_big(big); return; } for (int i = 0; i < big->num_bis; i++) { struct bt_iso_chan *bis = big->bis[i]; uint16_t bis_handle = sys_le16_to_cpu(evt->handle[i]); bis->conn->handle = bis_handle; bt_conn_set_state(bis->conn, BT_CONN_CONNECTED); bt_conn_unref(bis->conn); } /* 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 = &bigs[evt->big_handle]; BT_DBG("BIG[%u] %p sync lost", big->handle, big); big_disconnect(big); 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 = 0; i < 0x1F; 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 + 1; /* indices start from 1 */ } } 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_big *big; if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_SYNCED)) { BT_DBG("PA sync not synced"); return -EINVAL; } CHECKIF(param->mse > 0x1F) { BT_DBG("Invalid MSE 0x%02x", param->mse); return -EINVAL; } CHECKIF(param->sync_timeout < 0x000A || param->sync_timeout > 0x4000) { BT_DBG("Invalid sync timeout 0x%04x", param->sync_timeout); return -EINVAL; } CHECKIF(!param->bis_bitfield) { 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; } big = get_free_big(); if (!big) { return -ENOMEM; } big->bis = param->bis_channels; big->num_bis = param->num_bis; err = big_init_bis(big, false); if (err) { BT_DBG("Could not init BIG %d", err); cleanup_big(big); return err; } 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; } for (int i = 0; i < big->num_bis; i++) { bt_iso_chan_set_state(big->bis[i], BT_ISO_CONNECT); } *out_big = big; return 0; } #endif /* defined(CONFIG_BT_ISO_BROADCAST) */