Skip to content

Commit

Permalink
Bluetooth: Unlink CISes when LE disconnects in hci_conn_del
Browse files Browse the repository at this point in the history
Currently, hci_conn_del calls hci_conn_unlink for BR/EDR, (e)SCO, and
CIS connections, i.e., everything except LE connections. However, if
(e)SCO connections are unlinked when BR/EDR disconnects, CIS connections
should also be unlinked when LE disconnects.

In terms of disconnection behavior, CIS and (e)SCO connections are not
too different. One peculiarity of CIS is that when CIS connections are
disconnected, the CIS handle isn't deleted, as per [BLUETOOTH CORE
SPECIFICATION Version 5.4 | Vol 4, Part E] 7.1.6 Disconnect command:

        All SCO, eSCO, and CIS connections on a physical link should be
        disconnected before the ACL connection on the same physical
        connection is disconnected. If it does not, they will be
        implicitly disconnected as part of the ACL disconnection.
        ...
        Note: As specified in Section 7.7.5, on the Central, the handle
        for a CIS remains valid even after disconnection and, therefore,
        the Host can recreate a disconnected CIS at a later point in
        time using the same connection handle.

Since hci_conn_link invokes both hci_conn_get and hci_conn_hold,
hci_conn_unlink should perform both hci_conn_put and hci_conn_drop as
well. However, currently it performs only hci_conn_put.

This patch makes hci_conn_unlink call hci_conn_drop as well, which
simplifies the logic in hci_conn_del a bit and may benefit future users
of hci_conn_unlink. But it is noted that this change additionally
implies that hci_conn_unlink can queue disc_work on conn itself, with
the following call stack:

        hci_conn_unlink(conn)  [conn->parent == NULL]
                -> hci_conn_unlink(child)  [child->parent == conn]
                        -> hci_conn_drop(child->parent)
                                -> queue_delayed_work(&conn->disc_work)

Queued disc_work after hci_conn_del can be spurious, so during the
process of hci_conn_del, it is necessary to make the call to
cancel_delayed_work(&conn->disc_work) after invoking hci_conn_unlink.

Signed-off-by: Ruihan Li <lrh2000@pku.edu.cn>
Co-developed-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
  • Loading branch information
Ruihan Li authored and Luiz Augusto von Dentz committed May 19, 2023
1 parent a2ac591 commit a2904d2
Showing 1 changed file with 6 additions and 15 deletions.
21 changes: 6 additions & 15 deletions net/bluetooth/hci_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,9 @@ static void hci_conn_unlink(struct hci_conn *conn)
* yet at this point. Delete it now, otherwise it is
* possible for it to be stuck and can't be deleted.
*/
if (child->handle == HCI_CONN_HANDLE_UNSET)
if ((child->type == SCO_LINK ||
child->type == ESCO_LINK) &&
child->handle == HCI_CONN_HANDLE_UNSET)
hci_conn_del(child);
}

Expand All @@ -1113,6 +1115,7 @@ static void hci_conn_unlink(struct hci_conn *conn)
list_del_rcu(&conn->link->list);
synchronize_rcu();

hci_conn_drop(conn->parent);
hci_conn_put(conn->parent);
conn->parent = NULL;

Expand All @@ -1126,12 +1129,13 @@ void hci_conn_del(struct hci_conn *conn)

BT_DBG("%s hcon %p handle %d", hdev->name, conn, conn->handle);

hci_conn_unlink(conn);

cancel_delayed_work_sync(&conn->disc_work);
cancel_delayed_work_sync(&conn->auto_accept_work);
cancel_delayed_work_sync(&conn->idle_work);

if (conn->type == ACL_LINK) {
hci_conn_unlink(conn);
/* Unacked frames */
hdev->acl_cnt += conn->sent;
} else if (conn->type == LE_LINK) {
Expand All @@ -1142,13 +1146,6 @@ void hci_conn_del(struct hci_conn *conn)
else
hdev->acl_cnt += conn->sent;
} else {
struct hci_conn *acl = conn->parent;

if (acl) {
hci_conn_unlink(conn);
hci_conn_drop(acl);
}

/* Unacked ISO frames */
if (conn->type == ISO_LINK) {
if (hdev->iso_pkts)
Expand Down Expand Up @@ -2485,12 +2482,6 @@ void hci_conn_hash_flush(struct hci_dev *hdev)
list)) != NULL) {
conn->state = BT_CLOSED;
hci_disconn_cfm(conn, HCI_ERROR_LOCAL_HOST_TERM);

/* Unlink before deleting otherwise it is possible that
* hci_conn_del removes the link which may cause the list to
* contain items already freed.
*/
hci_conn_unlink(conn);
hci_conn_del(conn);
}
}
Expand Down

0 comments on commit a2904d2

Please sign in to comment.