diff --git a/include/bluetooth/bluetooth.h b/include/bluetooth/bluetooth.h index b013786bdbc..59642df1c43 100644 --- a/include/bluetooth/bluetooth.h +++ b/include/bluetooth/bluetooth.h @@ -54,6 +54,9 @@ struct bt_le_per_adv_sync; /* Don't require everyone to include conn.h */ struct bt_conn; +/* Don't require everyone to include iso.h */ +struct bt_iso_biginfo; + struct bt_le_ext_adv_sent_info { /** The number of advertising events completed. */ uint8_t num_sent; @@ -1191,6 +1194,17 @@ struct bt_le_per_adv_sync_cb { void (*state_changed)(struct bt_le_per_adv_sync *sync, const struct bt_le_per_adv_sync_state_info *info); + /** + * @brief BIGInfo advertising report received. + * + * This callback notifies the application of a BIGInfo advertising report. + * This is received if the advertiser is broadcasting isochronous streams in a BIG. + * See iso.h for more information. + * + * @param sync The advertising set object. + * @param biginfo The BIGInfo report. + */ + void (*biginfo)(struct bt_le_per_adv_sync *sync, const struct bt_iso_biginfo *biginfo); sys_snode_t node; }; diff --git a/include/bluetooth/hci.h b/include/bluetooth/hci.h index d6757c95cf1..8c130cf8547 100644 --- a/include/bluetooth/hci.h +++ b/include/bluetooth/hci.h @@ -2396,6 +2396,7 @@ struct bt_hci_evt_le_big_complete { uint8_t pto; uint8_t irc; uint16_t max_pdu; + uint16_t iso_interval; uint8_t num_bis; uint16_t handle[0]; } __packed; @@ -2416,6 +2417,7 @@ struct bt_hci_evt_le_big_sync_established { uint8_t pto; uint8_t irc; uint16_t max_pdu; + uint16_t iso_interval; uint8_t num_bis; uint16_t handle[0]; } __packed; diff --git a/include/bluetooth/iso.h b/include/bluetooth/iso.h index f04de6f8372..838a030a49e 100644 --- a/include/bluetooth/iso.h +++ b/include/bluetooth/iso.h @@ -79,24 +79,25 @@ struct bt_iso_chan_qos { /** @brief Channel direction * * Possible values: BT_ISO_CHAN_QOS_IN, BT_ISO_CHAN_QOS_OUT or - * BT_ISO_CHAN_QOS_INOUT. + * BT_ISO_CHAN_QOS_INOUT. Shall be BT_ISO_CHAN_QOS_IN for broadcast + * transmitting, and BT_ISO_CHAN_QOS_OUT for broadcast receiver. */ uint8_t dir; - /** Channel interval */ + /** Channel interval in us. Value range 0x0000FF - 0x0FFFFFF. */ uint32_t interval; - /** Channel SCA */ + /** Channel SCA - Only for CIS */ uint8_t sca; - /** Channel packing mode */ + /** Channel packing mode. 0 for unpacked, 1 for packed. */ uint8_t packing; - /** Channel framing mode */ + /** Channel framing mode. 0 for unframed, 1 for framed. */ uint8_t framing; - /** Channel Latency */ + /** Channel Latency in ms. Value range 0x0005 - 0x0FA0. */ uint16_t latency; - /** Channel SDU */ + /** Channel SDU. Value range 0x0000 0 0x0FFF. */ uint8_t sdu; - /** Channel PHY */ + /** Channel PHY - See BT_GAP_LE_PHY for values. Shall not be BT_GAP_LE_PHY_NONE. */ uint8_t phy; - /** Channel Retransmission Number */ + /** Channel Retransmission Number. Value range 0x00 - 0x0F. */ uint8_t rtn; }; @@ -118,6 +119,103 @@ struct bt_iso_chan_path { uint8_t cc[0]; }; + +/** Opaque type representing an Broadcast Isochronous Group (BIG). */ +struct bt_iso_big; + +struct bt_iso_big_create_param { + /** Array of pointers to BIS channels */ + struct bt_iso_chan **bis_channels; + + /** Number channels in @p bis_channels */ + uint8_t num_bis; + + /** Whether or not to encrypt the streams. */ + bool encryption; + + /** @brief Broadcast code + * + * The code used to derive the session key that is used to encrypt and + * decrypt BIS payloads. + */ + uint8_t bcode[16]; +}; + +struct bt_iso_big_sync_param { + /** Array of pointers to BIS channels */ + struct bt_iso_chan **bis_channels; + + /** Number channels in @p bis_channels */ + uint8_t num_bis; + + /** Bitfield of the BISes to sync to */ + uint32_t bis_bitfield; + + /** @brief Maximum subevents + * + * The MSE (Maximum Subevents) parameter is the maximum number of subevents that a + * Controller should use to receive data payloads in each interval for a BIS + */ + uint32_t mse; + + /** Synchronization timeout for the BIG (N * 10 MS) */ + uint16_t sync_timeout; + + /** Whether or not the streams of the BIG are encrypted */ + bool encryption; + + /** @brief Broadcast code + * + * The code used to derive the session key that is used to encrypt and + * decrypt BIS payloads. + */ + uint8_t bcode[16]; +}; + +struct bt_iso_biginfo { + /** Address of the advertiser */ + const bt_addr_le_t *addr; + + /** Advertiser SID */ + uint8_t sid; + + /** Number of BISes in the BIG */ + uint8_t num_bis; + + /** Maximum number of subevents in each isochronous event */ + uint8_t sub_evt_count; + + /** Interval between two BIG anchor point (N * 1.25 ms) */ + uint16_t iso_interval; + + /** The number of new payloads in each BIS event */ + uint8_t burst_number; + + /** Offset used for pre-transmissions */ + uint8_t offset; + + /** The number of times a payload is transmitted in a BIS event */ + uint8_t rep_count; + + /** Maximum size, in octets, of the payload */ + uint16_t max_pdu; + + /** The interval, in microseconds, of periodic SDUs. */ + uint32_t sdu_interval; + + /** Maximum size of an SDU, in octets. */ + uint16_t max_sdu; + + /** Channel PHY */ + uint8_t phy; + + /** Channel framing mode */ + uint8_t framing; + + /** Whether or not the BIG is encrypted */ + bool encryption; +}; + /** @brief ISO Channel operations structure. */ struct bt_iso_chan_ops { /** @brief Channel connected callback @@ -245,6 +343,38 @@ int bt_iso_chan_disconnect(struct bt_iso_chan *chan); */ int bt_iso_chan_send(struct bt_iso_chan *chan, struct net_buf *buf); +/** @brief Creates a BIG as a broadcaster + * + * @param[in] padv Pointer to the periodic advertising object the BIGInfo shall be sent on. + * @param[in] param The parameters used to create and enable the BIG. The QOS parameters are + * determined by the QOS field of the first BIS in the BIS list of this + * parameter. + * @param[out] out_big Broadcast Isochronous Group object on success. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_iso_big_create(struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *param, + struct bt_iso_big **out_big); + +/** @brief Terminates a BIG as a broadcaster or receiver + * + * @param big Pointer to the BIG structure. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_iso_big_terminate(struct bt_iso_big *big); + +/** @brief Creates a BIG as a receiver + * + * @param[in] sync Pointer to the periodic advertising sync object the BIGInfo was received on. + * @param[in] param The parameters used to create and enable the BIG sync. + * @param[out] out_big Broadcast Isochronous Group object on success. + * + * @return 0 in case of success or negative value in case of error. + */ +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); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/Kconfig b/subsys/bluetooth/Kconfig index 4e0e93a9473..88c1d335500 100644 --- a/subsys/bluetooth/Kconfig +++ b/subsys/bluetooth/Kconfig @@ -279,6 +279,27 @@ config BT_ISO_RX_MTU range 23 4095 help Maximum MTU for Isochronous channels RX buffers. + +# TODO: Split between broadcaster and observer for optimization +config BT_ISO_BROADCAST + bool "Bluetooth ISO Broadcast Channels supported" + select BT_EXT_ADV + select BT_PER_ADV + select BT_PER_ADV_SYNC + help + This option enables support for Bluetooth Broadcast + Isochronous channels. + +if BT_ISO_BROADCAST + +config BT_ISO_MAX_BIG + int "Maximum number of Broadcast Isochronous Groups (BIGs) to support" + default 1 + help + Maximmum number of BIGs that are supported by the host. A BIG can be + used for either transmitting or receiving, but not at the same time. + +endif # BT_ISO_BROADCAST endif # BT_ISO # Workaround for not being able to have commas in macro arguments diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index 277bf5f2af6..f2ffa07fb4f 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -32,6 +32,15 @@ config BT_AUDIO_UNICAST This option enables support for Bluetooth Unicast Audio using Isochronous channels. +# TODO: Make BT_AUDIO_BROADCAST not depend on BT_CONN +config BT_AUDIO_BROADCAST + bool "Bluetooth Broadcast Audio Support" + select BT_ISO + select BT_ISO_BROADCAST + help + This option enables support for Bluetooth Broadcast Audio using + Isochronous channels. + endif # BT_CONN config BT_AUDIO_DEBUG diff --git a/subsys/bluetooth/host/conn_internal.h b/subsys/bluetooth/host/conn_internal.h index c73e9ebbb00..b661b5b3718 100644 --- a/subsys/bluetooth/host/conn_internal.h +++ b/subsys/bluetooth/host/conn_internal.h @@ -99,10 +99,23 @@ struct bt_conn_sco { struct bt_conn_iso { /* Reference to ACL Connection */ struct bt_conn *acl; - /* CIG ID */ - uint8_t cig_id; - /* CIS ID */ - uint8_t cis_id; + union { + /* CIG ID */ + uint8_t cig_id; + /* BIG handle */ + uint8_t big_handle; + }; + + union { + /* CIS ID */ + uint8_t cis_id; + + /* BIS ID */ + uint8_t bis_id; + }; + + /** If true, this is a ISO for a BIS, else it is a ISO for a CIS */ + bool is_bis; }; typedef void (*bt_conn_tx_cb_t)(struct bt_conn *conn, void *user_data); diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c index 93e29c1186c..836ea6807d0 100644 --- a/subsys/bluetooth/host/hci_core.c +++ b/subsys/bluetooth/host/hci_core.c @@ -4737,6 +4737,47 @@ static void le_past_received(struct net_buf *buf) } } #endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_ISO_BROADCAST) +static void hci_le_biginfo_adv_report(struct net_buf *buf) +{ + struct bt_hci_evt_le_biginfo_adv_report *evt; + struct bt_le_per_adv_sync *per_adv_sync; + struct bt_le_per_adv_sync_cb *listener; + struct bt_iso_biginfo biginfo; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + per_adv_sync = get_per_adv_sync(sys_le16_to_cpu(evt->sync_handle)); + + if (!per_adv_sync) { + BT_ERR("Unknown handle 0x%04X for periodic advertising report", + sys_le16_to_cpu(evt->sync_handle)); + return; + } + + biginfo.addr = &per_adv_sync->addr; + biginfo.sid = per_adv_sync->sid; + biginfo.num_bis = evt->num_bis; + biginfo.sub_evt_count = evt->nse; + biginfo.iso_interval = sys_le16_to_cpu(evt->iso_interval); + biginfo.burst_number = evt->bn; + biginfo.offset = evt->pto; + biginfo.rep_count = evt->irc; + biginfo.max_pdu = sys_le16_to_cpu(evt->max_pdu); + biginfo.sdu_interval = sys_get_le24(evt->sdu_interval); + biginfo.max_sdu = sys_le16_to_cpu(evt->max_sdu); + biginfo.phy = evt->phy; + biginfo.framing = evt->framing; + biginfo.encryption = evt->encryption ? true : false; + + SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) { + if (listener->biginfo) { + listener->biginfo(per_adv_sync, &biginfo); + } + } +} +#endif /* defined(CONFIG_BT_ISO_BROADCAST) */ #endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ #endif /* defined(CONFIG_BT_EXT_ADV) */ @@ -5025,6 +5066,23 @@ static const struct event_handler meta_events[] = { sizeof(struct bt_hci_evt_le_cis_established)), EVENT_HANDLER(BT_HCI_EVT_LE_CIS_REQ, hci_le_cis_req, sizeof(struct bt_hci_evt_le_cis_req)), +#if defined(CONFIG_BT_ISO_BROADCAST) + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_COMPLETE, + hci_le_big_complete, + sizeof(struct bt_hci_evt_le_big_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_TERMINATE, + hci_le_big_termimate, + sizeof(struct bt_hci_evt_le_big_terminate)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_SYNC_ESTABLISHED, + hci_le_big_sync_established, + sizeof(struct bt_hci_evt_le_big_sync_established)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_SYNC_LOST, + hci_le_big_sync_lost, + sizeof(struct bt_hci_evt_le_big_sync_lost)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIGINFO_ADV_REPORT, + hci_le_biginfo_adv_report, + sizeof(struct bt_hci_evt_le_biginfo_adv_report)), +#endif /* (CONFIG_BT_ISO_BROADCAST) */ #endif /* (CONFIG_BT_ISO) */ }; @@ -5578,6 +5636,20 @@ static int le_set_event_mask(void) } } + /* Enable BIS events for broadcaster and/or receiver */ + if (IS_ENABLED(CONFIG_BT_ISO_BROADCAST) && + BT_FEAT_LE_BIS(bt_dev.le.features)) { + if (BT_FEAT_LE_ISO_BROADCASTER(bt_dev.le.features)) { + mask |= BT_EVT_MASK_LE_BIG_COMPLETE; + mask |= BT_EVT_MASK_LE_BIG_TERMINATED; + } + if (BT_FEAT_LE_SYNC_RECEIVER(bt_dev.le.features)) { + mask |= BT_EVT_MASK_LE_BIG_SYNC_ESTABLISHED; + mask |= BT_EVT_MASK_LE_BIG_SYNC_LOST; + mask |= BT_EVT_MASK_LE_BIGINFO_ADV_REPORT; + } + } + sys_put_le64(mask, cp_mask->events); return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EVENT_MASK, buf, NULL); } diff --git a/subsys/bluetooth/host/iso.c b/subsys/bluetooth/host/iso.c index ff61a2e63e5..1a8eb6b56d4 100644 --- a/subsys/bluetooth/host/iso.c +++ b/subsys/bluetooth/host/iso.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -35,6 +36,9 @@ NET_BUF_POOL_FIXED_DEFINE(iso_frag_pool, CONFIG_BT_ISO_TX_FRAG_COUNT, /* 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 { @@ -253,32 +257,40 @@ void bt_iso_cleanup(struct bt_conn *conn) { int i; - bt_conn_unref(conn->iso.acl); - conn->iso.acl = NULL; + if (conn->iso.acl) { /* CIS */ + bt_conn_unref(conn->iso.acl); + conn->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; + /* 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 (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); } } - - if (i == CONFIG_BT_ISO_MAX_CHAN) { - hci_le_remove_cig(conn->iso.cig_id); - } - bt_conn_unref(conn); - } struct bt_conn *iso_new(void) { - return bt_conn_new(iso_conns, ARRAY_SIZE(iso_conns)); + 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) @@ -290,8 +302,6 @@ struct bt_conn *bt_conn_add_iso(struct bt_conn *acl) } conn->iso.acl = bt_conn_ref(acl); - conn->type = BT_CONN_TYPE_ISO; - sys_slist_init(&conn->channels); return conn; } @@ -1096,3 +1106,469 @@ int bt_iso_chan_send(struct bt_iso_chan *chan, struct net_buf *buf) return bt_conn_send(chan->conn, buf); } + +#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_get(&bigs[i].initialized) == false) { + bigs[i].handle = i; + return &bigs[i]; + } + } + + BT_DBG("Could not allocate any more BIGs"); + + 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; + } + } + + big->bis = NULL; + big->num_bis = 0; + atomic_clear(&big->initialized); +} + +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->dir != (broadcaster ? BT_ISO_CHAN_QOS_IN : BT_ISO_CHAN_QOS_OUT)) { + 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 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->interval, req->sdu_interval); + req->max_sdu = sys_cpu_to_le16(qos->sdu); + req->max_latency = sys_cpu_to_le16(qos->latency); + req->rtn = qos->rtn; + req->phy = qos->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)); + } + + 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; + } + + atomic_set(&big->initialized, true); + + *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_get(&big->initialized) == false || !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->dir == BT_ISO_CHAN_QOS_IN; + + 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_termimate + */ + 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 = &bigs[evt->big_handle]; + + 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); + 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_termimate(struct net_buf *buf) +{ + struct bt_hci_evt_le_big_terminate *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->big_handle); + struct bt_iso_big *big = &bigs[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; + uint16_t big_handle = sys_le16_to_cpu(evt->big_handle); + struct bt_iso_big *big = &bigs[big_handle]; + + 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; + uint16_t handle = sys_le16_to_cpu(evt->big_handle); + struct bt_iso_big *big = &bigs[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 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; + } + + 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); + } + + atomic_set(&big->initialized, true); + + *out_big = big; + + return 0; +} +#endif /* defined(CONFIG_BT_ISO_BROADCAST) */ diff --git a/subsys/bluetooth/host/iso_internal.h b/subsys/bluetooth/host/iso_internal.h index f98be6c0fc0..c1fa3e565b3 100644 --- a/subsys/bluetooth/host/iso_internal.h +++ b/subsys/bluetooth/host/iso_internal.h @@ -26,6 +26,19 @@ struct iso_data { uint32_t ts; }; +struct bt_iso_big { + /** Array of ISO channels to setup as BIS (the BIG). */ + struct bt_iso_chan **bis; + + /** Total number of BISes in the BIG. */ + uint8_t num_bis; + + /** The BIG handle */ + uint8_t handle; + + atomic_t initialized; +}; + #define iso(buf) ((struct iso_data *)net_buf_user_data(buf)) #if defined(CONFIG_BT_ISO_MAX_CHAN) @@ -47,6 +60,18 @@ void hci_le_cis_estabilished(struct net_buf *buf); /* Process CIS Request event */ void hci_le_cis_req(struct net_buf *buf); +/** Process BIG complete event */ +void hci_le_big_complete(struct net_buf *buf); + +/** Process BIG terminate event */ +void hci_le_big_termimate(struct net_buf *buf); + +/** Process BIG sync established event */ +void hci_le_big_sync_established(struct net_buf *buf); + +/** Process BIG sync lost event */ +void hci_le_big_sync_lost(struct net_buf *buf); + /* Notify ISO channels of a new connection */ int bt_iso_accept(struct bt_conn *conn); diff --git a/subsys/bluetooth/shell/bt.c b/subsys/bluetooth/shell/bt.c index bdf9a5d61b4..783cbce860e 100644 --- a/subsys/bluetooth/shell/bt.c +++ b/subsys/bluetooth/shell/bt.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include @@ -70,10 +70,8 @@ static struct bt_le_oob oob_remote; #define HCI_CMD_MAX_PARAM 65 #if defined(CONFIG_BT_EXT_ADV) -#if defined(CONFIG_BT_BROADCASTER) -static uint8_t selected_adv; +uint8_t selected_adv; struct bt_le_ext_adv *adv_sets[CONFIG_BT_EXT_ADV_MAX_ADV_SET]; -#endif /* CONFIG_BT_BROADCASTER */ #endif /* CONFIG_BT_EXT_ADV */ #if defined(CONFIG_BT_OBSERVER) || defined(CONFIG_BT_USER_PHY_UPDATE) @@ -464,7 +462,7 @@ static struct bt_le_ext_adv_cb adv_callbacks = { #if defined(CONFIG_BT_PER_ADV_SYNC) -static struct bt_le_per_adv_sync *per_adv_syncs[CONFIG_BT_PER_ADV_SYNC_MAX]; +struct bt_le_per_adv_sync *per_adv_syncs[CONFIG_BT_PER_ADV_SYNC_MAX]; static void per_adv_sync_sync_cb(struct bt_le_per_adv_sync *sync, struct bt_le_per_adv_sync_synced_info *info) @@ -528,10 +526,29 @@ static void per_adv_sync_recv_cb( info->rssi, info->cte_type, buf->len); } +static void per_adv_sync_biginfo_cb(struct bt_le_per_adv_sync *sync, + const struct bt_iso_biginfo *biginfo) +{ + char le_addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(biginfo->addr, le_addr, sizeof(le_addr)); + shell_print(ctx_shell, "PER_ADV_SYNC[%u]: [DEVICE]: %s, sid 0x%02x, num_bis %u, " + "nse 0x%02x, interval 0x%04x (%u ms), bn 0x%02x, pto 0x%02x, irc 0x%02x, " + "max_pdu 0x%04x, sdu_interval 0x%04x, max_sdu 0x%04x, phy %s, framing 0x%02x, " + "%sencrypted", + bt_le_per_adv_sync_get_index(sync), le_addr, biginfo->sid, biginfo->num_bis, + biginfo->sub_evt_count, biginfo->iso_interval, + BT_INTERVAL_TO_MS(biginfo->iso_interval), biginfo->burst_number, + biginfo->offset, biginfo->rep_count, biginfo->max_pdu, biginfo->sdu_interval, + biginfo->max_sdu, phy2str(biginfo->phy), biginfo->framing, + biginfo->encryption ? "" : "not "); +} + static struct bt_le_per_adv_sync_cb per_adv_sync_cb = { .synced = per_adv_sync_sync_cb, .term = per_adv_sync_terminated_cb, - .recv = per_adv_sync_recv_cb + .recv = per_adv_sync_recv_cb, + .biginfo = per_adv_sync_biginfo_cb, }; #endif /* CONFIG_BT_PER_ADV_SYNC */ diff --git a/subsys/bluetooth/shell/bt.h b/subsys/bluetooth/shell/bt.h index 30175d6363f..83da8fb2d77 100644 --- a/subsys/bluetooth/shell/bt.h +++ b/subsys/bluetooth/shell/bt.h @@ -16,6 +16,18 @@ extern const struct shell *ctx_shell; extern struct bt_conn *default_conn; +#if defined(CONFIG_BT_ISO) +extern struct bt_iso_chan iso_chan; +#endif /* CONFIG_BT_ISO */ + +#if defined(CONFIG_BT_EXT_ADV) +extern uint8_t selected_adv; +extern struct bt_le_ext_adv *adv_sets[CONFIG_BT_EXT_ADV_MAX_ADV_SET]; +#if defined(CONFIG_BT_PER_ADV_SYNC) +extern struct bt_le_per_adv_sync *per_adv_syncs[CONFIG_BT_PER_ADV_SYNC_MAX]; +#endif /* CONFIG_BT_PER_ADV_SYNC */ +#endif /* CONFIG_BT_EXT_ADV */ + void conn_addr_str(struct bt_conn *conn, char *addr, size_t len); #endif /* __BT_H */ diff --git a/subsys/bluetooth/shell/iso.c b/subsys/bluetooth/shell/iso.c index ffb1eb70185..6ec478d5246 100644 --- a/subsys/bluetooth/shell/iso.c +++ b/subsys/bluetooth/shell/iso.c @@ -49,7 +49,7 @@ static struct bt_iso_chan_qos iso_qos = { .sca = 0x07, }; -static struct bt_iso_chan iso_chan = { +struct bt_iso_chan iso_chan = { .ops = &iso_ops, .qos = &iso_qos, }; @@ -229,6 +229,193 @@ static int cmd_disconnect(const struct shell *shell, size_t argc, return 0; } +#if defined(CONFIG_BT_ISO_BROADCAST) +#define BIS_ISO_CHAN_COUNT 1 + +static struct bt_iso_chan_qos bis_iso_qos; + +static struct bt_iso_chan bis_iso_chan = { + .ops = &iso_ops, + .qos = &bis_iso_qos, +}; + +static struct bt_iso_chan *bis_channels[BIS_ISO_CHAN_COUNT] = { &bis_iso_chan }; + +static struct bt_iso_big *big; + +NET_BUF_POOL_FIXED_DEFINE(bis_tx_pool, BIS_ISO_CHAN_COUNT, DATA_MTU, NULL); + +static int cmd_broadcast(const struct shell *shell, size_t argc, char *argv[]) +{ + static uint8_t buf_data[DATA_MTU] = { [0 ... (DATA_MTU - 1)] = 0xff }; + int ret, len, count = 1; + struct net_buf *buf; + + if (argc > 1) { + count = strtoul(argv[1], NULL, 10); + } + + if (!bis_iso_chan.conn) { + shell_error(shell, "BIG not created"); + return -ENOEXEC; + } + + if (bis_iso_qos.dir != BT_ISO_CHAN_QOS_IN) { + shell_error(shell, "BIG not setup as broadcaster"); + return -ENOEXEC; + } + + len = MIN(iso_chan.qos->sdu, DATA_MTU - BT_ISO_CHAN_SEND_RESERVE); + + while (count--) { + for (int i = 0; i < BIS_ISO_CHAN_COUNT; i++) { + buf = net_buf_alloc(&tx_pool, K_FOREVER); + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + + net_buf_add_mem(buf, buf_data, len); + ret = bt_iso_chan_send(&bis_iso_chan, buf); + if (ret < 0) { + shell_print(shell, "[%i]: Unable to broadcast: %d", i, -ret); + net_buf_unref(buf); + return -ENOEXEC; + } + } + } + + shell_print(shell, "ISO broadcasting..."); + + return 0; +} + +static int cmd_big_create(const struct shell *shell, size_t argc, char *argv[]) +{ + int err; + struct bt_iso_big_create_param param; + struct bt_le_ext_adv *adv = adv_sets[selected_adv]; + uint8_t original_dir = bis_iso_qos.dir; + + if (!adv) { + shell_error(shell, "No (periodic) advertising set selected"); + return -ENOEXEC; + } + + bis_iso_qos.dir = BT_ISO_CHAN_QOS_IN; + /* TODO: Allow setting QOS from shell */ + bis_iso_qos.interval = 10000; /* us */ + bis_iso_qos.latency = 20; /* ms */ + bis_iso_qos.phy = BT_GAP_LE_PHY_2M; /* 2 MBit */ + bis_iso_qos.rtn = 2; + bis_iso_qos.sdu = CONFIG_BT_ISO_TX_MTU; + + param.bis_channels = bis_channels; + param.num_bis = BIS_ISO_CHAN_COUNT; + param.encryption = false; + + if (argc > 1) { + if (!strcmp(argv[1], "enc")) { + uint8_t bcode_len = hex2bin(argv[1], strlen(argv[1]), param.bcode, + sizeof(param.bcode)); + if (!bcode_len || bcode_len != sizeof(param.bcode)) { + shell_error(shell, "Invalid Broadcast Code Length"); + return -ENOEXEC; + } + param.encryption = true; + } else { + shell_help(shell); + return SHELL_CMD_HELP_PRINTED; + } + } + + err = bt_iso_big_create(adv, ¶m, &big); + if (err) { + bis_iso_qos.dir = original_dir; + shell_error(shell, "Unable to create BIG (err %d)", err); + return 0; + } + + shell_print(shell, "BIG created"); + + return 0; +} + +static int cmd_big_sync(const struct shell *shell, size_t argc, char *argv[]) +{ + int err; + /* TODO: Add support to select which PA sync to BIG sync to */ + struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[0]; + struct bt_iso_big_sync_param param; + uint8_t original_dir = bis_iso_qos.dir; + + if (!pa_sync) { + shell_error(shell, "No PA sync selected"); + return -ENOEXEC; + } + + bis_iso_qos.dir = BT_ISO_CHAN_QOS_OUT; + + param.bis_channels = bis_channels; + param.num_bis = BIS_ISO_CHAN_COUNT; + param.encryption = false; + param.bis_bitfield = strtoul(argv[1], NULL, 16); + param.mse = 0; + param.sync_timeout = 0xFF; + + for (int i = 2; i < argc; i++) { + if (!strcmp(argv[i], "mse")) { + param.mse = strtoul(argv[i], NULL, 16); + } else if (!strcmp(argv[i], "timeout")) { + param.sync_timeout = strtoul(argv[i], NULL, 16); + } else if (!strcmp(argv[i], "enc")) { + uint8_t bcode_len; + + i++; + if (i == argc) { + shell_help(shell); + return SHELL_CMD_HELP_PRINTED; + } + + bcode_len = hex2bin(argv[i], strlen(argv[i]), param.bcode, + sizeof(param.bcode)); + + if (!bcode_len || bcode_len != sizeof(param.bcode)) { + shell_error(shell, "Invalid Broadcast Code Length"); + return -ENOEXEC; + } + param.encryption = true; + } else { + shell_help(shell); + return SHELL_CMD_HELP_PRINTED; + } + } + + err = bt_iso_big_sync(pa_sync, ¶m, &big); + if (err) { + bis_iso_qos.dir = original_dir; + shell_error(shell, "Unable to sync to BIG (err %d)", err); + return 0; + } + + shell_print(shell, "BIG syncing"); + + return 0; +} + +static int cmd_big_term(const struct shell *shell, size_t argc, char *argv[]) +{ + int err; + + err = bt_iso_big_terminate(big); + if (err) { + shell_error(shell, "Unable to terminate BIG", err); + return 0; + } + + shell_print(shell, "BIG terminated"); + + return 0; +} +#endif /* CONFIG_BT_ISO_BROADCAST */ + SHELL_STATIC_SUBCMD_SET_CREATE(iso_cmds, SHELL_CMD_ARG(bind, NULL, "[dir] [interval] [packing] [framing] " "[latency] [sdu] [phy] [rtn]", cmd_bind, 1, 8), @@ -238,6 +425,14 @@ SHELL_STATIC_SUBCMD_SET_CREATE(iso_cmds, cmd_send, 1, 1), SHELL_CMD_ARG(disconnect, NULL, "Disconnect ISO Channel", cmd_disconnect, 1, 0), +#if defined(CONFIG_BT_ISO_BROADCAST) + SHELL_CMD_ARG(create-big, NULL, "Create a BIG as a broadcaster [enc ]", + cmd_big_create, 1, 2), + SHELL_CMD_ARG(sync-big, NULL, "Synchronize to a BIG as a receiver [mse] " + "[timeout] [enc ]", cmd_big_sync, 2, 4), + SHELL_CMD_ARG(term-big, NULL, "Terminate a BIG", cmd_big_term, 1, 0), + SHELL_CMD_ARG(broadcast, NULL, "Broadcast on ISO channels", cmd_broadcast, 1, 1), +#endif /* CONFIG_BT_ISO_BROADCAST */ SHELL_SUBCMD_SET_END ); diff --git a/tests/bluetooth/shell/prj.conf b/tests/bluetooth/shell/prj.conf index 028e8685eb0..32c7877ae60 100644 --- a/tests/bluetooth/shell/prj.conf +++ b/tests/bluetooth/shell/prj.conf @@ -49,3 +49,4 @@ CONFIG_BT_AUTO_PHY_UPDATE=y CONFIG_BT_AUDIO=y CONFIG_BT_AUDIO_UNICAST=y +CONFIG_BT_AUDIO_BROADCAST=y