]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
Bluetooth: mgmt: Translate HCI reason in Device Disconnected event
authorMikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
Fri, 10 Jul 2026 09:47:31 +0000 (14:47 +0500)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 13 Jul 2026 14:10:13 +0000 (10:10 -0400)
MGMT_EV_DEVICE_DISCONNECTED carries a reason field which is defined to
be one of MGMT_DEV_DISCONN_* (0x00..0x05). hci_disconn_complete_evt()
converts the HCI error with hci_to_mgmt_reason(), but two other paths
pass the raw HCI error straight through:

  hci_cs_disconnect()   -> cp->reason
  mgmt_connect_failed() -> status

The latter is reached whenever the adapter is powered off or suspended:
hci_disconnect_all_sync() aborts every link with
HCI_ERROR_REMOTE_POWER_OFF, hci_disconnect_sync() deliberately does not
wait for HCI_EV_DISCONN_COMPLETE for that reason, so that
hci_abort_conn_sync() finishes the connection off through
hci_conn_failed() instead.

As a result userspace sees an out of range reason:

  @ MGMT Event: Device Disconnected (0x000c) plen 8
        BR/EDR Address: 8C:A9:6F:2C:51:46
        Reason: Reserved (0x15)

  bluetoothd: btd_bearer_disconnected() Unknown disconnection value: 21
  bluetoothd: device_disconnected() Unknown disconnection value: 21

Export hci_to_mgmt_reason() and use it in both places, so that a power
off is reported as MGMT_DEV_DISCONN_REMOTE rather than as the raw
HCI_ERROR_REMOTE_POWER_OFF (0x15).

Fixes: d47da6bd4cfa ("Bluetooth: hci_core: Fix sending MGMT_EV_CONNECT_FAILED")
Fixes: 182ee45da083 ("Bluetooth: hci_sync: Rework hci_suspend_notifier")
Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
include/net/bluetooth/hci_core.h
net/bluetooth/hci_event.c
net/bluetooth/mgmt.c

index 4ca09298e11ae759493221947aa6ba2687b185ab..e7133ff87fbfa81b836fcff87cb64fae601fe0da 100644 (file)
@@ -2430,6 +2430,7 @@ void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key,
                       bool persistent);
 void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn,
                           u8 *name, u8 name_len);
+u8 hci_to_mgmt_reason(u8 err);
 void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr,
                              u8 link_type, u8 addr_type, u8 reason,
                              bool mgmt_connected);
index b6d963ce26d0e2f6675a7268452eecd6b0771d6a..741d658e9630368e4b573d295032d7b2139257a2 100644 (file)
@@ -2763,7 +2763,7 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
        }
 
        mgmt_device_disconnected(hdev, &conn->dst, conn->type, conn->dst_type,
-                                cp->reason, mgmt_conn);
+                                hci_to_mgmt_reason(cp->reason), mgmt_conn);
 
        hci_disconn_cfm(conn, cp->reason);
 
@@ -3381,22 +3381,6 @@ unlock:
        hci_dev_unlock(hdev);
 }
 
-static u8 hci_to_mgmt_reason(u8 err)
-{
-       switch (err) {
-       case HCI_ERROR_CONNECTION_TIMEOUT:
-               return MGMT_DEV_DISCONN_TIMEOUT;
-       case HCI_ERROR_REMOTE_USER_TERM:
-       case HCI_ERROR_REMOTE_LOW_RESOURCES:
-       case HCI_ERROR_REMOTE_POWER_OFF:
-               return MGMT_DEV_DISCONN_REMOTE;
-       case HCI_ERROR_LOCAL_HOST_TERM:
-               return MGMT_DEV_DISCONN_LOCAL_HOST;
-       default:
-               return MGMT_DEV_DISCONN_UNKNOWN;
-       }
-}
-
 static void hci_disconn_complete_evt(struct hci_dev *hdev, void *data,
                                     struct sk_buff *skb)
 {
index 23a1aded0ca8fa77188a73614ea8fb480f9defd6..1db10e0f617f92cdde51cf8cd5f618843a21e724 100644 (file)
@@ -9908,6 +9908,22 @@ bool mgmt_powering_down(struct hci_dev *hdev)
        return false;
 }
 
+u8 hci_to_mgmt_reason(u8 err)
+{
+       switch (err) {
+       case HCI_ERROR_CONNECTION_TIMEOUT:
+               return MGMT_DEV_DISCONN_TIMEOUT;
+       case HCI_ERROR_REMOTE_USER_TERM:
+       case HCI_ERROR_REMOTE_LOW_RESOURCES:
+       case HCI_ERROR_REMOTE_POWER_OFF:
+               return MGMT_DEV_DISCONN_REMOTE;
+       case HCI_ERROR_LOCAL_HOST_TERM:
+               return MGMT_DEV_DISCONN_LOCAL_HOST;
+       default:
+               return MGMT_DEV_DISCONN_UNKNOWN;
+       }
+}
+
 void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr,
                              u8 link_type, u8 addr_type, u8 reason,
                              bool mgmt_connected)
@@ -9969,7 +9985,8 @@ void mgmt_connect_failed(struct hci_dev *hdev, struct hci_conn *conn, u8 status)
 
        if (test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) {
                mgmt_device_disconnected(hdev, &conn->dst, conn->type,
-                                        conn->dst_type, status, true);
+                                        conn->dst_type,
+                                        hci_to_mgmt_reason(status), true);
                return;
        }