[PATCH v3 4/6] Bluetooth: Don't leave abandoned SCO links up in the controller

From: Hitalo Souza

Date: Thu Oct 08 2026 - 18:27:07 EST


Since commit a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for
aborting connections"), aborting a SCO/eSCO connection whose setup is
pending, e.g. because its socket was closed, sends Create Connection
Cancel and deletes the hci_conn. That command cannot cancel a
synchronous connection, controllers just fail it (ACL Connection Already
Exists or Unknown Connection Identifier), and the link may still
complete afterwards. Unless another connection to the device takes it
over, its completion event then finds no connection waiting for it and
is ignored, so the link stays up in the controller, which rejects every
later setup for the device until the ACL drops (with Unsupported LMP
Parameter Value on a MediaTek MT7921). The same happens to a second
link completing for a connection that is already up, e.g. its own setup
after it took over the abandoned one.

There is no command to cancel a pending SCO/eSCO setup, so don't send
Create Connection Cancel for one, and disconnect a SCO/eSCO link that
completes with no connection waiting for it, with Synchronous
Connection Complete or, from Add SCO Connection, Connection Complete.

A controller may also answer the setup of a SCO_LINK connection with an
eSCO link, although only SCO packet types are allowed for it. Such a
link was ignored as well, and stayed up after the connection timed
out. Let the connection take it, as an eSCO connection already takes a
SCO link.

Link: https://github.com/bluez/bluez/issues/2562
Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
Assisted-by: LLM
Signed-off-by: Hitalo Souza <enghitalo@xxxxxxxxx>
---
net/bluetooth/hci_event.c | 43 +++++++++++++++++++++++++++++++++------
net/bluetooth/hci_sync.c | 8 ++++++++
2 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 1f9b47389..c825a99db 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -3306,8 +3306,12 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,

conn = hci_conn_hash_lookup_ba(hdev, ESCO_LINK,
&ev->bdaddr);
- if (!conn)
+ if (!conn) {
+ hci_disconnect_unused(hdev,
+ __le16_to_cpu(ev->handle),
+ HCI_ERROR_REMOTE_USER_TERM);
goto unlock;
+ }

conn->type = SCO_LINK;
}
@@ -3320,6 +3324,16 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
* whether the connection is already set up.
*/
if (!HCI_CONN_HANDLE_UNSET(conn->handle)) {
+ /* Another SCO link for a connection that is already up has
+ * no connection waiting for it either.
+ */
+ if (!status && ev->link_type == SCO_LINK &&
+ __le16_to_cpu(ev->handle) != conn->handle) {
+ hci_disconnect_unused(hdev, __le16_to_cpu(ev->handle),
+ HCI_ERROR_REMOTE_USER_TERM);
+ goto unlock;
+ }
+
bt_dev_err(hdev, "Ignoring HCI_Connection_Complete for existing connection");
goto unlock;
}
@@ -5212,9 +5226,6 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,

conn = hci_conn_hash_lookup_ba(hdev, ev->link_type, &ev->bdaddr);
if (!conn) {
- if (ev->link_type == ESCO_LINK)
- goto unlock;
-
/* When the link type in the event indicates SCO connection
* and lookup of the connection object fails, then check
* if an eSCO connection object exists.
@@ -5223,10 +5234,14 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,
* SCO or eSCO. The eSCO connection is preferred and tried
* to be setup first and until successfully established,
* the link type will be hinted as eSCO.
+ *
+ * The other way around, a controller may answer the setup of
+ * a SCO connection with an eSCO link.
*/
- conn = hci_conn_hash_lookup_ba(hdev, ESCO_LINK, &ev->bdaddr);
+ conn = hci_conn_hash_lookup_ba(hdev, ev->link_type == SCO_LINK ?
+ ESCO_LINK : SCO_LINK, &ev->bdaddr);
if (!conn)
- goto unlock;
+ goto unused;
}

/* The HCI_Synchronous_Connection_Complete event is only sent once per connection.
@@ -5236,6 +5251,13 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,
* whether the connection is already set up.
*/
if (!HCI_CONN_HANDLE_UNSET(conn->handle)) {
+ /* Another link for a connection that is already up, e.g. its
+ * own setup completing after it took over an abandoned one,
+ * has no connection waiting for it either.
+ */
+ if (__le16_to_cpu(ev->handle) != conn->handle)
+ goto unused;
+
bt_dev_err(hdev, "Ignoring HCI_Sync_Conn_Complete event for existing connection");
goto unlock;
}
@@ -5295,6 +5317,15 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,
hci_connect_cfm(conn, status);
if (status)
hci_conn_del(conn);
+ goto unlock;
+
+unused:
+ /* No connection waits for this link, e.g. because its setup was
+ * abandoned while pending: don't leave it up in the controller.
+ */
+ if (!status)
+ hci_disconnect_unused(hdev, __le16_to_cpu(ev->handle),
+ HCI_ERROR_REMOTE_USER_TERM);

unlock:
hci_dev_unlock(hdev);
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 014ceaefd..7f5c96b72 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -5942,6 +5942,14 @@ static int hci_connect_cancel_sync(struct hci_dev *hdev, struct hci_conn *conn,
return 0;
}

+ if (conn->type == SCO_LINK || conn->type == ESCO_LINK) {
+ /* There is no command to cancel a pending SCO/eSCO setup. If
+ * the link completes anyway, it is disconnected then, unless
+ * a new connection to the device takes it.
+ */
+ return 0;
+ }
+
if (hdev->hci_ver < BLUETOOTH_VER_1_2)
return 0;

--
2.55.0