[PATCH v2 1/1] device: Recreate paired device from storage after failed restore

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 enhances the device creation logic: if loading a paired device
from storage previously failed (e.g., due to pending session cleanup), its
address is recorded in the adapter's pending list. Later, when creating a
device from scan data, BlueZ checks this list and re-attempts creation from
storage if matched, ensuring the correct paired state is restored.

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@xxxxxxxxxxxxx>
---
 src/adapter.c | 113 ++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 101 insertions(+), 12 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 549a6c0b8..16163d1f5 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -342,6 +342,8 @@ struct btd_adapter {
 
 	struct queue *exp_pending;
 	struct queue *exps;
+
+	GSList *restore_dev_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,93 @@ 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 *match = NULL;
+	char *match_addr = NULL;
+	GKeyFile *key_file = NULL;
+
+	ba2str(bdaddr, addr);
+
+	match = g_slist_find_custom(adapter->restore_dev_addrs, addr,
+					(GCompareFunc)strcasecmp);
+	if (match) {
+		char filename[PATH_MAX];
+		GError *gerr = 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;
+
+		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);
+		}
+
+		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;
+		}
+		match_addr = match->data;
+		adapter->restore_dev_addrs =
+			g_slist_delete_link(adapter->restore_dev_addrs, match);
+		g_free(match_addr);
+
+		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;
+
+			info = ltk_info ? ltk_info : peripheral_ltk_info;
+			device_set_paired(device, info->bdaddr_type);
+			device_set_bonded(device, 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);
+		g_key_file_free(key_file);
+	} 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 +5166,15 @@ 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;
+
+			addr_copy = g_strdup(entry->d_name);
+			adapter->restore_dev_addrs =
+				g_slist_append(adapter->restore_dev_addrs,
+						addr_copy);
 			goto free;
+		}
 
 		if (irk_info)
 			device_set_rpa(device, true);
@@ -7085,6 +7171,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->restore_dev_addrs, g_free);
+	adapter->restore_dev_addrs = NULL;
 }
 
 const char *adapter_get_path(struct btd_adapter *adapter)
-- 
2.20.1





[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux