Linux bluetooth development
 help / color / mirror / Atom feed
From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: Marcel Holtmann <marcel@holtmann.org>,
	Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Subject: [RFC PATCH] asha: Defer BR/EDR inquiry while streaming
Date: Tue, 13 Oct 2026 03:54:04 +0000	[thread overview]
Message-ID: <20261013035348.525335-1-cochcoder@proton.me> (raw)

Defer BR/EDR discovery while ASHA streams are active on the adapter.
Keep client filters intact and leave LE discovery available.

Reject a new BR/EDR-only discovery request with Busy rather than
leaving its reply pending. Re-evaluate the scan type after START
completes, and leave suspended discovery suspended.

Assisted-by: OpenCode:gpt-6.1-sol
---
Submission review notes (not part of the commit message):
Local validation is for base 7428ca2df9356363079db0f48412c20cbdb9471f.
Compatibility with newer upstream tips has not been verified locally.
October 12 N8/UB500 capture 182750 has successful LE discovery during
streaming, 5342 audio SDUs, no in-stream Inquiry, and BR/EDR Inquiry after
STOP. This tests the combined package. Native MGMT acknowledgments,
cancellation, failure unwind and concurrent-client arbitration remain
review gates. A separate baseline SPA plugin crash occurs at daemon swap;
the capture does not establish the source-level cause of that crash.

 profiles/audio/asha.c |  30 ++++++++++-
 src/adapter.c         | 119 ++++++++++++++++++++++++++++++++++++++++--
 src/adapter.h         |   5 ++
 3 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/profiles/audio/asha.c b/profiles/audio/asha.c
index 8e625fdca3..602da68da4 100644
--- a/profiles/audio/asha.c
+++ b/profiles/audio/asha.c
@@ -60,8 +60,26 @@ struct bt_asha_device {
 	uint16_t imtu, omtu;
 
 	unsigned int resume_id;
+	bool streaming_counted;
 };
 
+/*
+ * Count each device once so inquiry stays deferred until all streams stop.
+ */
+static void asha_set_streaming(struct bt_asha_device *asha_dev, bool streaming)
+{
+	if (streaming == asha_dev->streaming_counted)
+		return;
+
+	if (streaming)
+		btd_adapter_le_audio_ref(device_get_adapter(asha_dev->device));
+	else
+		btd_adapter_le_audio_unref(
+					device_get_adapter(asha_dev->device));
+
+	asha_dev->streaming_counted = streaming;
+}
+
 static char *make_endpoint_path(struct bt_asha_device *asha_dev)
 {
 	char *path;
@@ -91,6 +109,7 @@ static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
 
 	if (err) {
 		error("%s", err->message);
+		asha_set_streaming(asha_dev, false);
 		return;
 	}
 
@@ -171,12 +190,15 @@ unsigned int bt_asha_device_start(struct bt_asha_device *asha_dev,
 
 	if (ret < 0)
 		return 0;
-	else
-		return (++asha_dev->resume_id);
+
+	asha_set_streaming(asha_dev, true);
+	return (++asha_dev->resume_id);
 }
 
 unsigned int bt_asha_device_stop(struct bt_asha_device *asha_dev)
 {
+	asha_set_streaming(asha_dev, false);
+
 	bt_asha_stop(asha_dev->asha);
 
 	if (asha_dev->io) {
@@ -190,6 +212,8 @@ unsigned int bt_asha_device_stop(struct bt_asha_device *asha_dev)
 
 void bt_asha_device_state_reset(struct bt_asha_device *asha_dev)
 {
+	asha_set_streaming(asha_dev, false);
+
 	if (asha_dev->io) {
 		g_io_channel_shutdown(asha_dev->io, TRUE, NULL);
 		g_io_channel_unref(asha_dev->io);
@@ -371,6 +395,8 @@ static void asha_source_device_remove(struct btd_service *service)
 		return;
 	}
 
+	asha_set_streaming(asha_dev, false);
+
 	bt_asha_free(asha_dev->asha);
 	g_free(asha_dev);
 }
diff --git a/src/adapter.c b/src/adapter.c
index d3aa03fb70..28c58106dd 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -302,6 +302,7 @@ struct btd_adapter {
 					 * without delay */
 	uint8_t discovery_type;		/* current active discovery type */
 	uint8_t discovery_enable;	/* discovery enabled/disabled */
+	unsigned int le_audio_active;	/* Active ASHA streams */
 	bool discovery_suspended;	/* discovery has been suspended */
 	bool discovery_discoverable;	/* discoverable while discovering */
 	GSList *discovery_list;		/* list of discovery clients */
@@ -1833,6 +1834,8 @@ static void discovery_remove(struct discovery_client *client)
 }
 
 static void trigger_start_discovery(struct btd_adapter *adapter, guint delay);
+static int update_discovery_filter(struct btd_adapter *adapter);
+static uint8_t discovery_scan_type(struct btd_adapter *adapter, uint8_t type);
 
 static struct discovery_client *discovery_complete(struct btd_adapter *adapter,
 						uint8_t status)
@@ -1867,6 +1870,7 @@ static void start_discovery_complete(uint8_t status, uint16_t length,
 	struct btd_adapter *adapter = user_data;
 	struct discovery_client *client;
 	const struct mgmt_cp_start_discovery *rp = param;
+	uint8_t type;
 
 	DBG("status 0x%02x", status);
 
@@ -1911,6 +1915,16 @@ static void start_discovery_complete(uint8_t status, uint16_t length,
 
 		discovery_complete(adapter, status);
 
+		/* Audio or filters may change while START is pending. */
+		type = adapter->current_discovery_filter ?
+				adapter->current_discovery_filter->type :
+				get_scan_type(adapter);
+		type = discovery_scan_type(adapter, type);
+
+		if (!adapter->discovery_suspended &&
+						adapter->discovery_type != type)
+			trigger_start_discovery(adapter, 0);
+
 		if (adapter->discovering)
 			return;
 
@@ -1934,23 +1948,60 @@ static void start_discovery_complete(uint8_t status, uint16_t length,
 	trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT * 2);
 }
 
+/*
+ * Defer BR/EDR discovery during ASHA streaming, leaving LE discovery available.
+ * A zero result means the client requested only BR/EDR discovery.
+ */
+static uint8_t discovery_scan_type(struct btd_adapter *adapter, uint8_t type)
+{
+	if ((type & SCAN_TYPE_BREDR) && btd_adapter_le_audio_active(adapter)) {
+		DBG("LE audio active: deferring BR/EDR inquiry");
+		type &= ~SCAN_TYPE_BREDR;
+	}
+
+	return type;
+}
+
+static void defer_discovery(struct btd_adapter *adapter)
+{
+	struct discovery_client *client;
+
+	/* Do not leave a new BR/EDR-only StartDiscovery call unanswered. */
+	client = discovery_complete(adapter, MGMT_STATUS_BUSY);
+	if (client) {
+		discovery_remove(client);
+
+		if (adapter->discovery_list)
+			update_discovery_filter(adapter);
+
+		return;
+	}
+
+	/* Keep accepted sessions until audio releases the adapter. */
+	trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT);
+}
+
 static bool start_discovery_timeout(gpointer user_data)
 {
 	struct btd_adapter *adapter = user_data;
 	struct mgmt_cp_start_service_discovery *sd_cp;
+	size_t sd_len;
 	uint8_t new_type;
 
 	DBG("");
 
 	adapter->discovery_idle_timeout = 0;
 
+	if (!adapter->discovery_list || adapter->discovery_suspended)
+		return FALSE;
+
 	/* If we're doing filtered discovery, it must be quickly restarted */
 	adapter->no_scan_restart_delay = !!adapter->current_discovery_filter;
 
 	DBG("adapter->current_discovery_filter == %d",
 	    !!adapter->current_discovery_filter);
 
-	new_type = get_scan_type(adapter);
+	new_type = discovery_scan_type(adapter, get_scan_type(adapter));
 
 	if (adapter->discovery_enable == 0x01) {
 		struct mgmt_cp_stop_discovery cp;
@@ -1999,6 +2050,12 @@ static bool start_discovery_timeout(gpointer user_data)
 		struct mgmt_cp_start_discovery cp;
 
 		cp.type = new_type;
+
+		if (!cp.type) {
+			defer_discovery(adapter);
+			return FALSE;
+		}
+
 		mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY,
 					adapter->dev_id, sizeof(cp), &cp,
 					start_discovery_complete, adapter,
@@ -2007,18 +2064,32 @@ static bool start_discovery_timeout(gpointer user_data)
 		return FALSE;
 	}
 
-	/* Filtered discovery is required */
-	sd_cp = adapter->current_discovery_filter;
+	/* Filtered discovery is required. Keep the cached client filter intact;
+	 * it is needed to restore BR/EDR discovery after audio stops.
+	 */
+	sd_len = sizeof(*sd_cp) +
+		btohs(adapter->current_discovery_filter->uuid_count) * 16;
+	sd_cp = util_memdup(adapter->current_discovery_filter, sd_len);
+	if (!sd_cp)
+		return FALSE;
+
+	sd_cp->type = discovery_scan_type(adapter, sd_cp->type);
+	if (!sd_cp->type) {
+		free(sd_cp);
+		defer_discovery(adapter);
+		return FALSE;
+	}
 
 	DBG("sending MGMT_OP_START_SERVICE_DISCOVERY %d, %d, %d",
 				sd_cp->rssi, sd_cp->type,
 				btohs(sd_cp->uuid_count));
 
 	mgmt_send(adapter->mgmt, MGMT_OP_START_SERVICE_DISCOVERY,
-		  adapter->dev_id, sizeof(*sd_cp) +
-		  btohs(sd_cp->uuid_count) * 16,
+		  adapter->dev_id, sd_len,
 		  sd_cp, start_discovery_complete, adapter, NULL);
 
+	free(sd_cp);
+
 	return FALSE;
 }
 
@@ -6266,6 +6337,44 @@ struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
 	return adapter;
 }
 
+void btd_adapter_le_audio_ref(struct btd_adapter *adapter)
+{
+	if (!adapter)
+		return;
+
+	adapter->le_audio_active++;
+
+	/* First active stream: re-evaluate any running discovery so an
+	 * in-progress BR/EDR inquiry is dropped right away rather than only at
+	 * the next ~5 s re-arm.
+	 */
+	if (adapter->le_audio_active == 1 && adapter->discovery_list &&
+						!adapter->discovery_suspended) {
+		DBG("LE audio active: restarting discovery without BR/EDR");
+		trigger_start_discovery(adapter, 0);
+	}
+}
+
+void btd_adapter_le_audio_unref(struct btd_adapter *adapter)
+{
+	if (!adapter || !adapter->le_audio_active)
+		return;
+
+	adapter->le_audio_active--;
+
+	/* Restore requested discovery after the last stream stops. */
+	if (adapter->le_audio_active == 0 && adapter->discovery_list &&
+						!adapter->discovery_suspended) {
+		DBG("LE audio idle: restoring BR/EDR discovery");
+		trigger_start_discovery(adapter, 0);
+	}
+}
+
+bool btd_adapter_le_audio_active(struct btd_adapter *adapter)
+{
+	return adapter && adapter->le_audio_active > 0;
+}
+
 void btd_adapter_unref(struct btd_adapter *adapter)
 {
 	if (__sync_sub_and_fetch(&adapter->ref_count, 1))
diff --git a/src/adapter.h b/src/adapter.h
index 583168f4bc..6ad8fb096f 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -119,6 +119,11 @@ bool btd_adapter_uuid_is_allowed(struct btd_adapter *adapter, const char *uuid);
 struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter);
 void btd_adapter_unref(struct btd_adapter *adapter);
 
+/* Defer BR/EDR discovery while ASHA streams are active. */
+void btd_adapter_le_audio_ref(struct btd_adapter *adapter);
+void btd_adapter_le_audio_unref(struct btd_adapter *adapter);
+bool btd_adapter_le_audio_active(struct btd_adapter *adapter);
+
 void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
 							uint8_t minor);
 int btd_adapter_set_name(struct btd_adapter *adapter, const char *name);

base-commit: 7428ca2df9356363079db0f48412c20cbdb9471f


             reply	other threads:[~2026-10-13  3:54 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13  3:54 Kyle Pepper [this message]
2026-10-13  4:53 ` [RFC] asha: Defer BR/EDR inquiry while streaming 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=20261013035348.525335-1-cochcoder@proton.me \
    --to=cochcoder@proton.me \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=marcel@holtmann.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox