]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
Bluetooth: ISO: Add support to bind to trigger PAST
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Fri, 5 Sep 2025 15:34:44 +0000 (11:34 -0400)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 1 Dec 2025 21:00:04 +0000 (16:00 -0500)
This makes it possible to bind to a different destination address
after being connected (BT_CONNECTED, BT_CONNECT2) which then triggers
PAST Sender proceedure to transfer the PA Sync to the destination
address.

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

index 8c22354448083836c72bf093660f0b40cf190e58..1f74722f3f4da38d64ae93e32e705b1560dce290 100644 (file)
@@ -1602,6 +1602,7 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst,
 struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid,
                              struct bt_iso_qos *qos,
                              __u8 base_len, __u8 *base, u16 timeout);
+int hci_past_bis(struct hci_conn *conn, bdaddr_t *dst, __u8 dst_type);
 struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst,
                                 __u8 dst_type, struct bt_iso_qos *qos,
                                 u16 timeout);
index e352a4e0ef8d76ff7b2762e0556c634927c4d58b..3133f40fa9f9d58d079615024dde660e9de592d1 100644 (file)
@@ -188,3 +188,4 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
 
 int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn);
 int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn);
+int hci_past_sync(struct hci_conn *conn, struct hci_conn *le);
index 6fc0692abf0576889b4f28b2522b6582e8ba331b..4f9dc1435cccb4f4cab91abb5d96c25dba1c7503 100644 (file)
@@ -2245,6 +2245,18 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid,
        return conn;
 }
 
+int hci_past_bis(struct hci_conn *conn, bdaddr_t *dst, __u8 dst_type)
+{
+       struct hci_conn *le;
+
+       /* Lookup existing LE connection to rebind to */
+       le = hci_conn_hash_lookup_le(conn->hdev, dst, dst_type);
+       if (!le)
+               return -EINVAL;
+
+       return hci_past_sync(conn, le);
+}
+
 static void bis_mark_per_adv(struct hci_conn *conn, void *data)
 {
        struct iso_list_data *d = data;
index ba6f13e9235cdf10345aeb3e3133ccf7bb8e8586..65f2701beb497de9bc692a7a71ede3fc08e9bbbb 100644 (file)
@@ -7228,3 +7228,95 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn)
        return hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn,
                                       create_big_complete);
 }
+
+struct past_data {
+       struct hci_conn *conn;
+       struct hci_conn *le;
+};
+
+static void past_complete(struct hci_dev *hdev, void *data, int err)
+{
+       struct past_data *past = data;
+
+       bt_dev_dbg(hdev, "err %d", err);
+
+       kfree(past);
+}
+
+static int hci_le_past_set_info_sync(struct hci_dev *hdev, void *data)
+{
+       struct past_data *past = data;
+       struct hci_cp_le_past_set_info cp;
+
+       hci_dev_lock(hdev);
+
+       if (!hci_conn_valid(hdev, past->conn) ||
+           !hci_conn_valid(hdev, past->le)) {
+               hci_dev_unlock(hdev);
+               return -ECANCELED;
+       }
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = cpu_to_le16(past->le->handle);
+       cp.adv_handle = past->conn->iso_qos.bcast.bis;
+
+       hci_dev_unlock(hdev);
+
+       return __hci_cmd_sync_status(hdev, HCI_OP_LE_PAST_SET_INFO,
+                                    sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+static int hci_le_past_sync(struct hci_dev *hdev, void *data)
+{
+       struct past_data *past = data;
+       struct hci_cp_le_past cp;
+
+       hci_dev_lock(hdev);
+
+       if (!hci_conn_valid(hdev, past->conn) ||
+           !hci_conn_valid(hdev, past->le)) {
+               hci_dev_unlock(hdev);
+               return -ECANCELED;
+       }
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = cpu_to_le16(past->le->handle);
+       cp.sync_handle = cpu_to_le16(past->conn->sync_handle);
+
+       hci_dev_unlock(hdev);
+
+       return __hci_cmd_sync_status(hdev, HCI_OP_LE_PAST,
+                                    sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+int hci_past_sync(struct hci_conn *conn, struct hci_conn *le)
+{
+       struct past_data *data;
+       int err;
+
+       if (conn->type != BIS_LINK && conn->type != PA_LINK)
+               return -EINVAL;
+
+       if (!past_sender_capable(conn->hdev))
+               return -EOPNOTSUPP;
+
+       data = kmalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->conn = conn;
+       data->le = le;
+
+       if (conn->role == HCI_ROLE_MASTER)
+               err = hci_cmd_sync_queue_once(conn->hdev,
+                                             hci_le_past_set_info_sync, data,
+                                             past_complete);
+       else
+               err = hci_cmd_sync_queue_once(conn->hdev, hci_le_past_sync,
+                                             data, past_complete);
+
+       if (err)
+               kfree(data);
+
+       return err;
+}
index d0a79f601e8d3fa227a8294edf3693e45d993c91..85fa9363b89754cce1f7d8c36aa164212dfd2316 100644 (file)
@@ -987,20 +987,14 @@ static int iso_sock_bind_bc(struct socket *sock, struct sockaddr_unsized *addr,
        return 0;
 }
 
-static int iso_sock_bind_pa_sk(struct sock *sk, struct sockaddr_iso *sa,
+/* Must be called on the locked socket. */
+static int iso_sock_rebind_bis(struct sock *sk, struct sockaddr_iso *sa,
                               int addr_len)
 {
        int err = 0;
 
-       if (sk->sk_type != SOCK_SEQPACKET) {
-               err = -EINVAL;
-               goto done;
-       }
-
-       if (addr_len != sizeof(*sa) + sizeof(*sa->iso_bc)) {
-               err = -EINVAL;
-               goto done;
-       }
+       if (!test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags))
+               return -EBADFD;
 
        if (sa->iso_bc->bc_num_bis > ISO_MAX_NUM_BIS) {
                err = -EINVAL;
@@ -1023,6 +1017,77 @@ done:
        return err;
 }
 
+static struct hci_dev *iso_conn_get_hdev(struct iso_conn *conn)
+{
+       struct hci_dev *hdev = NULL;
+
+       iso_conn_lock(conn);
+       if (conn->hcon)
+               hdev = hci_dev_hold(conn->hcon->hdev);
+       iso_conn_unlock(conn);
+
+       return hdev;
+}
+
+/* Must be called on the locked socket. */
+static int iso_sock_rebind_bc(struct sock *sk, struct sockaddr_iso *sa,
+                             int addr_len)
+{
+       struct hci_dev *hdev;
+       struct hci_conn *bis;
+       int err;
+
+       if (sk->sk_type != SOCK_SEQPACKET || !iso_pi(sk)->conn)
+               return -EINVAL;
+
+       /* Check if it is really a Broadcast address being requested */
+       if (addr_len != sizeof(*sa) + sizeof(*sa->iso_bc))
+               return -EINVAL;
+
+       /* Check if the address hasn't changed then perhaps only the number of
+        * bis has changed.
+        */
+       if (!bacmp(&iso_pi(sk)->dst, &sa->iso_bc->bc_bdaddr) ||
+           !bacmp(&sa->iso_bc->bc_bdaddr, BDADDR_ANY))
+               return iso_sock_rebind_bis(sk, sa, addr_len);
+
+       /* Check if the address type is of LE type */
+       if (!bdaddr_type_is_le(sa->iso_bc->bc_bdaddr_type))
+               return -EINVAL;
+
+       hdev = iso_conn_get_hdev(iso_pi(sk)->conn);
+       if (!hdev)
+               return -EINVAL;
+
+       bis = iso_pi(sk)->conn->hcon;
+
+       /* Release the socket before lookups since that requires hci_dev_lock
+        * which shall not be acquired while holding sock_lock for proper
+        * ordering.
+        */
+       release_sock(sk);
+       hci_dev_lock(bis->hdev);
+       lock_sock(sk);
+
+       if (!iso_pi(sk)->conn || iso_pi(sk)->conn->hcon != bis) {
+               /* raced with iso_conn_del() or iso_disconn_sock() */
+               err = -ENOTCONN;
+               goto unlock;
+       }
+
+       BT_DBG("sk %p %pMR type %u", sk, &sa->iso_bc->bc_bdaddr,
+              sa->iso_bc->bc_bdaddr_type);
+
+       err = hci_past_bis(bis, &sa->iso_bc->bc_bdaddr,
+                          le_addr_type(sa->iso_bc->bc_bdaddr_type));
+
+unlock:
+       hci_dev_unlock(hdev);
+       hci_dev_put(hdev);
+
+       return err;
+}
+
 static int iso_sock_bind(struct socket *sock, struct sockaddr_unsized *addr,
                         int addr_len)
 {
@@ -1038,13 +1103,12 @@ static int iso_sock_bind(struct socket *sock, struct sockaddr_unsized *addr,
 
        lock_sock(sk);
 
-       /* Allow the user to bind a PA sync socket to a number
-        * of BISes to sync to.
-        */
-       if ((sk->sk_state == BT_CONNECT2 ||
-            sk->sk_state == BT_CONNECTED) &&
-           test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) {
-               err = iso_sock_bind_pa_sk(sk, sa, addr_len);
+       if ((sk->sk_state == BT_CONNECT2 || sk->sk_state == BT_CONNECTED) &&
+           addr_len > sizeof(*sa)) {
+               /* Allow the user to rebind to a different address using
+                * PAST procedures.
+                */
+               err = iso_sock_rebind_bc(sk, sa, addr_len);
                goto done;
        }