Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ v2 0/4] shared/bap: Don't disconnect a CIS still used by another ASE
@ 2026-11-11 16:25 Luiz Augusto von Dentz
  2026-11-11 16:25 ` [PATCH BlueZ v2 1/4] shared/bap: Don't disconnect a CIS still configured for a link Luiz Augusto von Dentz
                   ` (13 more replies)
  0 siblings, 14 replies; 28+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-11 16:25 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

A remote initiator, e.g. a phone in a voice call, may enable the Source
ASE of a CIS shared with a Sink ASE and then switch to the Sink ASE
alone. The acceptor disconnected the CIS when the Source ASE detached
from it, although the linked Sink ASE was still configured for it, and
as the initiator does not request the CIS again, the Sink ASE was left
enabled without a CIS and the voice stayed silent.

Keep the CIS while another ASE is still configured for it. Unit tests
cover releasing an ASE of a CIS kept by its link, of a shared CIS and
switching the direction, and a functional test counts the CIS the
acceptor is asked to accept.

v2: Also tell the streams using the CIS of a linked stream when it is
    lost, not only the ones that hold it themselves.

Luiz Augusto von Dentz (4):
  shared/bap: Don't disconnect a CIS still configured for a link
  unit/test-bap: Add tests for a CIS shared by two ASEs
  doc/functional-bap: Document switching the direction of a CIS
  test: functional: Cover switching the direction of a CIS

 doc/functional-bap.rst      |  37 ++++++
 src/shared/bap.c            |  84 +++++++++----
 test/functional/test_bap.py |  68 +++++++++++
 unit/test-bap.c             | 227 ++++++++++++++++++++++++++++++++++++
 4 files changed, 391 insertions(+), 25 deletions(-)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread
* [PATCH BlueZ v3 1/5] shared/bap: Don't disconnect a CIS still configured for a link
@ 2026-11-11 20:37 Luiz Augusto von Dentz
  2026-11-12  0:37 ` shared/bap: Don't disconnect a CIS still used by another ASE bluez.test.bot
  0 siblings, 1 reply; 28+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-11 20:37 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

When a stream was detached from its CIS, its link was detached as well
if in QoS Configured state, which disconnected the CIS. As a server,
this happened as soon as the client disabled the Source ASE of a
bidirectional CIS, while it may enable the Sink ASE right after over
the same CIS, e.g. when a phone switches a headset from its microphone
to its speaker in a voice call:

  > LE-ACL: Handle 2048 flags 0x02 dlen 10       #1155 18:59:04.179316
        ATT: Write Command (0x52) len 5
          Opcode: Disable (0x05)
            ASE ID: 0x03
  < HCI Command: Disconnect (0x01|0x0006) plen 3 #1157 18:59:04.179773
        Handle: 2304 (LE-CIS)
        Reason: Remote User Terminated Connection (0x13)
  > LE-ACL: Handle 2048 flags 0x02 dlen 17       #1164 18:59:04.201326
        ATT: Write Command (0x52) len 12
          Opcode: Enable (0x03)
            ASE ID: 0x01

The client does not establish the CIS again, so the Sink ASE stayed in
Enabling without a CIS and its transport never became pending, leaving
the speaker silent.

Leave the link attached, so the CIS is only disconnected once no
stream uses it anymore, by the client, or when it is released.

As the IO may then outlive the stream it was created for, its
disconnect handler no longer keeps a pointer to that stream, which may
have been freed, but cleans up every stream using the IO, e.g. moving a
Source ASE releasing to Codec Configured although the Sink ASE also
using the CIS was found first, holding a reference to each of them.

Fixes: b798cec4ca62 ("shared/bap: Add initial code for handling BAP")
Assisted-by: Claude:claude-opus-5-5
---
 src/shared/bap.c | 84 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 59 insertions(+), 25 deletions(-)

diff --git a/src/shared/bap.c b/src/shared/bap.c
index 8cb7fb40f16c..37d14bd77c90 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -1564,20 +1564,8 @@ static void bap_stream_update_io_links(struct bt_bap_stream *stream)
 	queue_find(bap->streams, bap_stream_io_link, stream);
 }
 
-static bool match_stream_io(const void *data, const void *user_data)
-{
-	const struct bt_bap_stream *stream = data;
-	const struct bt_bap_stream_io *io = user_data;
-
-	if (!stream->io)
-		return false;
-
-	return stream->io == io;
-}
-
 static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 {
-	struct bt_bap_stream *link;
 	struct bt_bap_stream_io *io;
 
 	if (!stream->io)
@@ -1588,13 +1576,10 @@ static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 	io = stream->io;
 	stream->io = NULL;
 
-	link = queue_find(stream->links, match_stream_io, io);
-	if (link) {
-		/* Detach link if in QoS state */
-		if (link->ep->state == BT_ASCS_ASE_STATE_QOS)
-			bap_stream_io_detach(link);
-	}
-
+	/* A link keeps the CIS, even in QoS state, as it may be enabled
+	 * next over the same CIS: it is detached in turn when it is
+	 * disabled, released or the CIS is disconnected.
+	 */
 	stream_io_unref(io);
 
 	return true;
@@ -3215,7 +3200,10 @@ static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd,
 
 	io->connecting = connecting;
 	stream->io = io;
-	io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL);
+	/* The IO may outlive the stream it was created for, as long as a
+	 * link uses it, so the handler looks up the stream using it.
+	 */
+	io_set_disconnect_handler(io->io, stream_io_disconnected, io, NULL);
 
 	return true;
 }
@@ -7279,9 +7267,21 @@ bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data)
 	return stream->lpac->type == BT_BAP_BCAST_SINK;
 }
 
-static bool stream_io_disconnected(struct io *io, void *user_data)
+static void stream_io_collect(void *data, void *user_data)
 {
-	struct bt_bap_stream *stream = user_data;
+	struct bt_bap_stream *stream = data;
+	void **args = user_data;
+
+	if (stream_get_io(stream) == args[0])
+		queue_push_tail(args[1], bt_bap_stream_ref(stream));
+}
+
+static void stream_io_lost(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+
+	if (!bap_stream_valid(stream))
+		return;
 
 	DBG(stream->bap, "stream %p io disconnected", stream);
 
@@ -7289,9 +7289,9 @@ static bool stream_io_disconnected(struct io *io, void *user_data)
 	 * and PA Sync have been lost so switch to idle state to cleanup the
 	 * stream.
 	 */
-	if (stream->lpac->type == BT_BAP_BCAST_SINK) {
+	if (stream->lpac && stream->lpac->type == BT_BAP_BCAST_SINK) {
 		stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
-		return false;
+		return;
 	}
 
 	switch (stream->ep->state) {
@@ -7304,8 +7304,42 @@ static bool stream_io_disconnected(struct io *io, void *user_data)
 		stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
 		break;
 	}
+}
+
+static void stream_io_clear(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+
+	if (bap_stream_valid(stream))
+		bt_bap_stream_set_io(stream, -1);
+}
+
+static bool stream_io_disconnected(struct io *io, void *user_data)
+{
+	struct bt_bap_stream_io *sio = user_data;
+	struct bt_bap *bap;
+	struct queue *streams;
+	void *args[2];
+
+	bap = bt_bap_ref_safe(sio->bap);
+	if (!bap)
+		return false;
+
+	/* Every stream using the CIS, e.g. a Sink and a Source ASE sharing
+	 * it, has to be cleaned up, and their state callbacks may release
+	 * them or change their links, so hold a reference to each of them.
+	 */
+	streams = queue_new();
+	args[0] = sio;
+	args[1] = streams;
+	queue_foreach(bap->streams, stream_io_collect, args);
+
+	queue_foreach(streams, stream_io_lost, NULL);
+	queue_foreach(streams, stream_io_clear, NULL);
+
+	queue_destroy(streams, bt_bap_stream_unref);
+	bt_bap_unref(bap);
 
-	bt_bap_stream_set_io(stream, -1);
 	return false;
 }
 
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread
* [PATCH BlueZ v1 1/4] shared/bap: Don't disconnect a CIS still configured for a link
@ 2026-11-10 22:18 Luiz Augusto von Dentz
  2026-11-11  4:35 ` shared/bap: Don't disconnect a CIS still used by another ASE bluez.test.bot
  0 siblings, 1 reply; 28+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-10 22:18 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

When a stream was detached from its CIS, its link was detached as well
if in QoS Configured state, which disconnected the CIS. As a server,
this happened as soon as the client disabled the Source ASE of a
bidirectional CIS, while it may enable the Sink ASE right after over
the same CIS, e.g. when a phone switches a headset from its microphone
to its speaker in a voice call:

  > LE-ACL: Handle 2048 flags 0x02 dlen 10       #1155 18:59:04.179316
        ATT: Write Command (0x52) len 5
          Opcode: Disable (0x05)
            ASE ID: 0x03
  < HCI Command: Disconnect (0x01|0x0006) plen 3 #1157 18:59:04.179773
        Handle: 2304 (LE-CIS)
        Reason: Remote User Terminated Connection (0x13)
  > LE-ACL: Handle 2048 flags 0x02 dlen 17       #1164 18:59:04.201326
        ATT: Write Command (0x52) len 12
          Opcode: Enable (0x03)
            ASE ID: 0x01

The client does not establish the CIS again, so the Sink ASE stayed in
Enabling without a CIS and its transport never became pending, leaving
the speaker silent.

Leave the link attached, so the CIS is only disconnected once no
stream uses it anymore, by the client, or when it is released.

As the IO may then outlive the stream it was created for, its
disconnect handler no longer keeps a pointer to that stream, which may
have been freed, but cleans up every stream using the IO, e.g. moving a
Source ASE releasing to Codec Configured although the Sink ASE also
using the CIS was found first, holding a reference to each of them.

Fixes: b798cec4ca62 ("shared/bap: Add initial code for handling BAP")
Assisted-by: Claude:claude-opus-5-5
---
 src/shared/bap.c | 84 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 59 insertions(+), 25 deletions(-)

diff --git a/src/shared/bap.c b/src/shared/bap.c
index 8cb7fb40f16c..dd4e9e775bb0 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -1564,20 +1564,8 @@ static void bap_stream_update_io_links(struct bt_bap_stream *stream)
 	queue_find(bap->streams, bap_stream_io_link, stream);
 }
 
-static bool match_stream_io(const void *data, const void *user_data)
-{
-	const struct bt_bap_stream *stream = data;
-	const struct bt_bap_stream_io *io = user_data;
-
-	if (!stream->io)
-		return false;
-
-	return stream->io == io;
-}
-
 static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 {
-	struct bt_bap_stream *link;
 	struct bt_bap_stream_io *io;
 
 	if (!stream->io)
@@ -1588,13 +1576,10 @@ static bool bap_stream_io_detach(struct bt_bap_stream *stream)
 	io = stream->io;
 	stream->io = NULL;
 
-	link = queue_find(stream->links, match_stream_io, io);
-	if (link) {
-		/* Detach link if in QoS state */
-		if (link->ep->state == BT_ASCS_ASE_STATE_QOS)
-			bap_stream_io_detach(link);
-	}
-
+	/* A link keeps the CIS, even in QoS state, as it may be enabled
+	 * next over the same CIS: it is detached in turn when it is
+	 * disabled, released or the CIS is disconnected.
+	 */
 	stream_io_unref(io);
 
 	return true;
@@ -3215,7 +3200,10 @@ static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd,
 
 	io->connecting = connecting;
 	stream->io = io;
-	io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL);
+	/* The IO may outlive the stream it was created for, as long as a
+	 * link uses it, so the handler looks up the stream using it.
+	 */
+	io_set_disconnect_handler(io->io, stream_io_disconnected, io, NULL);
 
 	return true;
 }
@@ -7279,9 +7267,21 @@ bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data)
 	return stream->lpac->type == BT_BAP_BCAST_SINK;
 }
 
-static bool stream_io_disconnected(struct io *io, void *user_data)
+static void stream_io_collect(void *data, void *user_data)
 {
-	struct bt_bap_stream *stream = user_data;
+	struct bt_bap_stream *stream = data;
+	void **args = user_data;
+
+	if (stream->io == args[0])
+		queue_push_tail(args[1], bt_bap_stream_ref(stream));
+}
+
+static void stream_io_lost(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+
+	if (!bap_stream_valid(stream))
+		return;
 
 	DBG(stream->bap, "stream %p io disconnected", stream);
 
@@ -7289,9 +7289,9 @@ static bool stream_io_disconnected(struct io *io, void *user_data)
 	 * and PA Sync have been lost so switch to idle state to cleanup the
 	 * stream.
 	 */
-	if (stream->lpac->type == BT_BAP_BCAST_SINK) {
+	if (stream->lpac && stream->lpac->type == BT_BAP_BCAST_SINK) {
 		stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
-		return false;
+		return;
 	}
 
 	switch (stream->ep->state) {
@@ -7304,8 +7304,42 @@ static bool stream_io_disconnected(struct io *io, void *user_data)
 		stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
 		break;
 	}
+}
+
+static void stream_io_clear(void *data, void *user_data)
+{
+	struct bt_bap_stream *stream = data;
+
+	if (bap_stream_valid(stream))
+		bt_bap_stream_set_io(stream, -1);
+}
+
+static bool stream_io_disconnected(struct io *io, void *user_data)
+{
+	struct bt_bap_stream_io *sio = user_data;
+	struct bt_bap *bap;
+	struct queue *streams;
+	void *args[2];
+
+	bap = bt_bap_ref_safe(sio->bap);
+	if (!bap)
+		return false;
+
+	/* Every stream using the CIS, e.g. a Sink and a Source ASE sharing
+	 * it, has to be cleaned up, and their state callbacks may release
+	 * them or change their links, so hold a reference to each of them.
+	 */
+	streams = queue_new();
+	args[0] = sio;
+	args[1] = streams;
+	queue_foreach(bap->streams, stream_io_collect, args);
+
+	queue_foreach(streams, stream_io_lost, NULL);
+	queue_foreach(streams, stream_io_clear, NULL);
+
+	queue_destroy(streams, bt_bap_stream_unref);
+	bt_bap_unref(bap);
 
-	bt_bap_stream_set_io(stream, -1);
 	return false;
 }
 
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2026-11-12  0:37 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 16:25 [PATCH BlueZ v2 0/4] shared/bap: Don't disconnect a CIS still used by another ASE Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 1/4] shared/bap: Don't disconnect a CIS still configured for a link Luiz Augusto von Dentz
2026-11-11 19:38   ` shared/bap: Don't disconnect a CIS still used by another ASE bluez.test.bot
2026-11-11 16:25 ` [PATCH BlueZ v2 2/4] unit/test-bap: Add tests for a CIS shared by two ASEs Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 3/4] doc/functional-bap: Document switching the direction of a CIS Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 4/4] test: functional: Cover " Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 0/6] bap: Release cached ASEs on ClearConfiguration Luiz Augusto von Dentz
2026-11-11 16:25   ` [PATCH BlueZ v2 1/6] shared/bap: Add bt_bap_foreach_stream and bt_bap_stream_get_rpac Luiz Augusto von Dentz
2026-11-11 19:27     ` bap: Release cached ASEs on ClearConfiguration bluez.test.bot
2026-11-11 16:25   ` [PATCH BlueZ v2 2/6] shared/bap: Update the remote PAC when reusing an ASE Luiz Augusto von Dentz
2026-11-11 16:25   ` [PATCH BlueZ v2 3/6] bap: Release cached ASEs on ClearConfiguration of an endpoint Luiz Augusto von Dentz
2026-11-11 16:25   ` [PATCH BlueZ v2 4/6] client/player: Add endpoint.clear command Luiz Augusto von Dentz
2026-11-11 16:25   ` [PATCH BlueZ v2 5/6] doc/functional-bap: Document clearing a cached configuration Luiz Augusto von Dentz
2026-11-11 16:25   ` [PATCH BlueZ v2 6/6] test: functional: Cover " Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 0/8] transport: Hand the owner over to a link still streaming Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 1/8] " Luiz Augusto von Dentz
2026-11-11 19:49   ` [BlueZ,v2,1/8] " bluez.test.bot
2026-11-11 16:25 ` [PATCH BlueZ v2 2/8] unit/test-bap: Add test for disabling one ASE of a shared CIS Luiz Augusto von Dentz
2026-11-11 18:09   ` [BlueZ,v2,2/8] " bluez.test.bot
2026-11-11 16:25 ` [PATCH BlueZ v2 3/8] transport: Clear the owner of desynchronized links when removing it Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 4/8] transport: Don't hand the owner over while releasing Luiz Augusto von Dentz
2026-11-11 18:09   ` [BlueZ,v2,4/8] " bluez.test.bot
2026-11-11 16:25 ` [PATCH BlueZ v2 5/8] doc/functional-bap: Document disabling one ASE of a shared owner Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 6/8] test: functional: Cover " Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 7/8] doc/functional-bap: Document releasing " Luiz Augusto von Dentz
2026-11-11 16:25 ` [PATCH BlueZ v2 8/8] test: functional: Cover " Luiz Augusto von Dentz
  -- strict thread matches above, loose matches on Subject: below --
2026-11-11 20:37 [PATCH BlueZ v3 1/5] shared/bap: Don't disconnect a CIS still configured for a link Luiz Augusto von Dentz
2026-11-12  0:37 ` shared/bap: Don't disconnect a CIS still used by another ASE bluez.test.bot
2026-11-10 22:18 [PATCH BlueZ v1 1/4] shared/bap: Don't disconnect a CIS still configured for a link Luiz Augusto von Dentz
2026-11-11  4:35 ` shared/bap: Don't disconnect a CIS still used by another ASE bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox