Bluetooth: hci_conn: Add support for linking multiple hcon
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Tue, 11 Apr 2023 23:02:22 +0000 (16:02 -0700)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 24 Apr 2023 05:03:13 +0000 (22:03 -0700)
Since it is required for some configurations to have multiple CIS with
the same peer which is now covered by iso-tester in the following test
cases:

    ISO AC 6(i) - Success
    ISO AC 7(i) - Success
    ISO AC 8(i) - Success
    ISO AC 9(i) - Success
    ISO AC 11(i) - Success

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
include/net/bluetooth/hci_core.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_event.c
net/bluetooth/iso.c

index 827e671..4fe1e71 100644 (file)
@@ -770,7 +770,10 @@ struct hci_conn {
        void            *iso_data;
        struct amp_mgr  *amp_mgr;
 
-       struct hci_conn *link;
+       struct list_head link_list;
+       struct hci_conn *parent;
+       struct hci_link *link;
+
        struct bt_codec codec;
 
        void (*connect_cfm_cb)  (struct hci_conn *conn, u8 status);
@@ -780,6 +783,11 @@ struct hci_conn {
        void (*cleanup)(struct hci_conn *conn);
 };
 
+struct hci_link {
+       struct list_head list;
+       struct hci_conn *conn;
+};
+
 struct hci_chan {
        struct list_head list;
        __u16 handle;
@@ -1383,12 +1391,14 @@ static inline void hci_conn_put(struct hci_conn *conn)
        put_device(&conn->dev);
 }
 
-static inline void hci_conn_hold(struct hci_conn *conn)
+static inline struct hci_conn *hci_conn_hold(struct hci_conn *conn)
 {
        BT_DBG("hcon %p orig refcnt %d", conn, atomic_read(&conn->refcnt));
 
        atomic_inc(&conn->refcnt);
        cancel_delayed_work(&conn->disc_work);
+
+       return conn;
 }
 
 static inline void hci_conn_drop(struct hci_conn *conn)
index 01e0b72..d8466ab 100644 (file)
@@ -330,8 +330,11 @@ static void hci_add_sco(struct hci_conn *conn, __u16 handle)
 static bool find_next_esco_param(struct hci_conn *conn,
                                 const struct sco_param *esco_param, int size)
 {
+       if (!conn->parent)
+               return false;
+
        for (; conn->attempt <= size; conn->attempt++) {
-               if (lmp_esco_2m_capable(conn->link) ||
+               if (lmp_esco_2m_capable(conn->parent) ||
                    (esco_param[conn->attempt - 1].pkt_type & ESCO_2EV3))
                        break;
                BT_DBG("hcon %p skipped attempt %d, eSCO 2M not supported",
@@ -461,7 +464,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data)
                break;
 
        case BT_CODEC_CVSD:
-               if (lmp_esco_capable(conn->link)) {
+               if (conn->parent && lmp_esco_capable(conn->parent)) {
                        if (!find_next_esco_param(conn, esco_param_cvsd,
                                                  ARRAY_SIZE(esco_param_cvsd)))
                                return -EINVAL;
@@ -531,7 +534,7 @@ static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle)
                param = &esco_param_msbc[conn->attempt - 1];
                break;
        case SCO_AIRMODE_CVSD:
-               if (lmp_esco_capable(conn->link)) {
+               if (conn->parent && lmp_esco_capable(conn->parent)) {
                        if (!find_next_esco_param(conn, esco_param_cvsd,
                                                  ARRAY_SIZE(esco_param_cvsd)))
                                return false;
@@ -637,21 +640,22 @@ void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
 /* Device _must_ be locked */
 void hci_sco_setup(struct hci_conn *conn, __u8 status)
 {
-       struct hci_conn *sco = conn->link;
+       struct hci_link *link;
 
-       if (!sco)
+       link = list_first_entry_or_null(&conn->link_list, struct hci_link, list);
+       if (!link || !link->conn)
                return;
 
        BT_DBG("hcon %p", conn);
 
        if (!status) {
                if (lmp_esco_capable(conn->hdev))
-                       hci_setup_sync(sco, conn->handle);
+                       hci_setup_sync(link->conn, conn->handle);
                else
-                       hci_add_sco(sco, conn->handle);
+                       hci_add_sco(link->conn, conn->handle);
        } else {
-               hci_connect_cfm(sco, status);
-               hci_conn_del(sco);
+               hci_connect_cfm(link->conn, status);
+               hci_conn_del(link->conn);
        }
 }
 
@@ -1042,6 +1046,7 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
        skb_queue_head_init(&conn->data_q);
 
        INIT_LIST_HEAD(&conn->chan_list);
+       INIT_LIST_HEAD(&conn->link_list);
 
        INIT_DELAYED_WORK(&conn->disc_work, hci_conn_timeout);
        INIT_DELAYED_WORK(&conn->auto_accept_work, hci_conn_auto_accept);
@@ -1069,15 +1074,39 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
        return conn;
 }
 
-static bool hci_conn_unlink(struct hci_conn *conn)
+static void hci_conn_unlink(struct hci_conn *conn)
 {
+       struct hci_dev *hdev = conn->hdev;
+
+       bt_dev_dbg(hdev, "hcon %p", conn);
+
+       if (!conn->parent) {
+               struct hci_link *link, *t;
+
+               list_for_each_entry_safe(link, t, &conn->link_list, list)
+                       hci_conn_unlink(link->conn);
+
+               return;
+       }
+
        if (!conn->link)
-               return false;
+               return;
+
+       hci_conn_put(conn->parent);
+       conn->parent = NULL;
 
-       conn->link->link = NULL;
+       list_del_rcu(&conn->link->list);
+       synchronize_rcu();
+
+       kfree(conn->link);
        conn->link = NULL;
 
-       return true;
+       /* Due to race, SCO connection might be not established
+        * yet at this point. Delete it now, otherwise it is
+        * possible for it to be stuck and can't be deleted.
+        */
+       if (conn->handle == HCI_CONN_HANDLE_UNSET)
+               hci_conn_del(conn);
 }
 
 int hci_conn_del(struct hci_conn *conn)
@@ -1091,18 +1120,7 @@ int hci_conn_del(struct hci_conn *conn)
        cancel_delayed_work_sync(&conn->idle_work);
 
        if (conn->type == ACL_LINK) {
-               struct hci_conn *link = conn->link;
-
-               if (link) {
-                       hci_conn_unlink(conn);
-                       /* Due to race, SCO connection might be not established
-                        * yet at this point. Delete it now, otherwise it is
-                        * possible for it to be stuck and can't be deleted.
-                        */
-                       if (link->handle == HCI_CONN_HANDLE_UNSET)
-                               hci_conn_del(link);
-               }
-
+               hci_conn_unlink(conn);
                /* Unacked frames */
                hdev->acl_cnt += conn->sent;
        } else if (conn->type == LE_LINK) {
@@ -1113,7 +1131,7 @@ int hci_conn_del(struct hci_conn *conn)
                else
                        hdev->acl_cnt += conn->sent;
        } else {
-               struct hci_conn *acl = conn->link;
+               struct hci_conn *acl = conn->parent;
 
                if (acl) {
                        hci_conn_unlink(conn);
@@ -1600,11 +1618,40 @@ struct hci_conn *hci_connect_acl(struct hci_dev *hdev, bdaddr_t *dst,
        return acl;
 }
 
+static struct hci_link *hci_conn_link(struct hci_conn *parent,
+                                     struct hci_conn *conn)
+{
+       struct hci_dev *hdev = parent->hdev;
+       struct hci_link *link;
+
+       bt_dev_dbg(hdev, "parent %p hcon %p", parent, conn);
+
+       if (conn->link)
+               return conn->link;
+
+       if (conn->parent)
+               return NULL;
+
+       link = kzalloc(sizeof(*link), GFP_KERNEL);
+       if (!link)
+               return NULL;
+
+       link->conn = hci_conn_hold(conn);
+       conn->link = link;
+       conn->parent = hci_conn_get(parent);
+
+       /* Use list_add_tail_rcu append to the list */
+       list_add_tail_rcu(&link->list, &parent->link_list);
+
+       return link;
+}
+
 struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
                                 __u16 setting, struct bt_codec *codec)
 {
        struct hci_conn *acl;
        struct hci_conn *sco;
+       struct hci_link *link;
 
        acl = hci_connect_acl(hdev, dst, BT_SECURITY_LOW, HCI_AT_NO_BONDING,
                              CONN_REASON_SCO_CONNECT);
@@ -1620,10 +1667,12 @@ struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
                }
        }
 
-       acl->link = sco;
-       sco->link = acl;
-
-       hci_conn_hold(sco);
+       link = hci_conn_link(acl, sco);
+       if (!link) {
+               hci_conn_drop(acl);
+               hci_conn_drop(sco);
+               return NULL;
+       }
 
        sco->setting = setting;
        sco->codec = *codec;
@@ -1890,7 +1939,7 @@ static int hci_create_cis_sync(struct hci_dev *hdev, void *data)
        u8 cig;
 
        memset(&cmd, 0, sizeof(cmd));
-       cmd.cis[0].acl_handle = cpu_to_le16(conn->link->handle);
+       cmd.cis[0].acl_handle = cpu_to_le16(conn->parent->handle);
        cmd.cis[0].cis_handle = cpu_to_le16(conn->handle);
        cmd.cp.num_cis++;
        cig = conn->iso_qos.ucast.cig;
@@ -1903,11 +1952,12 @@ static int hci_create_cis_sync(struct hci_dev *hdev, void *data)
                struct hci_cis *cis = &cmd.cis[cmd.cp.num_cis];
 
                if (conn == data || conn->type != ISO_LINK ||
-                   conn->state == BT_CONNECTED || conn->iso_qos.ucast.cig != cig)
+                   conn->state == BT_CONNECTED ||
+                   conn->iso_qos.ucast.cig != cig)
                        continue;
 
                /* Check if all CIS(s) belonging to a CIG are ready */
-               if (!conn->link || conn->link->state != BT_CONNECTED ||
+               if (!conn->parent || conn->parent->state != BT_CONNECTED ||
                    conn->state != BT_CONNECT) {
                        cmd.cp.num_cis = 0;
                        break;
@@ -1924,7 +1974,7 @@ static int hci_create_cis_sync(struct hci_dev *hdev, void *data)
                 * command have been generated, the Controller shall return the
                 * error code Command Disallowed (0x0C).
                 */
-               cis->acl_handle = cpu_to_le16(conn->link->handle);
+               cis->acl_handle = cpu_to_le16(conn->parent->handle);
                cis->cis_handle = cpu_to_le16(conn->handle);
                cmd.cp.num_cis++;
        }
@@ -1943,15 +1993,33 @@ static int hci_create_cis_sync(struct hci_dev *hdev, void *data)
 int hci_le_create_cis(struct hci_conn *conn)
 {
        struct hci_conn *cis;
+       struct hci_link *link, *t;
        struct hci_dev *hdev = conn->hdev;
        int err;
 
+       bt_dev_dbg(hdev, "hcon %p", conn);
+
        switch (conn->type) {
        case LE_LINK:
-               if (!conn->link || conn->state != BT_CONNECTED)
+               if (conn->state != BT_CONNECTED || list_empty(&conn->link_list))
                        return -EINVAL;
-               cis = conn->link;
-               break;
+
+               cis = NULL;
+
+               /* hci_conn_link uses list_add_tail_rcu so the list is in
+                * the same order as the connections are requested.
+                */
+               list_for_each_entry_safe(link, t, &conn->link_list, list) {
+                       if (link->conn->state == BT_BOUND) {
+                               err = hci_le_create_cis(link->conn);
+                               if (err)
+                                       return err;
+
+                               cis = link->conn;
+                       }
+               }
+
+               return cis ? 0 : -EINVAL;
        case ISO_LINK:
                cis = conn;
                break;
@@ -2172,6 +2240,7 @@ struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst,
 {
        struct hci_conn *le;
        struct hci_conn *cis;
+       struct hci_link *link;
 
        if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
                le = hci_connect_le(hdev, dst, dst_type, false,
@@ -2197,16 +2266,18 @@ struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst,
                return cis;
        }
 
-       le->link = cis;
-       cis->link = le;
-
-       hci_conn_hold(cis);
+       link = hci_conn_link(le, cis);
+       if (!link) {
+               hci_conn_drop(le);
+               hci_conn_drop(cis);
+               return NULL;
+       }
 
        /* If LE is already connected and CIS handle is already set proceed to
         * Create CIS immediately.
         */
        if (le->state == BT_CONNECTED && cis->handle != HCI_CONN_HANDLE_UNSET)
-               hci_le_create_cis(le);
+               hci_le_create_cis(cis);
 
        return cis;
 }
index 0e0a93c..d00ef6e 100644 (file)
@@ -2345,7 +2345,8 @@ static void hci_cs_create_conn(struct hci_dev *hdev, __u8 status)
 static void hci_cs_add_sco(struct hci_dev *hdev, __u8 status)
 {
        struct hci_cp_add_sco *cp;
-       struct hci_conn *acl, *sco;
+       struct hci_conn *acl;
+       struct hci_link *link;
        __u16 handle;
 
        bt_dev_dbg(hdev, "status 0x%2.2x", status);
@@ -2365,12 +2366,13 @@ static void hci_cs_add_sco(struct hci_dev *hdev, __u8 status)
 
        acl = hci_conn_hash_lookup_handle(hdev, handle);
        if (acl) {
-               sco = acl->link;
-               if (sco) {
-                       sco->state = BT_CLOSED;
+               link = list_first_entry_or_null(&acl->link_list,
+                                               struct hci_link, list);
+               if (link && link->conn) {
+                       link->conn->state = BT_CLOSED;
 
-                       hci_connect_cfm(sco, status);
-                       hci_conn_del(sco);
+                       hci_connect_cfm(link->conn, status);
+                       hci_conn_del(link->conn);
                }
        }
 
@@ -2637,74 +2639,61 @@ static void hci_cs_read_remote_ext_features(struct hci_dev *hdev, __u8 status)
        hci_dev_unlock(hdev);
 }
 
-static void hci_cs_setup_sync_conn(struct hci_dev *hdev, __u8 status)
+static void hci_setup_sync_conn_status(struct hci_dev *hdev, __u16 handle,
+                                      __u8 status)
 {
-       struct hci_cp_setup_sync_conn *cp;
-       struct hci_conn *acl, *sco;
-       __u16 handle;
-
-       bt_dev_dbg(hdev, "status 0x%2.2x", status);
-
-       if (!status)
-               return;
-
-       cp = hci_sent_cmd_data(hdev, HCI_OP_SETUP_SYNC_CONN);
-       if (!cp)
-               return;
+       struct hci_conn *acl;
+       struct hci_link *link;
 
-       handle = __le16_to_cpu(cp->handle);
-
-       bt_dev_dbg(hdev, "handle 0x%4.4x", handle);
+       bt_dev_dbg(hdev, "handle 0x%4.4x status 0x%2.2x", handle, status);
 
        hci_dev_lock(hdev);
 
        acl = hci_conn_hash_lookup_handle(hdev, handle);
        if (acl) {
-               sco = acl->link;
-               if (sco) {
-                       sco->state = BT_CLOSED;
+               link = list_first_entry_or_null(&acl->link_list,
+                                               struct hci_link, list);
+               if (link && link->conn) {
+                       link->conn->state = BT_CLOSED;
 
-                       hci_connect_cfm(sco, status);
-                       hci_conn_del(sco);
+                       hci_connect_cfm(link->conn, status);
+                       hci_conn_del(link->conn);
                }
        }
 
        hci_dev_unlock(hdev);
 }
 
-static void hci_cs_enhanced_setup_sync_conn(struct hci_dev *hdev, __u8 status)
+static void hci_cs_setup_sync_conn(struct hci_dev *hdev, __u8 status)
 {
-       struct hci_cp_enhanced_setup_sync_conn *cp;
-       struct hci_conn *acl, *sco;
-       __u16 handle;
+       struct hci_cp_setup_sync_conn *cp;
 
        bt_dev_dbg(hdev, "status 0x%2.2x", status);
 
        if (!status)
                return;
 
-       cp = hci_sent_cmd_data(hdev, HCI_OP_ENHANCED_SETUP_SYNC_CONN);
+       cp = hci_sent_cmd_data(hdev, HCI_OP_SETUP_SYNC_CONN);
        if (!cp)
                return;
 
-       handle = __le16_to_cpu(cp->handle);
+       hci_setup_sync_conn_status(hdev, __le16_to_cpu(cp->handle), status);
+}
 
-       bt_dev_dbg(hdev, "handle 0x%4.4x", handle);
+static void hci_cs_enhanced_setup_sync_conn(struct hci_dev *hdev, __u8 status)
+{
+       struct hci_cp_enhanced_setup_sync_conn *cp;
 
-       hci_dev_lock(hdev);
+       bt_dev_dbg(hdev, "status 0x%2.2x", status);
 
-       acl = hci_conn_hash_lookup_handle(hdev, handle);
-       if (acl) {
-               sco = acl->link;
-               if (sco) {
-                       sco->state = BT_CLOSED;
+       if (!status)
+               return;
 
-                       hci_connect_cfm(sco, status);
-                       hci_conn_del(sco);
-               }
-       }
+       cp = hci_sent_cmd_data(hdev, HCI_OP_ENHANCED_SETUP_SYNC_CONN);
+       if (!cp)
+               return;
 
-       hci_dev_unlock(hdev);
+       hci_setup_sync_conn_status(hdev, __le16_to_cpu(cp->handle), status);
 }
 
 static void hci_cs_sniff_mode(struct hci_dev *hdev, __u8 status)
@@ -3834,19 +3823,20 @@ static u8 hci_cc_le_set_cig_params(struct hci_dev *hdev, void *data,
        rcu_read_lock();
 
        list_for_each_entry_rcu(conn, &hdev->conn_hash.list, list) {
-               if (conn->type != ISO_LINK || conn->iso_qos.ucast.cig != rp->cig_id ||
+               if (conn->type != ISO_LINK ||
+                   conn->iso_qos.ucast.cig != rp->cig_id ||
                    conn->state == BT_CONNECTED)
                        continue;
 
                conn->handle = __le16_to_cpu(rp->handle[i++]);
 
-               bt_dev_dbg(hdev, "%p handle 0x%4.4x link %p", conn,
-                          conn->handle, conn->link);
+               bt_dev_dbg(hdev, "%p handle 0x%4.4x parent %p", conn,
+                          conn->handle, conn->parent);
 
                /* Create CIS if LE is already connected */
-               if (conn->link && conn->link->state == BT_CONNECTED) {
+               if (conn->parent && conn->parent->state == BT_CONNECTED) {
                        rcu_read_unlock();
-                       hci_le_create_cis(conn->link);
+                       hci_le_create_cis(conn);
                        rcu_read_lock();
                }
 
@@ -5031,7 +5021,7 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,
                if (conn->out) {
                        conn->pkt_type = (hdev->esco_type & SCO_ESCO_MASK) |
                                        (hdev->esco_type & EDR_ESCO_MASK);
-                       if (hci_setup_sync(conn, conn->link->handle))
+                       if (hci_setup_sync(conn, conn->parent->handle))
                                goto unlock;
                }
                fallthrough;
index 74117df..34d55a8 100644 (file)
@@ -1657,8 +1657,12 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status)
 
                /* Check if LE link has failed */
                if (status) {
-                       if (hcon->link)
-                               iso_conn_del(hcon->link, bt_to_errno(status));
+                       struct hci_link *link, *t;
+
+                       list_for_each_entry_safe(link, t, &hcon->link_list,
+                                                list)
+                               iso_conn_del(link->conn, bt_to_errno(status));
+
                        return;
                }