include/net/bluetooth/hci_core.h | 2 +- net/bluetooth/hci_conn.c | 66 ++++++++++++++++++++++---------- net/bluetooth/hci_event.c | 33 ++++++++++++++++ net/bluetooth/l2cap_core.c | 12 +----- 4 files changed, 81 insertions(+), 32 deletions(-)
When a BLE peripheral sends an L2CAP Connection Parameter Update Request
the processing path is:
process_pending_rx() [takes conn->lock]
l2cap_le_sig_channel()
l2cap_conn_param_update_req()
hci_le_conn_update() [takes hdev->lock]
Meanwhile other code paths take the locks in the opposite order:
l2cap_chan_connect() [takes hdev->lock]
...
mutex_lock(&conn->lock)
l2cap_conn_ready() [hdev->lock via hci_cb_list_lock]
...
mutex_lock(&conn->lock)
This is a classic AB/BA deadlock which lockdep reports as a circular
locking dependency when connecting a BLE MIDI keyboard (Carry-On FC-49).
Fix this by making hci_le_conn_update() defer the HCI command through
hci_cmd_sync_queue() so it no longer needs to take hdev->lock in the
caller context.
The connection parameter storage (hci_conn_params update) and userspace
notification (mgmt_new_conn_param) are moved into
hci_le_conn_update_complete_evt() where they are handled when the
controller confirms the update, using hci_sent_cmd_data() to retrieve
the originally requested min/max interval values.
Fixes: f044eb0524a0 ("Bluetooth: Store latency and supervision timeout in connection params")
Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
---
Changes in v3 (Luiz Augusto von Dentz):
- Move hci_cmd_sync_queue into hci_le_conn_update itself instead of open-coding
the deferral in l2cap_core.c
- Move conn_params update and mgmt_new_conn_param into
hci_le_conn_update_complete_evt, using hci_sent_cmd_data to retrieve
the originally requested parameters
Changes in v2 (Paul Menzel, Sashiko/Gemini AI review):
- Allocate before sending ACCEPTED response to avoid state mismatch on OOM
- Verify connection handle and address in sync callback against reuse race
- Expand commit message with implementation details
include/net/bluetooth/hci_core.h | 2 +-
net/bluetooth/hci_conn.c | 66 ++++++++++++++++++++++----------
net/bluetooth/hci_event.c | 33 ++++++++++++++++
net/bluetooth/l2cap_core.c | 12 +-----
4 files changed, 81 insertions(+), 32 deletions(-)
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index a7bffb908c1e..aa600fbf9a53 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -2495,7 +2495,7 @@ void mgmt_adv_monitor_device_lost(struct hci_dev *hdev, u16 handle,
bdaddr_t *bdaddr, u8 addr_type);
int hci_abort_conn(struct hci_conn *conn, u8 reason);
-u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
+void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
u16 to_multiplier);
void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
__u8 ltk[16], __u8 key_size);
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 11d3ad8d2551..207221560a02 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -480,40 +480,64 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle)
return hci_setup_sync_conn(conn, handle);
}
-u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
- u16 to_multiplier)
+struct le_conn_update_data {
+ struct hci_conn *conn;
+ u16 min;
+ u16 max;
+ u16 latency;
+ u16 to_multiplier;
+};
+
+static int le_conn_update_sync(struct hci_dev *hdev, void *data)
{
- struct hci_dev *hdev = conn->hdev;
- struct hci_conn_params *params;
+ struct le_conn_update_data *d = data;
+ struct hci_conn *conn;
struct hci_cp_le_conn_update cp;
hci_dev_lock(hdev);
-
- params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type);
- if (params) {
- params->conn_min_interval = min;
- params->conn_max_interval = max;
- params->conn_latency = latency;
- params->supervision_timeout = to_multiplier;
- }
-
+ /* Verify connection is still alive. */
+ conn = hci_conn_valid(hdev, d->conn) ? d->conn : NULL;
hci_dev_unlock(hdev);
+ if (!conn)
+ return -ECANCELED;
memset(&cp, 0, sizeof(cp));
cp.handle = cpu_to_le16(conn->handle);
- cp.conn_interval_min = cpu_to_le16(min);
- cp.conn_interval_max = cpu_to_le16(max);
- cp.conn_latency = cpu_to_le16(latency);
- cp.supervision_timeout = cpu_to_le16(to_multiplier);
+ cp.conn_interval_min = cpu_to_le16(d->min);
+ cp.conn_interval_max = cpu_to_le16(d->max);
+ cp.conn_latency = cpu_to_le16(d->latency);
+ cp.supervision_timeout = cpu_to_le16(d->to_multiplier);
cp.min_ce_len = cpu_to_le16(0x0000);
cp.max_ce_len = cpu_to_le16(0x0000);
- hci_send_cmd(hdev, HCI_OP_LE_CONN_UPDATE, sizeof(cp), &cp);
+ return __hci_cmd_sync_status(hdev, HCI_OP_LE_CONN_UPDATE,
+ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+static void le_conn_update_complete(struct hci_dev *hdev, void *data,
+ int err)
+{
+ kfree(data);
+}
- if (params)
- return 0x01;
+void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
+ u16 to_multiplier)
+{
+ struct le_conn_update_data *d;
- return 0x00;
+ d = kmalloc(sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return;
+
+ d->conn = conn;
+ d->min = min;
+ d->max = max;
+ d->latency = latency;
+ d->to_multiplier = to_multiplier;
+
+ if (hci_cmd_sync_queue(conn->hdev, le_conn_update_sync, d,
+ le_conn_update_complete) < 0)
+ kfree(d);
}
void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 3ebc5e6d45d9..9df8fc762a84 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -6065,6 +6065,8 @@ static void hci_le_conn_update_complete_evt(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
struct hci_ev_le_conn_update_complete *ev = data;
+ struct hci_cp_le_conn_update *sent;
+ struct hci_conn_params *params;
struct hci_conn *conn;
bt_dev_dbg(hdev, "status 0x%2.2x", ev->status);
@@ -6079,6 +6081,37 @@ static void hci_le_conn_update_complete_evt(struct hci_dev *hdev, void *data,
conn->le_conn_interval = le16_to_cpu(ev->interval);
conn->le_conn_latency = le16_to_cpu(ev->latency);
conn->le_supv_timeout = le16_to_cpu(ev->supervision_timeout);
+
+ /* Update stored connection parameters and notify userspace
+ * using the parameters from the original HCI command.
+ */
+ sent = hci_sent_cmd_data(hdev, HCI_OP_LE_CONN_UPDATE);
+ if (sent) {
+ u8 store_hint;
+
+ params = hci_conn_params_lookup(hdev, &conn->dst,
+ conn->dst_type);
+ if (params) {
+ params->conn_min_interval =
+ le16_to_cpu(sent->conn_interval_min);
+ params->conn_max_interval =
+ le16_to_cpu(sent->conn_interval_max);
+ params->conn_latency =
+ le16_to_cpu(sent->conn_latency);
+ params->supervision_timeout =
+ le16_to_cpu(sent->supervision_timeout);
+ store_hint = 0x01;
+ } else {
+ store_hint = 0x00;
+ }
+
+ mgmt_new_conn_param(hdev, &conn->dst, conn->dst_type,
+ store_hint,
+ le16_to_cpu(sent->conn_interval_min),
+ le16_to_cpu(sent->conn_interval_max),
+ le16_to_cpu(sent->conn_latency),
+ le16_to_cpu(sent->supervision_timeout));
+ }
}
hci_dev_unlock(hdev);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 95c65fece39b..aac2db1d6fbb 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -4706,16 +4706,8 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,
sizeof(rsp), &rsp);
- if (!err) {
- u8 store_hint;
-
- store_hint = hci_le_conn_update(hcon, min, max, latency,
- to_multiplier);
- mgmt_new_conn_param(hcon->hdev, &hcon->dst, hcon->dst_type,
- store_hint, min, max, latency,
- to_multiplier);
-
- }
+ if (!err)
+ hci_le_conn_update(hcon, min, max, latency, to_multiplier);
return 0;
}
--
2.53.0
Hi Mikhail,
On Mon, Apr 13, 2026 at 7:08 PM Mikhail Gavrilov
<mikhail.v.gavrilov@gmail.com> wrote:
>
> When a BLE peripheral sends an L2CAP Connection Parameter Update Request
> the processing path is:
>
> process_pending_rx() [takes conn->lock]
> l2cap_le_sig_channel()
> l2cap_conn_param_update_req()
> hci_le_conn_update() [takes hdev->lock]
>
> Meanwhile other code paths take the locks in the opposite order:
>
> l2cap_chan_connect() [takes hdev->lock]
> ...
> mutex_lock(&conn->lock)
>
> l2cap_conn_ready() [hdev->lock via hci_cb_list_lock]
> ...
> mutex_lock(&conn->lock)
>
> This is a classic AB/BA deadlock which lockdep reports as a circular
> locking dependency when connecting a BLE MIDI keyboard (Carry-On FC-49).
>
> Fix this by making hci_le_conn_update() defer the HCI command through
> hci_cmd_sync_queue() so it no longer needs to take hdev->lock in the
> caller context.
>
> The connection parameter storage (hci_conn_params update) and userspace
> notification (mgmt_new_conn_param) are moved into
> hci_le_conn_update_complete_evt() where they are handled when the
> controller confirms the update, using hci_sent_cmd_data() to retrieve
> the originally requested min/max interval values.
>
> Fixes: f044eb0524a0 ("Bluetooth: Store latency and supervision timeout in connection params")
> Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
> ---
>
> Changes in v3 (Luiz Augusto von Dentz):
> - Move hci_cmd_sync_queue into hci_le_conn_update itself instead of open-coding
> the deferral in l2cap_core.c
> - Move conn_params update and mgmt_new_conn_param into
> hci_le_conn_update_complete_evt, using hci_sent_cmd_data to retrieve
> the originally requested parameters
>
> Changes in v2 (Paul Menzel, Sashiko/Gemini AI review):
> - Allocate before sending ACCEPTED response to avoid state mismatch on OOM
> - Verify connection handle and address in sync callback against reuse race
> - Expand commit message with implementation details
>
> include/net/bluetooth/hci_core.h | 2 +-
> net/bluetooth/hci_conn.c | 66 ++++++++++++++++++++++----------
> net/bluetooth/hci_event.c | 33 ++++++++++++++++
> net/bluetooth/l2cap_core.c | 12 +-----
> 4 files changed, 81 insertions(+), 32 deletions(-)
>
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index a7bffb908c1e..aa600fbf9a53 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -2495,7 +2495,7 @@ void mgmt_adv_monitor_device_lost(struct hci_dev *hdev, u16 handle,
> bdaddr_t *bdaddr, u8 addr_type);
>
> int hci_abort_conn(struct hci_conn *conn, u8 reason);
> -u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
> +void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
> u16 to_multiplier);
> void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
> __u8 ltk[16], __u8 key_size);
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 11d3ad8d2551..207221560a02 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -480,40 +480,64 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle)
> return hci_setup_sync_conn(conn, handle);
> }
>
> -u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
> - u16 to_multiplier)
> +struct le_conn_update_data {
> + struct hci_conn *conn;
> + u16 min;
> + u16 max;
> + u16 latency;
> + u16 to_multiplier;
> +};
> +
> +static int le_conn_update_sync(struct hci_dev *hdev, void *data)
> {
> - struct hci_dev *hdev = conn->hdev;
> - struct hci_conn_params *params;
> + struct le_conn_update_data *d = data;
> + struct hci_conn *conn;
> struct hci_cp_le_conn_update cp;
>
> hci_dev_lock(hdev);
> -
> - params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type);
> - if (params) {
> - params->conn_min_interval = min;
> - params->conn_max_interval = max;
> - params->conn_latency = latency;
> - params->supervision_timeout = to_multiplier;
> - }
> -
> + /* Verify connection is still alive. */
> + conn = hci_conn_valid(hdev, d->conn) ? d->conn : NULL;
> hci_dev_unlock(hdev);
> + if (!conn)
> + return -ECANCELED;
>
> memset(&cp, 0, sizeof(cp));
> cp.handle = cpu_to_le16(conn->handle);
> - cp.conn_interval_min = cpu_to_le16(min);
> - cp.conn_interval_max = cpu_to_le16(max);
> - cp.conn_latency = cpu_to_le16(latency);
> - cp.supervision_timeout = cpu_to_le16(to_multiplier);
> + cp.conn_interval_min = cpu_to_le16(d->min);
> + cp.conn_interval_max = cpu_to_le16(d->max);
> + cp.conn_latency = cpu_to_le16(d->latency);
> + cp.supervision_timeout = cpu_to_le16(d->to_multiplier);
> cp.min_ce_len = cpu_to_le16(0x0000);
> cp.max_ce_len = cpu_to_le16(0x0000);
>
> - hci_send_cmd(hdev, HCI_OP_LE_CONN_UPDATE, sizeof(cp), &cp);
> + return __hci_cmd_sync_status(hdev, HCI_OP_LE_CONN_UPDATE,
> + sizeof(cp), &cp, HCI_CMD_TIMEOUT);
> +}
> +
> +static void le_conn_update_complete(struct hci_dev *hdev, void *data,
> + int err)
> +{
> + kfree(data);
> +}
>
> - if (params)
> - return 0x01;
> +void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
> + u16 to_multiplier)
> +{
> + struct le_conn_update_data *d;
>
> - return 0x00;
> + d = kmalloc(sizeof(*d), GFP_KERNEL);
> + if (!d)
> + return;
> +
> + d->conn = conn;
> + d->min = min;
> + d->max = max;
> + d->latency = latency;
> + d->to_multiplier = to_multiplier;
> +
> + if (hci_cmd_sync_queue(conn->hdev, le_conn_update_sync, d,
> + le_conn_update_complete) < 0)
> + kfree(d);
> }
>
> void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index 3ebc5e6d45d9..9df8fc762a84 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -6065,6 +6065,8 @@ static void hci_le_conn_update_complete_evt(struct hci_dev *hdev, void *data,
> struct sk_buff *skb)
> {
> struct hci_ev_le_conn_update_complete *ev = data;
> + struct hci_cp_le_conn_update *sent;
> + struct hci_conn_params *params;
> struct hci_conn *conn;
>
> bt_dev_dbg(hdev, "status 0x%2.2x", ev->status);
> @@ -6079,6 +6081,37 @@ static void hci_le_conn_update_complete_evt(struct hci_dev *hdev, void *data,
> conn->le_conn_interval = le16_to_cpu(ev->interval);
> conn->le_conn_latency = le16_to_cpu(ev->latency);
> conn->le_supv_timeout = le16_to_cpu(ev->supervision_timeout);
> +
> + /* Update stored connection parameters and notify userspace
> + * using the parameters from the original HCI command.
> + */
> + sent = hci_sent_cmd_data(hdev, HCI_OP_LE_CONN_UPDATE);
> + if (sent) {
> + u8 store_hint;
> +
> + params = hci_conn_params_lookup(hdev, &conn->dst,
> + conn->dst_type);
> + if (params) {
> + params->conn_min_interval =
> + le16_to_cpu(sent->conn_interval_min);
> + params->conn_max_interval =
> + le16_to_cpu(sent->conn_interval_max);
> + params->conn_latency =
> + le16_to_cpu(sent->conn_latency);
> + params->supervision_timeout =
> + le16_to_cpu(sent->supervision_timeout);
> + store_hint = 0x01;
> + } else {
> + store_hint = 0x00;
> + }
> +
> + mgmt_new_conn_param(hdev, &conn->dst, conn->dst_type,
> + store_hint,
> + le16_to_cpu(sent->conn_interval_min),
> + le16_to_cpu(sent->conn_interval_max),
> + le16_to_cpu(sent->conn_latency),
> + le16_to_cpu(sent->supervision_timeout));
> + }
> }
>
> hci_dev_unlock(hdev);
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index 95c65fece39b..aac2db1d6fbb 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -4706,16 +4706,8 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
> l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,
> sizeof(rsp), &rsp);
>
> - if (!err) {
> - u8 store_hint;
> -
> - store_hint = hci_le_conn_update(hcon, min, max, latency,
> - to_multiplier);
> - mgmt_new_conn_param(hcon->hdev, &hcon->dst, hcon->dst_type,
> - store_hint, min, max, latency,
> - to_multiplier);
> -
> - }
> + if (!err)
> + hci_le_conn_update(hcon, min, max, latency, to_multiplier);
>
> return 0;
> }
> --
> 2.53.0
>
https://sashiko.dev/#/patchset/20260413230813.21393-1-mikhail.v.gavrilov%40gmail.com
Most comments seem valid. Regarding the last 2 we might need to wait
for HCI_EV_LE_CONN_UPDATE_COMPLETE by passing it to
__hci_cmd_sync_status_sk.
For the first comment I guess we will need to use hci_conn_get to
prevent the hci_conn from being freed. It can still be removed so the
usage of hci_conn_valid remains valid, but we will need to call
hci_conn_put even if hci_conn is already removed.
--
Luiz Augusto von Dentz
© 2016 - 2026 Red Hat, Inc.