All of lore.kernel.org
 help / color / mirror / Atom feed
From: "xinpeng.wang" <wangxinpeng@uniontech.com>
To: linux-bluetooth@vger.kernel.org
Cc: "xinpeng . wang" <wangxinpeng@uniontech.com>
Subject: 
Date: Fri, 29 Aug 2025 10:01:28 +0800	[thread overview]
Message-ID: <20250829020128.949863-1-wangxinpeng@uniontech.com> (raw)

From d364976e93e23f5defbbf711dcda4787bdad3beb Mon Sep 17 00:00:00 2001
From: xinpeng wang <wangxinpeng@uniontech.com>
Date: Thu, 28 Aug 2025 21:31:31 +0800
Subject: [PATCH] device: Recreate paired device from storage if previous
 restoration failed

When a USB Bluetooth adapter is resumed from S4 suspend, the kernel may trigger
an "index remove" followed by an "index add". BlueZ responds by removing all
devices and attempting to recreate them from stored configuration (storage).

However, if a connected A2DP device disconnects just before suspend, BlueZ may
have started a disconnect timer (via set_disconnect_timer) but not yet freed
the session. During this period:
- The session pointer is set to NULL and becomes inaccessible.
- The session still holds a reference to the device, preventing it from being freed.
- As a result, the "index add" event fails to recreate the device from storage (due to
  D-Bus path conflict or incomplete cleanup).
- Later, when the timer expires, a new device is created from discovery data, bypassing
  storage and causing it to appear as unpaired.

This leads to loss of pairing information and confuses desktop applications that
rely on paired/unpaired state.

This patch improves the device creation logic during discovery: when creating a
device from scan data, it checks whether the remote address corresponds to a
known paired device in storage. If so, and if no valid device instance exists,
it forces recreation from storage to restore the correct paired state.

This ensures that devices are properly restored after suspend/resume cycles,
even in race conditions involving delayed session cleanup.

Signed-off-by: xinpeng.wang <wangxinpeng@uniontech.com>
---
 src/adapter.c | 111 ++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 99 insertions(+), 12 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 549a6c0b8..c2ac19f32 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -342,6 +342,8 @@ struct btd_adapter {
 
 	struct queue *exp_pending;
 	struct queue *exps;
+
+	GSList *pending_restore_device_addrs;
 };
 
 static char *adapter_power_state_str(uint32_t power_state)
@@ -1400,17 +1402,7 @@ static void adapter_add_device(struct btd_adapter *adapter,
 
 static struct btd_device *adapter_create_device(struct btd_adapter *adapter,
 						const bdaddr_t *bdaddr,
-						uint8_t bdaddr_type)
-{
-	struct btd_device *device;
-
-	device = device_create(adapter, bdaddr, bdaddr_type);
-	if (!device)
-		return NULL;
-
-	adapter_add_device(adapter, device);
-	return device;
-}
+						uint8_t bdaddr_type);
 
 static void service_auth_cancel(struct service_auth *auth)
 {
@@ -4969,6 +4961,95 @@ done:
 	mgmt_tlv_list_free(list);
 }
 
+static struct btd_device *adapter_create_device(struct btd_adapter *adapter,
+						const bdaddr_t *bdaddr,
+						uint8_t bdaddr_type)
+{
+	struct btd_device *device;
+	char addr[18];
+	GSList *l;
+	GKeyFile *key_file = NULL;
+
+	ba2str(bdaddr, addr);
+
+	l = g_slist_find_custom(adapter->pending_restore_device_addrs, addr,
+		(GCompareFunc)strcasecmp);
+	if (l != NULL) {
+		char filename[PATH_MAX];
+    		GError *gerr = NULL;
+
+		create_filename(filename, PATH_MAX, "/%s/%s/info",
+					btd_adapter_get_storage_dir(adapter),
+					addr);
+
+		key_file = g_key_file_new();
+		if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) {
+			error("Unable to load key file from %s: (%s)", filename,
+								gerr->message);
+			g_clear_error(&gerr);
+			adapter->pending_restore_device_addrs =
+				g_slist_delete_link(adapter->pending_restore_device_addrs, l);
+        		g_free(l->data);
+			key_file = NULL;
+		}
+	}
+
+	if (key_file != NULL) {
+		struct link_key_info *key_info;
+		struct smp_ltk_info *ltk_info;
+		struct smp_ltk_info *peripheral_ltk_info;
+		struct irk_info *irk_info;
+		
+		DBG("Found device %s but restoring from storage", addr);
+        	device = device_create_from_storage(adapter, addr, key_file);
+		if (!device) {
+			g_key_file_free(key_file);
+            		return NULL;
+		}
+		adapter->pending_restore_device_addrs = 
+			g_slist_delete_link(adapter->pending_restore_device_addrs, l);
+        	g_free(l->data);
+
+		if (bdaddr_type == BDADDR_BREDR)
+			device_set_bredr_support(device);
+		else
+			device_set_le_support(device, bdaddr_type);
+
+		key_info = get_key_info(key_file, addr, bdaddr_type);
+		ltk_info = get_ltk_info(key_file, addr, bdaddr_type);
+		peripheral_ltk_info = get_peripheral_ltk_info(key_file,
+						addr, bdaddr_type);
+		irk_info = get_irk_info(key_file, addr, bdaddr_type);
+		if (key_info) {
+			device_set_paired(device, BDADDR_BREDR);
+			device_set_bonded(device, BDADDR_BREDR);
+		}
+		if (ltk_info || peripheral_ltk_info) {
+			struct smp_ltk_info *info = ltk_info ? ltk_info : peripheral_ltk_info;
+			device_set_paired(device, ltk_info->bdaddr_type);
+			device_set_bonded(device, ltk_info->bdaddr_type);
+			
+			device_set_ltk(device, info->val, info->central,
+						info->enc_size);
+		}
+		if (irk_info)
+			device_set_rpa(device, true);
+
+		btd_device_set_temporary(device, false);
+		g_free(key_info);
+		g_free(ltk_info);
+		g_free(peripheral_ltk_info);
+		g_free(irk_info);
+	} else {
+		device = device_create(adapter, bdaddr, bdaddr_type);
+		if (!device)
+			return NULL;
+	}
+
+	adapter_add_device(adapter, device);
+	return device;
+}
+
 static void load_devices(struct btd_adapter *adapter)
 {
 	char dirname[PATH_MAX];
@@ -5087,8 +5168,11 @@ static void load_devices(struct btd_adapter *adapter)
 
 		device = device_create_from_storage(adapter, entry->d_name,
 							key_file);
-		if (!device)
+		if (!device) {
+			char *addr_copy = g_strdup(entry->d_name);
+    			adapter->pending_restore_device_addrs = g_slist_append(adapter->pending_restore_device_addrs,addr_copy);
 			goto free;
+		}
 
 		if (irk_info)
 			device_set_rpa(device, true);
@@ -7085,6 +7169,9 @@ static void adapter_remove(struct btd_adapter *adapter)
 	adapter->msd_callbacks = NULL;
 
 	queue_remove_all(adapter->exp_pending, NULL, NULL, cancel_exp_pending);
+
+	g_slist_free_full(adapter->pending_restore_device_addrs,g_free);
+	adapter->pending_restore_device_addrs = NULL;
 }
 
 const char *adapter_get_path(struct btd_adapter *adapter)
-- 
2.20.1


             reply	other threads:[~2025-08-29  2:02 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-29  2:01 xinpeng.wang [this message]
2025-08-29  2:42 ` bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250829020128.949863-1-wangxinpeng@uniontech.com \
    --to=wangxinpeng@uniontech.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.