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
next 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