@@ -198,6 +198,8 @@ struct bt_iso_bcast_qos {
__u8 sync_cte_type;
__u8 mse;
__u16 timeout;
+ __u8 dummy[2]; /* Dummy octets for padding compatibility with old BlueZ */
+ __u8 num_bis;
};
struct bt_iso_qos {
@@ -1,6 +1,7 @@
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2000-2001 Qualcomm Incorporated
+ Copyright 2023 NXP
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
@@ -2812,6 +2813,12 @@ struct hci_evt_le_create_big_complete {
__le16 bis_handle[];
} __packed;
+#define HCI_EVT_LE_TERM_BIG_COMPLETE 0x1c
+struct hci_evt_le_term_big_complete {
+ __u8 handle;
+ __u8 reason;
+} __packed;
+
#define HCI_EVT_LE_BIG_SYNC_ESTABILISHED 0x1d
struct hci_evt_le_big_sync_estabilished {
__u8 status;
@@ -583,6 +583,7 @@ struct hci_dev {
struct list_head pend_le_reports;
struct list_head blocked_keys;
struct list_head local_codecs;
+ struct list_head bigs;
struct hci_dev_stats stat;
@@ -973,7 +974,6 @@ enum {
HCI_CONN_NEW_LINK_KEY,
HCI_CONN_SCANNING,
HCI_CONN_AUTH_FAILURE,
- HCI_CONN_PER_ADV,
};
static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
@@ -1258,6 +1258,31 @@ static inline struct hci_conn *hci_conn_hash_lookup_big(struct hci_dev *hdev,
return NULL;
}
+static inline struct hci_conn *
+hci_conn_hash_lookup_big_state(struct hci_dev *hdev,
+ __u8 handle, __u16 state)
+{
+ struct hci_conn_hash *h = &hdev->conn_hash;
+ struct hci_conn *c;
+
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+ if (bacmp(&c->dst, BDADDR_ANY) || c->type != ISO_LINK ||
+ c->state != state)
+ continue;
+
+ if (handle == c->iso_qos.bcast.big) {
+ rcu_read_unlock();
+ return c;
+ }
+ }
+
+ rcu_read_unlock();
+
+ return NULL;
+}
+
static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev,
__u8 type, __u16 state)
{
@@ -1369,6 +1394,8 @@ void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active);
void hci_conn_failed(struct hci_conn *conn, u8 status);
+int hci_le_create_big(struct hci_conn *conn, struct bt_iso_qos *qos);
+
/*
* hci_conn_get() and hci_conn_put() are used to control the life-time of an
* "hci_conn" object. They do not guarantee that the hci_conn object is running,
@@ -1576,6 +1603,9 @@ struct hci_conn_params *hci_pend_le_action_lookup(struct list_head *list,
bdaddr_t *addr,
u8 addr_type);
+struct iso_big *hci_bigs_list_lookup(struct list_head *list,
+ __u8 handle);
+
void hci_uuids_clear(struct hci_dev *hdev);
void hci_link_keys_clear(struct hci_dev *hdev);
@@ -3,6 +3,7 @@
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2022 Intel Corporation
+ * Copyright 2023 NXP
*/
#ifndef __ISO_H
@@ -29,4 +30,17 @@ struct sockaddr_iso {
struct sockaddr_iso_bc iso_bc[];
};
+struct iso_bis {
+ __u16 handle;
+ bool assigned;
+};
+
+/* hdev BIG list entry */
+struct iso_big {
+ struct list_head list;
+ __u8 handle;
+ __u8 num_bis;
+ struct iso_bis bis[ISO_MAX_NUM_BIS];
+};
+
#endif /* __ISO_H */
@@ -35,6 +35,7 @@
#include <net/bluetooth/mgmt.h>
#include "hci_request.h"
+#include "hci_debugfs.h"
#include "smp.h"
#include "a2mp.h"
#include "eir.h"
@@ -826,13 +827,6 @@ static int terminate_big_sync(struct hci_dev *hdev, void *data)
hci_remove_ext_adv_instance_sync(hdev, d->bis, NULL);
- /* Check if ISO connection is a BIS and terminate BIG if there are
- * no other connections using it.
- */
- hci_conn_hash_list_state(hdev, find_bis, ISO_LINK, BT_CONNECTED, d);
- if (d->count)
- return 0;
-
return hci_le_terminate_big_sync(hdev, d->big,
HCI_ERROR_LOCAL_HOST_TERM);
}
@@ -914,11 +908,25 @@ static int hci_le_big_terminate(struct hci_dev *hdev, u8 big, u16 sync_handle)
static void bis_cleanup(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
+ struct iso_list_data data;
+ struct iso_big *big;
bt_dev_dbg(hdev, "conn %p", conn);
if (conn->role == HCI_ROLE_MASTER) {
- if (!test_and_clear_bit(HCI_CONN_PER_ADV, &conn->flags))
+ big = hci_bigs_list_lookup(&hdev->bigs, conn->iso_qos.bcast.big);
+
+ for (int i = 0; i < big->num_bis; i++)
+ if (!big->bis[i].assigned)
+ return;
+
+ data.count = 0;
+ data.big = conn->iso_qos.bcast.big;
+ data.bis = conn->iso_qos.bcast.bis;
+
+ hci_conn_hash_list_state(hdev, bis_list, ISO_LINK, BT_CONNECTED,
+ &data);
+ if (data.count)
return;
hci_le_terminate_big(hdev, conn->iso_qos.bcast.big,
@@ -1486,13 +1494,40 @@ static int qos_set_bis(struct hci_dev *hdev, struct bt_iso_qos *qos)
return 0;
}
+static int hci_match_bis_params(struct hci_dev *hdev, struct bt_iso_qos *qos,
+ __u8 base_len, __u8 *base, __u16 bis_state)
+{
+ struct hci_conn *conn;
+ __u8 eir[HCI_MAX_PER_AD_LENGTH];
+
+ if (base_len && base)
+ base_len = eir_append_service_data(eir, 0, 0x1851, base, base_len);
+
+ conn = hci_conn_hash_lookup_big_state(hdev, qos->bcast.big, bis_state);
+
+ if (memcmp(qos, &conn->iso_qos, sizeof(*qos)) ||
+ base_len != conn->le_per_adv_data_len ||
+ memcmp(conn->le_per_adv_data, eir, base_len))
+ return -EADDRINUSE;
+
+ return 0;
+}
+
/* This function requires the caller holds hdev->lock */
static struct hci_conn *hci_add_bis(struct hci_dev *hdev, bdaddr_t *dst,
- struct bt_iso_qos *qos)
+ struct bt_iso_qos *qos, __u8 base_len,
+ __u8 *base, bool *big_create,
+ bool *connected)
{
struct hci_conn *conn;
struct iso_list_data data;
int err;
+ int i;
+ struct iso_big *big;
+ __u16 handle;
+
+ *big_create = false;
+ *connected = false;
/* Let's make sure that le is enabled.*/
if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) {
@@ -1509,26 +1544,71 @@ static struct hci_conn *hci_add_bis(struct hci_dev *hdev, bdaddr_t *dst,
if (err)
return ERR_PTR(err);
- data.big = qos->bcast.big;
- data.bis = qos->bcast.bis;
- data.count = 0;
+ /* Check if BIG is already created */
+ big = hci_bigs_list_lookup(&hdev->bigs, qos->bcast.big);
+ if (!big) {
+ /* Check if there are other BISes bound to the same BIG */
+ data.big = qos->bcast.big;
+ data.bis = qos->bcast.bis;
+ data.count = 0;
- /* Check if there is already a matching BIG/BIS */
- hci_conn_hash_list_state(hdev, bis_list, ISO_LINK, BT_BOUND, &data);
- if (data.count)
- return ERR_PTR(-EADDRINUSE);
+ hci_conn_hash_list_state(hdev, bis_list, ISO_LINK, BT_BOUND, &data);
+ if (data.count) {
+ /* Check QoS and base parameters against the
+ * other BOUND connections
+ */
+ err = hci_match_bis_params(hdev, qos, base_len, base, BT_BOUND);
+ goto done;
+ }
- conn = hci_conn_hash_lookup_bis(hdev, dst, qos->bcast.big, qos->bcast.bis);
- if (conn)
- return ERR_PTR(-EADDRINUSE);
+ *big_create = true;
+ goto done;
+ }
+
+ conn = hci_conn_hash_lookup_big_state(hdev, qos->bcast.big, BT_CONNECTED);
+ if (!conn) {
+ /* BIG is in the process of terminating.
+ * Check BIS parameters against other BOUND connections if any,
+ * and mark BIS as bound for the BIG. BIG will be recreated
+ * after receiving the HCI_EVT_LE_TERM_BIG_COMPLETE event
+ */
+ err = hci_match_bis_params(hdev, qos, base_len, base, BT_BOUND);
+ goto done;
+ }
+
+ /* BIG is already created. Check that QoS and
+ * base parameters match the BIG
+ */
+ err = hci_match_bis_params(hdev, qos, base_len, base, BT_CONNECTED);
+ if (!err) {
+ /* Try to assign a bis handle */
+ for (i = 0; i < big->num_bis; i++) {
+ if (big->bis[i].assigned)
+ continue;
+
+ handle = big->bis[i].handle;
+ big->bis[i].assigned = true;
+ *connected = true;
+ break;
+ }
+
+ if (i == big->num_bis)
+ err = -EADDRINUSE;
+ }
+
+done:
+ if (err)
+ return ERR_PTR(err);
conn = hci_conn_add(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
if (!conn)
return ERR_PTR(-ENOMEM);
- set_bit(HCI_CONN_PER_ADV, &conn->flags);
conn->state = BT_CONNECT;
+ if (*connected)
+ conn->handle = handle;
+
hci_conn_hold(conn);
return conn;
}
@@ -1736,7 +1816,7 @@ static void cis_list(struct hci_conn *conn, void *data)
cis_add(d, &conn->iso_qos);
}
-static int hci_le_create_big(struct hci_conn *conn, struct bt_iso_qos *qos)
+int hci_le_create_big(struct hci_conn *conn, struct bt_iso_qos *qos)
{
struct hci_dev *hdev = conn->hdev;
struct hci_cp_le_create_big cp;
@@ -1745,7 +1825,7 @@ static int hci_le_create_big(struct hci_conn *conn, struct bt_iso_qos *qos)
cp.handle = qos->bcast.big;
cp.adv_handle = qos->bcast.bis;
- cp.num_bis = 0x01;
+ cp.num_bis = qos->bcast.num_bis;
hci_cpu_to_le24(qos->bcast.out.interval, cp.bis.sdu_interval);
cp.bis.sdu = cpu_to_le16(qos->bcast.out.sdu);
cp.bis.latency = cpu_to_le16(qos->bcast.out.latency);
@@ -2156,9 +2236,12 @@ struct hci_conn *hci_connect_bis(struct hci_dev *hdev, bdaddr_t *dst,
{
struct hci_conn *conn;
int err;
+ bool big_create = false;
+ bool connected = false;
/* We need hci_conn object using the BDADDR_ANY as dst */
- conn = hci_add_bis(hdev, dst, qos);
+ conn = hci_add_bis(hdev, dst, qos, base_len, base,
+ &big_create, &connected);
if (IS_ERR(conn))
return conn;
@@ -2171,18 +2254,27 @@ struct hci_conn *hci_connect_bis(struct hci_dev *hdev, bdaddr_t *dst,
conn->le_per_adv_data_len = base_len;
}
- /* Queue start periodic advertising and create BIG */
- err = hci_cmd_sync_queue(hdev, create_big_sync, conn,
- create_big_complete);
- if (err < 0) {
- hci_conn_drop(conn);
- return ERR_PTR(err);
+ if (big_create) {
+ /* Queue start periodic advertising and create BIG */
+ err = hci_cmd_sync_queue(hdev, create_big_sync, conn,
+ create_big_complete);
+ if (err < 0) {
+ hci_conn_drop(conn);
+ return ERR_PTR(err);
+ }
}
hci_iso_qos_setup(hdev, conn, &qos->bcast.out,
conn->le_tx_phy ? conn->le_tx_phy :
hdev->le_tx_def_phys);
+ if (connected) {
+ conn->state = BT_CONNECTED;
+ hci_debugfs_create_conn(conn);
+ hci_conn_add_sysfs(conn);
+ hci_iso_setup_path(conn);
+ }
+
return conn;
}
@@ -2,6 +2,7 @@
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2000-2001 Qualcomm Incorporated
Copyright (C) 2011 ProFUSION Embedded Systems
+ Copyright 2023 NXP
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
@@ -38,6 +39,7 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/l2cap.h>
+#include <net/bluetooth/iso.h>
#include <net/bluetooth/mgmt.h>
#include "hci_request.h"
@@ -2264,6 +2266,20 @@ struct hci_conn_params *hci_pend_le_action_lookup(struct list_head *list,
return NULL;
}
+/* This function requires the caller holds hdev->lock */
+struct iso_big *hci_bigs_list_lookup(struct list_head *list,
+ __u8 handle)
+{
+ struct iso_big *big;
+
+ list_for_each_entry(big, list, list) {
+ if (big->handle == handle)
+ return big;
+ }
+
+ return NULL;
+}
+
/* This function requires the caller holds hdev->lock */
struct hci_conn_params *hci_conn_params_add(struct hci_dev *hdev,
bdaddr_t *addr, u8 addr_type)
@@ -2525,6 +2541,8 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
INIT_LIST_HEAD(&hdev->monitored_devices);
INIT_LIST_HEAD(&hdev->local_codecs);
+ INIT_LIST_HEAD(&hdev->bigs);
+
INIT_WORK(&hdev->rx_work, hci_rx_work);
INIT_WORK(&hdev->cmd_work, hci_cmd_work);
INIT_WORK(&hdev->tx_work, hci_tx_work);
@@ -30,6 +30,7 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/mgmt.h>
+#include <net/bluetooth/iso.h>
#include "hci_request.h"
#include "hci_debugfs.h"
@@ -3903,6 +3904,11 @@ static void hci_cs_le_create_big(struct hci_dev *hdev, u8 status)
bt_dev_dbg(hdev, "status 0x%2.2x", status);
}
+static void hci_cs_le_term_big(struct hci_dev *hdev, u8 status)
+{
+ bt_dev_dbg(hdev, "status 0x%2.2x", status);
+}
+
static u8 hci_cc_set_per_adv_param(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
@@ -4275,6 +4281,7 @@ static const struct hci_cs {
HCI_CS(HCI_OP_LE_EXT_CREATE_CONN, hci_cs_le_ext_create_conn),
HCI_CS(HCI_OP_LE_CREATE_CIS, hci_cs_le_create_cis),
HCI_CS(HCI_OP_LE_CREATE_BIG, hci_cs_le_create_big),
+ HCI_CS(HCI_OP_LE_TERM_BIG, hci_cs_le_term_big),
};
static void hci_cmd_status_evt(struct hci_dev *hdev, void *data,
@@ -6910,6 +6917,9 @@ static void hci_le_create_big_complete_evt(struct hci_dev *hdev, void *data,
{
struct hci_evt_le_create_big_complete *ev = data;
struct hci_conn *conn;
+ struct iso_big *big;
+ struct hci_conn_hash *h = &hdev->conn_hash;
+ __u8 bis_idx = 0;
BT_DBG("%s status 0x%2.2x", hdev->name, ev->status);
@@ -6919,30 +6929,78 @@ static void hci_le_create_big_complete_evt(struct hci_dev *hdev, void *data,
hci_dev_lock(hdev);
- conn = hci_conn_hash_lookup_big(hdev, ev->handle);
- if (!conn)
- goto unlock;
+ if (!ev->status) {
+ /* Add the created BIG to the list */
+ big = kzalloc(sizeof(*big), GFP_KERNEL);
+ if (!big)
+ return;
- if (conn->type != ISO_LINK) {
- bt_dev_err(hdev,
- "Invalid connection link type handle 0x%2.2x",
- ev->handle);
- goto unlock;
+ big->handle = ev->handle;
+ big->num_bis = ev->num_bis;
+
+ for (int i = 0; i < ev->num_bis; i++) {
+ big->bis[i].handle = __le16_to_cpu(ev->bis_handle[i]);
+ big->bis[i].assigned = false;
+ }
+
+ list_add(&big->list, &hdev->bigs);
}
- if (ev->num_bis)
- conn->handle = __le16_to_cpu(ev->bis_handle[0]);
+ rcu_read_lock();
- if (!ev->status) {
- conn->state = BT_CONNECTED;
- hci_debugfs_create_conn(conn);
- hci_conn_add_sysfs(conn);
- hci_iso_setup_path(conn);
- goto unlock;
+ /* Connect all BISes that are bound to the BIG */
+ list_for_each_entry_rcu(conn, &h->list, list) {
+ if (bacmp(&conn->dst, BDADDR_ANY) || conn->type != ISO_LINK ||
+ conn->state != BT_BOUND ||
+ conn->iso_qos.bcast.big != ev->handle)
+ continue;
+
+ if (ev->status) {
+ hci_connect_cfm(conn, ev->status);
+ hci_conn_del(conn);
+ }
+
+ if (big->num_bis > bis_idx) {
+ conn->handle = __le16_to_cpu(big->bis[bis_idx].handle);
+ big->bis[bis_idx].assigned = true;
+ bis_idx++;
+
+ conn->state = BT_CONNECTED;
+ hci_debugfs_create_conn(conn);
+ hci_conn_add_sysfs(conn);
+ hci_iso_setup_path(conn);
+ continue;
+ }
}
- hci_connect_cfm(conn, ev->status);
- hci_conn_del(conn);
+ rcu_read_unlock();
+ hci_dev_unlock(hdev);
+}
+
+static void hci_le_term_big_complete_evt(struct hci_dev *hdev, void *data,
+ struct sk_buff *skb)
+{
+ struct hci_evt_le_term_big_complete *ev = data;
+ struct iso_big *big;
+ struct hci_conn *conn;
+
+ BT_DBG("%s reason 0x%2.2x", hdev->name, ev->reason);
+
+ hci_dev_lock(hdev);
+
+ big = hci_bigs_list_lookup(&hdev->bigs, ev->handle);
+
+ if (big) {
+ list_del(&big->list);
+ kfree(big);
+ }
+
+ /* If there are any bound connections to the BIG, recreate it */
+ conn = hci_conn_hash_lookup_big_state(hdev, ev->handle, BT_BOUND);
+ if (!conn)
+ goto unlock;
+
+ hci_le_create_big(conn, &conn->iso_qos);
unlock:
hci_dev_unlock(hdev);
@@ -7089,6 +7147,10 @@ static const struct hci_le_ev {
hci_le_create_big_complete_evt,
sizeof(struct hci_evt_le_create_big_complete),
HCI_MAX_EVENT_SIZE),
+ /* [0x1c = HCI_EVT_LE_TERM_BIG_COMPLETE] */
+ HCI_LE_EV(HCI_EVT_LE_TERM_BIG_COMPLETE,
+ hci_le_term_big_complete_evt,
+ sizeof(struct hci_evt_le_term_big_complete)),
/* [0x1d = HCI_EV_LE_BIG_SYNC_ESTABILISHED] */
HCI_LE_EV_VL(HCI_EVT_LE_BIG_SYNC_ESTABILISHED,
hci_le_big_sync_established_evt,
@@ -717,6 +717,7 @@ static struct bt_iso_qos default_qos = {
.sync_cte_type = 0x00,
.mse = 0x00,
.timeout = 0x4000,
+ .num_bis = 0x01,
},
};
@@ -1249,6 +1250,9 @@ static bool check_bcast_qos(struct bt_iso_qos *qos)
if (qos->bcast.timeout < 0x000a || qos->bcast.timeout > 0x4000)
return false;
+ if (qos->bcast.num_bis < 0x01 || qos->bcast.num_bis > ISO_MAX_NUM_BIS)
+ return false;
+
return true;
}
It is required for some configurations to have multiple BISes as part of the same BIG, which is now covered by iso-tester in the following test case: ISO Broadcaster AC 13 - Success Signed-off-by: Iulia Tanasescu <iulia.tanasescu@nxp.com> --- include/net/bluetooth/bluetooth.h | 2 + include/net/bluetooth/hci.h | 7 ++ include/net/bluetooth/hci_core.h | 32 ++++++- include/net/bluetooth/iso.h | 14 +++ net/bluetooth/hci_conn.c | 150 ++++++++++++++++++++++++------ net/bluetooth/hci_core.c | 18 ++++ net/bluetooth/hci_event.c | 98 +++++++++++++++---- net/bluetooth/iso.c | 4 + 8 files changed, 277 insertions(+), 48 deletions(-)