* [PATCH BlueZ v3 00/10] BAP stream reconfiguration
@ 2025-06-08 21:32 Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created Pauli Virtanen
` (10 more replies)
0 siblings, 11 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Implement ClearConfiguration() and Reconfigure() for BAP unicast
MediaEndpoints.
v3:
- Add bt_bap_stream_lock(), instead of bt_bap_stream_discard()
- Separate commit for "detach on RELEASING"
- Move "bap: do not try QoS before links are updated" first,
as it makes sense standalone
- Add "bap: don't show error when releasing stream"
- Add "bap: delay QoS & IO creation if CIG is busy or setups configuring"
This reworks the unicast stream creation/QoS/CIG activation logic, to
avoid doing operations that are impossible due to CIG state machine
rules, or lead to ID conflicts.
Unicast tested manually on:
- Sony Linkbuds S: switching between AC 6(ii), 8(ii), 11(ii)
- Earfun Air Pro 3: switching between AC 6(i), 11(i)
with corresponding Pipewire branch
https://gitlab.freedesktop.org/pvir/pipewire/-/commits/bap-codec-switch-select
Some more testing is probably warranted.
Broadcast has not been tested.
Pauli Virtanen (10):
bap: do not try QoS before links are updated & io created
shared/bap: detach ucast io on RELEASING and unlink streams
shared/bap: add client ASE reuse and upper level stream locking
bap: lock streams when used
bap: add ready callback for setup configuration
bap: support removing streams with ClearConfiguration()
bap: add callback at the end of ucast client select/config
bap: implement Reconfigure()
bap: don't show error when releasing stream
bap: delay QoS & IO creation if CIG is busy or setups configuring
profiles/audio/bap.c | 1085 ++++++++++++++++++++++++++++--------
profiles/audio/transport.c | 20 +-
profiles/audio/transport.h | 1 +
src/shared/bap.c | 168 ++++--
src/shared/bap.h | 3 +
5 files changed, 1005 insertions(+), 272 deletions(-)
--
2.49.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 22:55 ` BAP stream reconfiguration bluez.test.bot
2025-06-08 21:32 ` [PATCH BlueZ v3 02/10] shared/bap: detach ucast io on RELEASING and unlink streams Pauli Virtanen
` (9 subsequent siblings)
10 siblings, 1 reply; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
In setup config, QoS must be done after corresponding bap_state
callback, because stream links are updated only at that point. If the
ASE was in CONFIG state before reconfiguration, this gets done in wrong
order.
Track explicitly that bap_state() is done after bt_bap_stream_config(),
before proceeding to QoS.
---
profiles/audio/bap.c | 82 ++++++++++++++++++++++++++------------------
1 file changed, 49 insertions(+), 33 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index ee7c8bc49..b420354cd 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -75,6 +75,7 @@ struct bap_setup {
bool recreate;
bool cig_active;
uint8_t sid;
+ bool config_pending;
struct iovec *caps;
struct iovec *metadata;
unsigned int id;
@@ -720,6 +721,46 @@ static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason,
setup->msg = NULL;
}
+static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
+ struct bt_bap_stream *stream, int defer);
+
+static int setup_qos(struct bap_setup *setup)
+{
+ struct bap_data *data = setup->ep->data;
+ struct bt_bap_stream *stream = setup->stream;
+
+ if (!stream)
+ return -EINVAL;
+ if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_CONFIG)
+ goto error;
+ if (setup->id)
+ goto error;
+
+ setup_create_io(data, setup, stream, true);
+ if (!setup->io) {
+ error("Unable to create io");
+ goto error;
+ }
+
+ /* Wait QoS response to respond */
+ setup->id = bt_bap_stream_qos(stream, &setup->qos, qos_cb, setup);
+ if (!setup->id) {
+ error("Failed to Configure QoS");
+ goto error;
+ }
+
+ /* Bcast does not call the callback */
+ if (bt_bap_stream_get_type(setup->stream) == BT_BAP_STREAM_TYPE_BCAST)
+ setup->id = 0;
+
+ return 0;
+
+error:
+ if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_RELEASING)
+ bt_bap_stream_release(stream, NULL, NULL);
+ return -EIO;
+}
+
static void config_cb(struct bt_bap_stream *stream,
uint8_t code, uint8_t reason,
void *user_data)
@@ -732,17 +773,8 @@ static void config_cb(struct bt_bap_stream *stream,
setup->id = 0;
if (!code) {
- /* Check state is already set to config then proceed to qos */
- if (bt_bap_stream_get_state(stream) ==
- BT_BAP_STREAM_STATE_CONFIG) {
- setup->id = bt_bap_stream_qos(stream, &setup->qos,
- qos_cb, setup);
- if (!setup->id) {
- error("Failed to Configure QoS");
- bt_bap_stream_release(stream, NULL, NULL);
- }
- }
-
+ if (!setup->config_pending)
+ setup_qos(setup);
return;
}
@@ -984,6 +1016,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac,
&setup->qos, setup->caps);
bt_bap_stream_set_user_data(setup->stream, ep->path);
+ setup->config_pending = true;
setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
setup->caps, config_cb, setup);
if (!setup->id) {
@@ -1003,6 +1036,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
case BT_BAP_STREAM_TYPE_BCAST:
/* No message sent over the air for broadcast */
setup->id = 0;
+ setup->config_pending = false;
if (ep->data->service)
service_set_connecting(ep->data->service);
@@ -1401,6 +1435,7 @@ static void setup_config(void *data, void *user_data)
ep->rpac, &setup->qos,
setup->caps);
+ setup->config_pending = true;
setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
setup->caps, config_cb, setup);
if (!setup->id) {
@@ -1805,9 +1840,6 @@ static bool is_cig_busy(struct bap_data *data, uint8_t cig)
return queue_find(sessions, cig_busy_session, &info);
}
-static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
- struct bt_bap_stream *stream, int defer);
-
static gboolean setup_io_recreate(void *user_data)
{
struct bap_setup *setup = user_data;
@@ -2187,25 +2219,9 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state,
queue_remove(data->server_streams, stream);
break;
case BT_BAP_STREAM_STATE_CONFIG:
- if (setup && !setup->id) {
- setup_create_io(data, setup, stream, true);
- if (!setup->io) {
- error("Unable to create io");
- if (old_state != BT_BAP_STREAM_STATE_RELEASING)
- bt_bap_stream_release(stream, NULL,
- NULL);
- return;
- }
-
- /* Wait QoS response to respond */
- setup->id = bt_bap_stream_qos(stream,
- &setup->qos,
- qos_cb, setup);
- if (!setup->id) {
- error("Failed to Configure QoS");
- bt_bap_stream_release(stream,
- NULL, NULL);
- }
+ if (setup) {
+ setup->config_pending = false;
+ setup_qos(setup);
}
break;
case BT_BAP_STREAM_STATE_QOS:
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 02/10] shared/bap: detach ucast io on RELEASING and unlink streams
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 03/10] shared/bap: add client ASE reuse and upper level stream locking Pauli Virtanen
` (8 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
When RELEASING, ucast stream QoS becomes invalid and client stream
transport can no longer be acquired. Client shall close the CIS when
ASE is RELEASING (BAP v1.0.2 §5.6.6).
As client, detach IO when RELEASING. Clear the stream links, as the QoS
is no longer valid. Even if caching config the ASE may be reused for
different purpose, and stream will anyway be reconfigured & re-linked as
needed.
Also clear the stream transport, as it's not in acquirable state, and
its configuration may change after this. This makes BAP Client to have
existing transports only for streams that are >= QOS. (BAP Server is
not changed here.)
---
src/shared/bap.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index 854855f6b..fda1e3560 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -1508,6 +1508,13 @@ static void bap_stream_state_changed(struct bt_bap_stream *stream)
if (stream->client)
bt_bap_stream_stop(stream, stream_stop_complete, NULL);
break;
+ case BT_ASCS_ASE_STATE_RELEASING:
+ if (stream->client) {
+ bap_stream_clear_cfm(stream);
+ bap_stream_io_detach(stream);
+ bt_bap_stream_io_unlink(stream, NULL);
+ }
+ break;
}
}
@@ -2623,6 +2630,30 @@ static int bap_ucast_io_link(struct bt_bap_stream *stream,
return 0;
}
+static void stream_unlink_ucast(void *data)
+{
+ struct bt_bap_stream *link = data;
+
+ DBG(link->bap, "stream %p unlink", link);
+
+ queue_destroy(link->links, NULL);
+ link->links = NULL;
+}
+
+static int bap_ucast_io_unlink(struct bt_bap_stream *stream,
+ struct bt_bap_stream *link)
+{
+ if (!stream)
+ return -EINVAL;
+
+ queue_destroy(stream->links, stream_unlink_ucast);
+ stream->links = NULL;
+
+ DBG(stream->bap, "stream %p unlink", stream);
+ return 0;
+
+}
+
static void stream_link(void *data, void *user_data)
{
struct bt_bap_stream *stream = (void *)data;
@@ -2728,7 +2759,7 @@ static const struct bt_bap_stream_ops stream_ops[] = {
bap_ucast_release, bap_ucast_detach,
bap_ucast_set_io, bap_ucast_get_io,
bap_ucast_io_dir, bap_ucast_io_link,
- NULL),
+ bap_ucast_io_unlink),
STREAM_OPS(BT_BAP_SOURCE, bap_ucast_set_state,
bap_ucast_get_state,
bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
@@ -2738,7 +2769,7 @@ static const struct bt_bap_stream_ops stream_ops[] = {
bap_ucast_release, bap_ucast_detach,
bap_ucast_set_io, bap_ucast_get_io,
bap_ucast_io_dir, bap_ucast_io_link,
- NULL),
+ bap_ucast_io_unlink),
STREAM_OPS(BT_BAP_BCAST_SINK, bap_bcast_set_state,
bap_bcast_get_state,
bap_bcast_config, bap_bcast_qos, bap_bcast_sink_enable,
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 03/10] shared/bap: add client ASE reuse and upper level stream locking
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 02/10] shared/bap: detach ucast io on RELEASING and unlink streams Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 04/10] bap: lock streams when used Pauli Virtanen
` (7 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Change ucast client stream design so that:
* upper level locks streams to indicate which ones it is using
* unused streams are reused when upper level wants a new stream
* only locked streams are used for bidi CIS linking
* streams (still) correspond 1-to-1 to non-idle ASEs
This fixes some issues:
* bap_ucast_stream_new() could pick a stream upper level is already
using if lpac & rpac match (can occur with multi-stream AC 6(ii) etc)
* Avoids assuming ASE enters idle state at end of stream life cycle.
This is False for some devices like Sony headsets, which always cache
codec config so RELEASING -> CONFIG always, never RELEASING -> IDLE,
so ASE never go IDLE again.
* Allows reconfiguring an ASE with different codec in this case.
* Allows upper level to only QoS some of the streams.
Reconfiguring ASE in QOS/CONFIG state with different codec here results
to need_reconfig=true state, where ASE and stream configs do not match,
and upper level needs to do bt_bap_stream_config() to sync them.
---
src/shared/bap.c | 133 ++++++++++++++++++++++++++++++-----------------
src/shared/bap.h | 3 ++
2 files changed, 89 insertions(+), 47 deletions(-)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index fda1e3560..1c15a4ecb 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -296,6 +296,8 @@ struct bt_bap_stream {
struct queue *pending_states;
bool no_cache_config;
bool client;
+ bool locked;
+ bool need_reconfig;
void *user_data;
};
@@ -1925,6 +1927,9 @@ static unsigned int bap_ucast_qos(struct bt_bap_stream *stream,
if (!stream->client)
return 0;
+ if (stream->need_reconfig)
+ return 0;
+
memset(&qos, 0, sizeof(qos));
/* TODO: Figure out how to pass these values around */
@@ -2327,7 +2332,6 @@ static unsigned int bap_ucast_release(struct bt_bap_stream *stream,
/* If stream does not belong to a client session, clean it up now */
if (!bap_stream_valid(stream)) {
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
- stream = NULL;
return 0;
}
@@ -2610,6 +2614,9 @@ static int bap_ucast_io_link(struct bt_bap_stream *stream,
stream->ep->dir == link->ep->dir)
return -EINVAL;
+ if (stream->client && !(stream->locked && link->locked))
+ return -EINVAL;
+
if (!stream->links)
stream->links = queue_new();
@@ -5073,6 +5080,8 @@ static void ep_status_config(struct bt_bap *bap, struct bt_bap_endpoint *ep,
ep->stream->cc = new0(struct iovec, 1);
util_iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len);
+
+ ep->stream->need_reconfig = false;
}
static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err)
@@ -5980,43 +5989,6 @@ bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac)
return false;
}
-static bool find_ep_unused(const void *data, const void *user_data)
-{
- const struct bt_bap_endpoint *ep = data;
- const struct match_pac *match = user_data;
-
- if (ep->stream)
- return false;
-
- if (match->rpac)
- return ep->dir == match->rpac->type;
- else
- return true;
-}
-
-static bool find_ep_pacs(const void *data, const void *user_data)
-{
- const struct bt_bap_endpoint *ep = data;
- const struct match_pac *match = user_data;
-
- if (!ep->stream)
- return false;
-
- if (ep->stream->lpac != match->lpac)
- return false;
-
- if (ep->stream->rpac != match->rpac)
- return false;
-
- switch (ep->state) {
- case BT_BAP_STREAM_STATE_CONFIG:
- case BT_BAP_STREAM_STATE_QOS:
- return true;
- }
-
- return false;
-}
-
static bool find_ep_source(const void *data, const void *user_data)
{
const struct bt_bap_endpoint *ep = data;
@@ -6196,6 +6168,48 @@ static struct bt_bap_stream *bap_bcast_stream_new(struct bt_bap *bap,
return stream;
}
+static bool find_ep_ucast(const void *data, const void *user_data)
+{
+ const struct bt_bap_endpoint *ep = data;
+ const struct match_pac *match = user_data;
+
+ if (ep->stream) {
+ if (!ep->stream->client)
+ return false;
+ if (ep->stream->locked)
+ return false;
+ if (!queue_isempty(ep->stream->pending_states))
+ return false;
+
+ switch (ep->stream->state) {
+ case BT_BAP_STREAM_STATE_IDLE:
+ case BT_BAP_STREAM_STATE_CONFIG:
+ case BT_BAP_STREAM_STATE_QOS:
+ break;
+ default:
+ return false;
+ }
+ }
+
+ if (ep->dir != match->rpac->type)
+ return false;
+
+ switch (match->lpac->type) {
+ case BT_BAP_SOURCE:
+ if (ep->dir != BT_BAP_SINK)
+ return false;
+ break;
+ case BT_BAP_SINK:
+ if (ep->dir != BT_BAP_SOURCE)
+ return false;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
static struct bt_bap_stream *bap_ucast_stream_new(struct bt_bap *bap,
struct bt_bap_pac *lpac,
struct bt_bap_pac *rpac,
@@ -6213,20 +6227,26 @@ static struct bt_bap_stream *bap_ucast_stream_new(struct bt_bap *bap,
match.lpac = lpac;
match.rpac = rpac;
- /* Check for existing stream */
- ep = queue_find(bap->remote_eps, find_ep_pacs, &match);
+ /* Get free ASE */
+ ep = queue_find(bap->remote_eps, find_ep_ucast, &match);
if (!ep) {
- /* Check for unused ASE */
- ep = queue_find(bap->remote_eps, find_ep_unused, &match);
- if (!ep) {
- DBG(bap, "Unable to find unused ASE");
- return NULL;
- }
+ DBG(bap, "Unable to find usable ASE");
+ return NULL;
}
stream = ep->stream;
- if (!stream)
+ if (stream) {
+ /* Replace lpac: the stream generally needs to be reconfigured
+ * after this, otherwise things like codec config not match.
+ */
+ bap_stream_clear_cfm(stream);
+ stream->lpac = lpac;
+ util_iov_free(stream->cc, 1);
+ stream->cc = util_iov_dup(data, 1);
+ stream->need_reconfig = true;
+ } else {
stream = bap_stream_new(bap, ep, lpac, rpac, data, true);
+ }
return stream;
}
@@ -6247,6 +6267,25 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
return bap_bcast_stream_new(bap, lpac, pqos, data);
}
+void bt_bap_stream_lock(struct bt_bap_stream *stream)
+{
+ if (!stream || !stream->client)
+ return;
+
+ /* Reserve stream ASE for use by upper level, so it won't get
+ * reallocated
+ */
+ stream->locked = true;
+}
+
+void bt_bap_stream_unlock(struct bt_bap_stream *stream)
+{
+ if (!stream || !stream->client)
+ return;
+
+ stream->locked = false;
+}
+
struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream)
{
if (!stream)
diff --git a/src/shared/bap.h b/src/shared/bap.h
index d10581428..fba8b6b17 100644
--- a/src/shared/bap.h
+++ b/src/shared/bap.h
@@ -183,6 +183,9 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
struct bt_bap_qos *pqos,
struct iovec *data);
+void bt_bap_stream_lock(struct bt_bap_stream *stream);
+void bt_bap_stream_unlock(struct bt_bap_stream *stream);
+
struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream);
uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream);
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 04/10] bap: lock streams when used
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (2 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 03/10] shared/bap: add client ASE reuse and upper level stream locking Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 05/10] bap: add ready callback for setup configuration Pauli Virtanen
` (6 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Indicate to lower layer when we are using the streams.
When setup is freed, make sure the corresponding stream is released
after unlocking.
---
profiles/audio/bap.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index b420354cd..6be6ff8fd 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -861,6 +861,17 @@ static struct bap_setup *setup_new(struct bap_ep *ep)
return setup;
}
+static void release_stream(struct bt_bap_stream *stream)
+{
+ switch (bt_bap_stream_get_state(stream)) {
+ case BT_BAP_STREAM_STATE_IDLE:
+ case BT_BAP_STREAM_STATE_RELEASING:
+ break;
+ default:
+ bt_bap_stream_release(stream, NULL, NULL);
+ }
+}
+
static void setup_free(void *data)
{
struct bap_setup *setup = data;
@@ -892,6 +903,10 @@ static void setup_free(void *data)
if (setup->destroy)
setup->destroy(setup);
+ bt_bap_stream_unlock(setup->stream);
+
+ release_stream(setup->stream);
+
free(setup);
}
@@ -1015,6 +1030,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac,
&setup->qos, setup->caps);
+ bt_bap_stream_lock(setup->stream);
bt_bap_stream_set_user_data(setup->stream, ep->path);
setup->config_pending = true;
setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
@@ -1107,6 +1123,7 @@ static void create_stream_for_bis(struct bap_data *bap_data,
/* Create and configure stream */
setup->stream = bt_bap_stream_new(bap_data->bap,
lpac, NULL, &setup->qos, caps);
+ bt_bap_stream_lock(setup->stream);
setup->sid = sid;
bt_bap_stream_set_user_data(setup->stream, path);
@@ -1430,10 +1447,12 @@ static void setup_config(void *data, void *user_data)
/* TODO: Check if stream capabilities match add support for Latency
* and PHY.
*/
- if (!setup->stream)
+ if (!setup->stream) {
setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac,
ep->rpac, &setup->qos,
setup->caps);
+ bt_bap_stream_lock(setup->stream);
+ }
setup->config_pending = true;
setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 05/10] bap: add ready callback for setup configuration
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (3 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 04/10] bap: lock streams when used Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 06/10] bap: support removing streams with ClearConfiguration() Pauli Virtanen
` (5 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Operations like SetConfiguration need to wait until setup configuration
finishes. Abstract this to a setup_config() callback emitted on QoS
completion or failure, instead of hardcoding DBus reply.
---
profiles/audio/bap.c | 179 +++++++++++++++++++++++++++----------------
1 file changed, 112 insertions(+), 67 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 6be6ff8fd..b39314fa9 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -63,6 +63,11 @@
#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
#define MEDIA_INTERFACE "org.bluez.Media1"
+struct bap_setup;
+
+typedef void (*bap_setup_ready_func_t)(struct bap_setup *setup, int code,
+ uint8_t reason, void *data);
+
struct bap_setup {
struct bap_ep *ep;
struct bap_data *data;
@@ -76,11 +81,13 @@ struct bap_setup {
bool cig_active;
uint8_t sid;
bool config_pending;
+ bool readying;
struct iovec *caps;
struct iovec *metadata;
unsigned int id;
struct iovec *base;
- DBusMessage *msg;
+ bap_setup_ready_func_t ready_cb;
+ void *ready_cb_data;
void (*destroy)(struct bap_setup *setup);
};
@@ -116,6 +123,10 @@ struct bap_data {
static struct queue *sessions;
+static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
+ void *user_data);
+
+
static bool bap_data_set_user_data(struct bap_data *data, void *user_data)
{
if (!data)
@@ -697,28 +708,32 @@ fail:
return -EINVAL;
}
+static void setup_ready(struct bap_setup *setup, int code,
+ uint8_t reason)
+{
+ if (!setup->readying)
+ return;
+
+ setup->readying = false;
+
+ if (setup->ready_cb) {
+ setup->ready_cb(setup, code, reason, setup->ready_cb_data);
+ setup->ready_cb = NULL;
+ setup->ready_cb_data = NULL;
+ }
+}
+
static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason,
void *user_data)
{
struct bap_setup *setup = user_data;
- DBusMessage *reply;
DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason);
setup->id = 0;
- if (!setup->msg)
- return;
-
- if (!code)
- reply = dbus_message_new_method_return(setup->msg);
- else
- reply = btd_error_failed(setup->msg, "Unable to configure");
-
- g_dbus_send_message(btd_get_dbus_connection(), reply);
-
- dbus_message_unref(setup->msg);
- setup->msg = NULL;
+ if (code)
+ setup_ready(setup, code, reason);
}
static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
@@ -766,26 +781,19 @@ static void config_cb(struct bt_bap_stream *stream,
void *user_data)
{
struct bap_setup *setup = user_data;
- DBusMessage *reply;
+ int err = 0;
DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason);
setup->id = 0;
- if (!code) {
- if (!setup->config_pending)
- setup_qos(setup);
- return;
- }
+ if (code)
+ err = code;
+ else if (!setup->config_pending)
+ err = setup_qos(setup);
- if (!setup->msg)
- return;
-
- reply = btd_error_failed(setup->msg, "Unable to configure");
- g_dbus_send_message(btd_get_dbus_connection(), reply);
-
- dbus_message_unref(setup->msg);
- setup->msg = NULL;
+ if (err)
+ setup_ready(setup, err, reason);
}
static void setup_io_close(void *data, void *user_data)
@@ -875,22 +883,16 @@ static void release_stream(struct bt_bap_stream *stream)
static void setup_free(void *data)
{
struct bap_setup *setup = data;
- DBusMessage *reply;
DBG("%p", setup);
+ setup_ready(setup, -ECANCELED, 0);
+
if (setup->stream && setup->id) {
bt_bap_stream_cancel(setup->stream, setup->id);
setup->id = 0;
}
- if (setup->msg) {
- reply = btd_error_failed(setup->msg, "Canceled");
- g_dbus_send_message(btd_get_dbus_connection(), reply);
- dbus_message_unref(setup->msg);
- setup->msg = NULL;
- }
-
if (setup->ep)
queue_remove(setup->ep->setups, setup);
@@ -987,6 +989,29 @@ static bool setup_mismatch_qos(const void *data, const void *user_data)
return !match_bcast_qos(&setup->qos.bcast, &match->qos.bcast);
}
+struct set_configuration_data {
+ struct bap_setup *setup;
+ DBusMessage *msg;
+};
+
+static void set_configuration_ready(struct bap_setup *setup, int code,
+ uint8_t reason, void *user_data)
+{
+ struct set_configuration_data *data = user_data;
+ DBusMessage *reply;
+
+ if (!code)
+ reply = dbus_message_new_method_return(data->msg);
+ else if (code == -ECANCELED)
+ reply = btd_error_failed(data->msg, "Canceled");
+ else
+ reply = btd_error_failed(data->msg, "Unable to configure");
+
+ g_dbus_send_message(btd_get_dbus_connection(), reply);
+ dbus_message_unref(data->msg);
+ free(data);
+}
+
static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -994,6 +1019,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
struct bap_setup *setup;
const char *path;
DBusMessageIter args, props;
+ struct set_configuration_data *cbdata;
dbus_message_iter_init(msg, &args);
@@ -1028,36 +1054,23 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
return btd_error_invalid_args(msg);
}
- setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac,
- &setup->qos, setup->caps);
- bt_bap_stream_lock(setup->stream);
- bt_bap_stream_set_user_data(setup->stream, ep->path);
- setup->config_pending = true;
- setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
- setup->caps, config_cb, setup);
- if (!setup->id) {
+ cbdata = new0(struct set_configuration_data, 1);
+ cbdata->setup = setup;
+
+ if (setup_config(setup, set_configuration_ready, cbdata)) {
DBG("Unable to config stream");
setup_free(setup);
+ free(cbdata);
return btd_error_invalid_args(msg);
}
- if (setup->metadata && setup->metadata->iov_len)
- bt_bap_stream_metadata(setup->stream, setup->metadata, NULL,
- NULL);
+ cbdata->msg = dbus_message_ref(msg);
switch (bt_bap_stream_get_type(setup->stream)) {
- case BT_BAP_STREAM_TYPE_UCAST:
- setup->msg = dbus_message_ref(msg);
- break;
case BT_BAP_STREAM_TYPE_BCAST:
- /* No message sent over the air for broadcast */
- setup->id = 0;
- setup->config_pending = false;
-
if (ep->data->service)
service_set_connecting(ep->data->service);
-
- return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+ break;
}
return NULL;
@@ -1436,11 +1449,14 @@ static struct bap_ep *ep_register(struct btd_service *service,
return ep;
}
-static void setup_config(void *data, void *user_data)
+static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
+ void *user_data)
{
- struct bap_setup *setup = data;
struct bap_ep *ep = setup->ep;
+ if (setup->readying)
+ return -EBUSY;
+
DBG("setup %p caps %p metadata %p", setup, setup->caps,
setup->metadata);
@@ -1454,27 +1470,49 @@ static void setup_config(void *data, void *user_data)
bt_bap_stream_lock(setup->stream);
}
- setup->config_pending = true;
+ bt_bap_stream_set_user_data(setup->stream, ep->path);
setup->id = bt_bap_stream_config(setup->stream, &setup->qos,
setup->caps, config_cb, setup);
- if (!setup->id) {
- DBG("Unable to config stream");
- setup_free(setup);
- return;
+ if (!setup->id)
+ return -EINVAL;
+
+ switch (bt_bap_stream_get_type(setup->stream)) {
+ case BT_BAP_STREAM_TYPE_UCAST:
+ setup->config_pending = true;
+ break;
+ case BT_BAP_STREAM_TYPE_BCAST:
+ /* Broadcast does not call the callback */
+ setup->id = 0;
+ break;
}
if (setup->metadata && setup->metadata->iov_len)
bt_bap_stream_metadata(setup->stream, setup->metadata, NULL,
NULL);
- bt_bap_stream_set_user_data(setup->stream, ep->path);
+ setup->readying = true;
+ setup->ready_cb = cb;
+ setup->ready_cb_data = user_data;
+
+ return 0;
+}
+
+static void bap_config_setup(void *data, void *user_data)
+{
+ struct bap_setup *setup = data;
+
+ if (setup_config(setup, NULL, NULL)) {
+ DBG("Unable to config stream");
+ setup_free(setup);
+ return;
+ }
}
static void bap_config(void *data, void *user_data)
{
struct bap_ep *ep = data;
- queue_foreach(ep->setups, setup_config, NULL);
+ queue_foreach(ep->setups, bap_config_setup, NULL);
}
static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps,
@@ -2240,11 +2278,18 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state,
case BT_BAP_STREAM_STATE_CONFIG:
if (setup) {
setup->config_pending = false;
- setup_qos(setup);
+ if (!setup->id) {
+ int err = setup_qos(setup);
+
+ if (err)
+ setup_ready(setup, err, 0);
+ }
}
break;
case BT_BAP_STREAM_STATE_QOS:
- setup_create_io(data, setup, stream, true);
+ setup_create_io(data, setup, stream, true);
+ if (setup)
+ setup_ready(setup, 0, 0);
break;
case BT_BAP_STREAM_STATE_ENABLING:
if (setup)
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 06/10] bap: support removing streams with ClearConfiguration()
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (4 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 05/10] bap: add ready callback for setup configuration Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 07/10] bap: add callback at the end of ucast client select/config Pauli Virtanen
` (4 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Implement removing streams via ClearConfiguration().
---
profiles/audio/bap.c | 184 +++++++++++++++++++++++++++++++++----
profiles/audio/transport.c | 17 ++++
profiles/audio/transport.h | 1 +
3 files changed, 185 insertions(+), 17 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index b39314fa9..648acbf30 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -57,6 +57,8 @@
#include "src/log.h"
#include "src/error.h"
+#include "transport.h"
+
#define ISO_SOCKET_UUID "6fbaf188-05e0-496a-9885-d6ddfdb4e03e"
#define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb"
#define BCAAS_UUID_STR "00001852-0000-1000-8000-00805f9b34fb"
@@ -67,6 +69,7 @@ struct bap_setup;
typedef void (*bap_setup_ready_func_t)(struct bap_setup *setup, int code,
uint8_t reason, void *data);
+typedef void (*bap_setup_close_func_t)(struct bap_setup *setup, void *data);
struct bap_setup {
struct bap_ep *ep;
@@ -82,12 +85,15 @@ struct bap_setup {
uint8_t sid;
bool config_pending;
bool readying;
+ bool closing;
struct iovec *caps;
struct iovec *metadata;
unsigned int id;
struct iovec *base;
bap_setup_ready_func_t ready_cb;
void *ready_cb_data;
+ bap_setup_close_func_t close_cb;
+ void *close_cb_data;
void (*destroy)(struct bap_setup *setup);
};
@@ -746,6 +752,8 @@ static int setup_qos(struct bap_setup *setup)
if (!stream)
return -EINVAL;
+ if (setup->closing)
+ return -EINVAL;
if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_CONFIG)
goto error;
if (setup->id)
@@ -822,12 +830,103 @@ static void setup_io_close(void *data, void *user_data)
bt_bap_stream_io_connecting(setup->stream, -1);
}
-static void ep_close(struct bap_ep *ep)
+static bool release_stream(struct bt_bap_stream *stream)
{
- if (!ep)
+ if (!stream)
+ return true;
+
+ switch (bt_bap_stream_get_state(stream)) {
+ case BT_BAP_STREAM_STATE_IDLE:
+ case BT_BAP_STREAM_STATE_RELEASING:
+ return true;
+ default:
+ bt_bap_stream_release(stream, NULL, NULL);
+ return false;
+ }
+}
+
+static int setup_close(struct bap_setup *setup, bap_setup_close_func_t cb,
+ void *user_data)
+{
+ if (setup->closing)
+ return -EBUSY;
+
+ DBG("%p", setup);
+
+ setup->close_cb = cb;
+ setup->close_cb_data = user_data;
+ setup->closing = true;
+
+ bt_bap_stream_unlock(setup->stream);
+
+ if (release_stream(setup->stream)) {
+ setup_free(setup);
+ return 0;
+ }
+
+ return 0;
+}
+
+struct ep_close_data {
+ int remaining;
+ int count;
+ const char *path;
+ void (*cb)(int count, void *user_data);
+ void *user_data;
+};
+
+static void ep_close_setup_cb(struct bap_setup *setup, void *user_data)
+{
+ struct ep_close_data *epdata = user_data;
+
+ epdata->remaining--;
+
+ DBG("closed setup %p remain %d", setup, epdata->remaining);
+
+ if (epdata->remaining)
return;
- queue_foreach(ep->setups, setup_io_close, NULL);
+ if (epdata->cb)
+ epdata->cb(epdata->count, epdata->user_data);
+
+ free(epdata);
+}
+
+static void ep_close_setup(void *data, void *user_data)
+{
+ struct bap_setup *setup = data;
+ struct ep_close_data *epdata = user_data;
+ struct bt_bap_stream *stream = setup->stream;
+ const char *path = media_transport_stream_path(stream);
+
+ if (epdata->path && (!path || strcmp(epdata->path, path)))
+ return;
+
+ epdata->remaining++;
+ if (setup_close(setup, ep_close_setup_cb, epdata))
+ epdata->remaining--;
+ else
+ epdata->count++;
+}
+
+static void ep_close(struct bap_ep *ep, const char *transport_path,
+ void (*cb)(int count, void *user_data), void *user_data)
+{
+ struct ep_close_data *epdata;
+
+ DBG("close ep %p path %s", ep, transport_path ? transport_path : "-");
+
+ epdata = new0(struct ep_close_data, 1);
+ epdata->cb = cb;
+ epdata->path = transport_path;
+ epdata->user_data = user_data;
+ epdata->remaining = 1;
+
+ if (ep)
+ queue_foreach(ep->setups, ep_close_setup, epdata);
+
+ epdata->path = NULL;
+ ep_close_setup_cb(NULL, epdata);
}
static struct bap_setup *setup_new(struct bap_ep *ep)
@@ -869,17 +968,6 @@ static struct bap_setup *setup_new(struct bap_ep *ep)
return setup;
}
-static void release_stream(struct bt_bap_stream *stream)
-{
- switch (bt_bap_stream_get_state(stream)) {
- case BT_BAP_STREAM_STATE_IDLE:
- case BT_BAP_STREAM_STATE_RELEASING:
- break;
- default:
- bt_bap_stream_release(stream, NULL, NULL);
- }
-}
-
static void setup_free(void *data)
{
struct bap_setup *setup = data;
@@ -888,6 +976,9 @@ static void setup_free(void *data)
setup_ready(setup, -ECANCELED, 0);
+ if (setup->closing && setup->close_cb)
+ setup->close_cb(setup, setup->close_cb_data);
+
if (setup->stream && setup->id) {
bt_bap_stream_cancel(setup->stream, setup->id);
setup->id = 0;
@@ -1035,7 +1126,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
* TO DO reconfiguration of a BIS.
*/
if (bt_bap_pac_get_type(ep->lpac) != BT_BAP_BCAST_SOURCE)
- ep_close(ep);
+ ep_close(ep, NULL, NULL, NULL);
setup = setup_new(ep);
@@ -1076,6 +1167,51 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
return NULL;
}
+struct clear_configuration_data {
+ DBusMessage *msg;
+ bool all;
+};
+
+static void clear_configuration_cb(int count, void *user_data)
+{
+ struct clear_configuration_data *data = user_data;
+ DBusMessage *reply;
+
+ DBG("%p", data);
+
+ if (!data->all && count == 0)
+ reply = btd_error_invalid_args(data->msg);
+ else
+ reply = dbus_message_new_method_return(data->msg);
+
+ g_dbus_send_message(btd_get_dbus_connection(), reply);
+ dbus_message_unref(data->msg);
+ free(data);
+}
+
+static DBusMessage *clear_configuration(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct bap_ep *ep = data;
+ const char *path;
+ struct clear_configuration_data *cbdata;
+ DBusMessageIter args;
+
+ dbus_message_iter_init(msg, &args);
+ dbus_message_iter_get_basic(&args, &path);
+
+ if (strcmp(path, ep->path) == 0)
+ path = NULL;
+
+ cbdata = new0(struct clear_configuration_data, 1);
+ cbdata->msg = dbus_message_ref(msg);
+ cbdata->all = (path == NULL);
+
+ DBG("%p %s %s", cbdata, ep->path, path ? path : "NULL");
+ ep_close(ep, path, clear_configuration_cb, cbdata);
+ return NULL;
+}
+
static bool stream_io_unset(const void *data, const void *user_data)
{
struct bt_bap_stream *stream = (struct bt_bap_stream *)data;
@@ -1249,6 +1385,9 @@ static const GDBusMethodTable ep_methods[] = {
GDBUS_ARGS({ "endpoint", "o" },
{ "Configuration", "a{sv}" } ),
NULL, set_configuration) },
+ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ClearConfiguration",
+ GDBUS_ARGS({ "transport", "o" }),
+ NULL, clear_configuration) },
{ },
};
@@ -1259,10 +1398,9 @@ static void ep_free(void *data)
struct bap_ep *ep = data;
struct queue *setups = ep->setups;
- ep_cancel_select(ep);
-
ep->setups = NULL;
queue_destroy(setups, setup_free);
+ ep_cancel_select(ep);
free(ep->path);
free(ep);
}
@@ -1456,6 +1594,8 @@ static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
if (setup->readying)
return -EBUSY;
+ if (setup->closing)
+ return -EINVAL;
DBG("setup %p caps %p metadata %p", setup, setup->caps,
setup->metadata);
@@ -2235,6 +2375,9 @@ static void setup_create_bcast_io(struct bap_data *data,
static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
struct bt_bap_stream *stream, int defer)
{
+ if (setup && setup->closing)
+ return;
+
DBG("setup %p stream %p defer %s", setup, stream,
defer ? "true" : "false");
@@ -2267,6 +2410,13 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state,
setup = bap_find_setup_by_stream(data, stream);
+ if (setup && setup->closing) {
+ if (old_state == BT_BAP_STREAM_STATE_RELEASING) {
+ setup_free(setup);
+ return;
+ }
+ }
+
switch (new_state) {
case BT_BAP_STREAM_STATE_IDLE:
/* Release stream if idle */
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index a1fdf948b..244d2c4ae 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -2670,3 +2670,20 @@ void media_transport_update_device_volume(struct btd_device *dev,
btd_device_set_volume(dev, volume);
}
+
+const char *media_transport_stream_path(void *stream)
+{
+ GSList *l;
+
+ if (!stream)
+ return NULL;
+
+ for (l = transports; l; l = l->next) {
+ struct media_transport *transport = l->data;
+
+ if (media_transport_get_stream(transport) == stream)
+ return transport->path;
+ }
+
+ return NULL;
+}
diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h
index 808e1a193..7c107281a 100644
--- a/profiles/audio/transport.h
+++ b/profiles/audio/transport.h
@@ -33,3 +33,4 @@ void transport_get_properties(struct media_transport *transport,
int media_transport_get_device_volume(struct btd_device *dev);
void media_transport_update_device_volume(struct btd_device *dev,
int volume);
+const char *media_transport_stream_path(void *stream);
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 07/10] bap: add callback at the end of ucast client select/config
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (5 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 06/10] bap: support removing streams with ClearConfiguration() Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 08/10] bap: implement Reconfigure() Pauli Virtanen
` (3 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Restructure pac_select() and add a callback that is called when all
setups reach QoS.
---
profiles/audio/bap.c | 172 +++++++++++++++++++++++++++++++++----------
1 file changed, 133 insertions(+), 39 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 648acbf30..c08a0a645 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -70,6 +70,7 @@ struct bap_setup;
typedef void (*bap_setup_ready_func_t)(struct bap_setup *setup, int code,
uint8_t reason, void *data);
typedef void (*bap_setup_close_func_t)(struct bap_setup *setup, void *data);
+typedef void (*bap_select_done_t)(int err, void *data);
struct bap_setup {
struct bap_ep *ep;
@@ -97,6 +98,15 @@ struct bap_setup {
void (*destroy)(struct bap_setup *setup);
};
+struct bap_select {
+ struct bap_data *data;
+ struct queue *eps;
+ int remaining;
+ int err;
+ bap_select_done_t done_cb;
+ void *done_cb_data;
+};
+
struct bap_ep {
char *path;
struct bap_data *data;
@@ -106,6 +116,7 @@ struct bap_ep {
uint16_t supported_context;
uint16_t context;
struct queue *setups;
+ struct bap_select *select;
};
struct bap_data {
@@ -123,7 +134,6 @@ struct bap_data {
struct queue *server_streams;
GIOChannel *listen_io;
unsigned int io_id;
- int selecting;
void *user_data;
};
@@ -1637,14 +1647,37 @@ static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
return 0;
}
-static void bap_config_setup(void *data, void *user_data)
+static void bap_config_setup_cb(struct bap_setup *setup, int code,
+ uint8_t reason, void *user_data)
{
- struct bap_setup *setup = data;
+ struct bap_select *select = user_data;
- if (setup_config(setup, NULL, NULL)) {
+ select->remaining--;
+
+ DBG("setup %p code %d remain %d", setup, code, select->remaining);
+
+ if (code)
+ select->err = code;
+
+ if (select->remaining)
+ return;
+
+ if (select->done_cb)
+ select->done_cb(select->err, select->done_cb_data);
+
+ free(select);
+}
+
+static void bap_config_setup(void *item, void *user_data)
+{
+ struct bap_setup *setup = item;
+ struct bap_select *select = user_data;
+
+ select->remaining++;
+ if (setup_config(setup, bap_config_setup_cb, select)) {
DBG("Unable to config stream");
setup_free(setup);
- return;
+ select->remaining--;
}
}
@@ -1652,7 +1685,37 @@ static void bap_config(void *data, void *user_data)
{
struct bap_ep *ep = data;
- queue_foreach(ep->setups, bap_config_setup, NULL);
+ queue_foreach(ep->setups, bap_config_setup, user_data);
+}
+
+static void pac_select_clear_ep(void *data)
+{
+ struct bap_ep *ep = data;
+
+ ep->select = NULL;
+}
+
+static void bap_select_complete_select(struct bap_select *select)
+{
+ select->remaining--;
+
+ DBG("selecting %d", select->remaining);
+
+ if (select->remaining)
+ return;
+
+ DBG("configure (err %d)", select->err);
+
+ queue_destroy(select->eps, pac_select_clear_ep);
+
+ select->remaining++;
+
+ if (!select->err) {
+ queue_foreach(select->data->srcs, bap_config, select);
+ queue_foreach(select->data->snks, bap_config, select);
+ }
+
+ bap_config_setup_cb(NULL, 0, 0, select);
}
static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps,
@@ -1660,11 +1723,11 @@ static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps,
void *user_data)
{
struct bap_ep *ep = user_data;
+ struct bap_select *select = ep->select;
struct bap_setup *setup;
if (err) {
error("err %d", err);
- ep->data->selecting--;
goto done;
}
@@ -1673,38 +1736,15 @@ static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps,
setup->metadata = util_iov_dup(metadata, 1);
setup->qos = *qos;
- DBG("selecting %d", ep->data->selecting);
- ep->data->selecting--;
-
done:
- if (ep->data->selecting)
- return;
-
- queue_foreach(ep->data->srcs, bap_config, NULL);
- queue_foreach(ep->data->snks, bap_config, NULL);
- queue_foreach(ep->data->bcast, bap_config, NULL);
-}
-
-static bool pac_register(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
- void *user_data)
-{
- struct btd_service *service = user_data;
- struct bap_ep *ep;
-
- DBG("lpac %p rpac %p", lpac, rpac);
-
- ep = ep_register(service, lpac, rpac);
- if (!ep)
- error("Unable to register endpoint for pac %p", rpac);
-
- return true;
+ bap_select_complete_select(select);
}
static bool pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
void *user_data)
{
- struct btd_service *service = user_data;
- struct bap_data *data = btd_service_get_user_data(service);
+ struct bap_select *select = user_data;
+ struct bap_data *data = select->data;
struct match_ep match = { lpac, rpac };
struct queue *queue;
struct bap_ep *ep;
@@ -1726,9 +1766,57 @@ static bool pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
return true;
}
+ if (ep->select && ep->select != select) {
+ select->err = -EBUSY;
+ return true;
+ }
+
/* TODO: Cache LRU? */
- if (btd_service_is_initiator(service))
- bt_bap_select(lpac, rpac, &ep->data->selecting, select_cb, ep);
+
+ if (!ep->select) {
+ ep->select = select;
+ queue_push_tail(select->eps, ep);
+ }
+
+ bt_bap_select(lpac, rpac, &select->remaining, select_cb, ep);
+
+ return true;
+}
+
+static int bap_select_all(struct bap_data *data, bap_select_done_t cb,
+ void *user_data)
+{
+ struct bap_select *select;
+
+ if (!btd_service_is_initiator(data->service))
+ return -EINVAL;
+
+ select = new0(struct bap_select, 1);
+ select->remaining = 1;
+ select->data = data;
+ select->eps = queue_new();
+ select->done_cb = cb;
+ select->done_cb_data = user_data;
+
+ bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_select, select);
+ bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_select, select);
+
+ bap_select_complete_select(select);
+
+ return 0;
+}
+
+static bool pac_register(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
+ void *user_data)
+{
+ struct btd_service *service = user_data;
+ struct bap_ep *ep;
+
+ DBG("lpac %p rpac %p", lpac, rpac);
+
+ ep = ep_register(service, lpac, rpac);
+ if (!ep)
+ error("Unable to register endpoint for pac %p", rpac);
return true;
}
@@ -1746,9 +1834,16 @@ static bool pac_cancel_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
static void ep_cancel_select(struct bap_ep *ep)
{
struct bt_bap *bap = ep->data->bap;
+ struct bap_select *select;
bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_cancel_select, ep);
bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_cancel_select, ep);
+
+ select = ep->select;
+ if (select) {
+ queue_remove(select->eps, ep);
+ ep->select = NULL;
+ }
}
static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
@@ -1775,6 +1870,7 @@ static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
static void bap_ready(struct bt_bap *bap, void *user_data)
{
struct btd_service *service = user_data;
+ struct bap_data *data = btd_service_get_user_data(service);
DBG("bap %p", bap);
@@ -1784,8 +1880,7 @@ static void bap_ready(struct bt_bap *bap, void *user_data)
bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_register, service);
bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_register, service);
- bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_select, service);
- bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_select, service);
+ bap_select_all(data, NULL, NULL);
}
static bool match_setup_stream(const void *data, const void *user_data)
@@ -2742,8 +2837,7 @@ static void pac_added(struct bt_bap_pac *pac, void *user_data)
bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_register, service);
bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_register, service);
- bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_select, service);
- bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_select, service);
+ bap_select_all(data, NULL, NULL);
}
static void pac_added_broadcast(struct bt_bap_pac *pac, void *user_data)
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 08/10] bap: implement Reconfigure()
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (6 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 07/10] bap: add callback at the end of ucast client select/config Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 09/10] bap: don't show error when releasing stream Pauli Virtanen
` (2 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add Reconfigure() on a BAP unicast endpoint, which triggers its
reconfiguration or marks it for reconfiguration.
First, all associated streams are closed. After that, endpoints marked
for reconfiguration are reconfigured using the same flow as in the
initial configuration.
---
profiles/audio/bap.c | 151 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 146 insertions(+), 5 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index c08a0a645..2d8b7aa91 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -101,6 +101,7 @@ struct bap_setup {
struct bap_select {
struct bap_data *data;
struct queue *eps;
+ bool reconfigure;
int remaining;
int err;
bap_select_done_t done_cb;
@@ -117,6 +118,7 @@ struct bap_ep {
uint16_t context;
struct queue *setups;
struct bap_select *select;
+ bool reconfigure;
};
struct bap_data {
@@ -141,7 +143,8 @@ static struct queue *sessions;
static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
void *user_data);
-
+static int bap_select_all(struct bap_data *data, bool reconfigure,
+ bap_select_done_t cb, void *user_data);
static bool bap_data_set_user_data(struct bap_data *data, void *user_data)
{
@@ -1222,6 +1225,132 @@ static DBusMessage *clear_configuration(DBusConnection *conn, DBusMessage *msg,
return NULL;
}
+static int reconfigure_parse(DBusMessageIter *props, bool *defer)
+{
+ const char *key;
+
+ if (dbus_message_iter_get_arg_type(props) != DBUS_TYPE_DICT_ENTRY)
+ return -EINVAL;
+
+ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter value, entry;
+ int var;
+
+ dbus_message_iter_recurse(props, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ var = dbus_message_iter_get_arg_type(&value);
+
+ if (!strcasecmp(key, "Defer")) {
+ dbus_bool_t flag;
+
+ if (var != DBUS_TYPE_BOOLEAN)
+ goto fail;
+
+ dbus_message_iter_get_basic(&value, &flag);
+ *defer = flag;
+ }
+
+ dbus_message_iter_next(props);
+ }
+
+ return 0;
+
+fail:
+ DBG("Failed parsing %s", key);
+
+ return -EINVAL;
+}
+
+struct reconfigure_data {
+ int remaining;
+ struct bap_data *data;
+ DBusMessage *msg;
+};
+
+static void reconfigure_select_cb(int err, void *user_data)
+{
+ struct reconfigure_data *data = user_data;
+ DBusMessage *reply;
+
+ if (!err)
+ reply = dbus_message_new_method_return(data->msg);
+ else
+ reply = btd_error_failed(data->msg, "Failed to configure");
+
+ g_dbus_send_message(btd_get_dbus_connection(), reply);
+ dbus_message_unref(data->msg);
+ free(data);
+}
+
+static void reconfigure_close_cb(int count, void *user_data)
+{
+ struct reconfigure_data *data = user_data;
+
+ data->remaining--;
+
+ DBG("remain %d", data->remaining);
+
+ if (data->remaining)
+ return;
+
+ bap_select_all(data->data, true, reconfigure_select_cb, data);
+}
+
+static void ep_close_if_reconfigure(void *obj, void *user_data)
+{
+ struct bap_ep *ep = obj;
+ struct reconfigure_data *data = user_data;
+
+ if (ep->reconfigure) {
+ data->remaining++;
+ ep_close(ep, NULL, reconfigure_close_cb, data);
+ }
+}
+
+static DBusMessage *reconfigure(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct bap_ep *ep = user_data;
+ struct bap_data *data = ep->data;
+ struct reconfigure_data *cbdata;
+ bool defer = false;
+ DBusMessageIter args, props;
+
+ switch (bt_bap_pac_get_type(ep->lpac)) {
+ case BT_BAP_SOURCE:
+ case BT_BAP_SINK:
+ break;
+ default:
+ return btd_error_invalid_args(msg);
+ }
+
+ dbus_message_iter_init(msg, &args);
+ dbus_message_iter_recurse(&args, &props);
+ if (reconfigure_parse(&props, &defer))
+ return btd_error_invalid_args(msg);
+
+ DBG("%s defer %d", ep->path, (int)defer);
+
+ ep->reconfigure = true;
+ if (defer)
+ return dbus_message_new_method_return(msg);
+
+ cbdata = new0(struct reconfigure_data, 1);
+ cbdata->data = ep->data;
+ cbdata->msg = dbus_message_ref(msg);
+ cbdata->remaining = 1;
+
+ queue_foreach(data->snks, ep_close_if_reconfigure, cbdata);
+ queue_foreach(data->srcs, ep_close_if_reconfigure, cbdata);
+
+ reconfigure_close_cb(0, cbdata);
+ return NULL;
+}
+
static bool stream_io_unset(const void *data, const void *user_data)
{
struct bt_bap_stream *stream = (struct bt_bap_stream *)data;
@@ -1398,6 +1527,10 @@ static const GDBusMethodTable ep_methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("ClearConfiguration",
GDBUS_ARGS({ "transport", "o" }),
NULL, clear_configuration) },
+ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Reconfigure",
+ GDBUS_ARGS(
+ { "properties", "a{sv}" }),
+ NULL, reconfigure) },
{ },
};
@@ -1771,6 +1904,11 @@ static bool pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
return true;
}
+ if (select->reconfigure && !ep->reconfigure)
+ return true;
+
+ ep->reconfigure = false;
+
/* TODO: Cache LRU? */
if (!ep->select) {
@@ -1783,15 +1921,18 @@ static bool pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
return true;
}
-static int bap_select_all(struct bap_data *data, bap_select_done_t cb,
- void *user_data)
+static int bap_select_all(struct bap_data *data, bool reconfigure,
+ bap_select_done_t cb, void *user_data)
{
struct bap_select *select;
if (!btd_service_is_initiator(data->service))
return -EINVAL;
+ DBG("data %p reconfig %d", data, (int)reconfigure);
+
select = new0(struct bap_select, 1);
+ select->reconfigure = reconfigure;
select->remaining = 1;
select->data = data;
select->eps = queue_new();
@@ -1880,7 +2021,7 @@ static void bap_ready(struct bt_bap *bap, void *user_data)
bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_register, service);
bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_register, service);
- bap_select_all(data, NULL, NULL);
+ bap_select_all(data, false, NULL, NULL);
}
static bool match_setup_stream(const void *data, const void *user_data)
@@ -2837,7 +2978,7 @@ static void pac_added(struct bt_bap_pac *pac, void *user_data)
bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_register, service);
bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_register, service);
- bap_select_all(data, NULL, NULL);
+ bap_select_all(data, false, NULL, NULL);
}
static void pac_added_broadcast(struct bt_bap_pac *pac, void *user_data)
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 09/10] bap: don't show error when releasing stream
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (7 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 08/10] bap: implement Reconfigure() Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring Pauli Virtanen
2025-06-09 19:05 ` [PATCH BlueZ v3 00/10] BAP stream reconfiguration patchwork-bot+bluetooth
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
When stream is released, just set transport to not playing and don't
show error about missing io.
---
profiles/audio/transport.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 244d2c4ae..9bf3b47ee 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -2147,7 +2147,8 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state,
case BT_BAP_STREAM_STATE_RELEASING:
if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SINK)
return;
- break;
+ transport_update_playing(transport, FALSE);
+ goto done;
}
io = bt_bap_stream_get_io(stream);
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH BlueZ v3 10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (8 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 09/10] bap: don't show error when releasing stream Pauli Virtanen
@ 2025-06-08 21:32 ` Pauli Virtanen
2025-06-09 19:05 ` [PATCH BlueZ v3 00/10] BAP stream reconfiguration patchwork-bot+bluetooth
10 siblings, 0 replies; 14+ messages in thread
From: Pauli Virtanen @ 2025-06-08 21:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Unicast Client IO creation / activation should be done synchronously for
whole adapter, as active CIG has to be removed before it can be
reconfigured (Core v6.0 Sec 4.5.14.3). Some adapters support only one
active CIG.
Move related logic to bap_update_cig*() which does QoS and IO creation
as needed by current state.
If CIG is active or setups are being configured or closed, delay
transitions to QoS and IO recreation until that completes. Also delay
activating CIG (= enabling CIS) until setups have finished readying or
closing.
Operations are delayed within the same CIG, with the exception that
streams with unassigned CIG delay every CIG. This is because such
streams are likely meant to go to some existing CIG, so we must wait
them to be configurable before asking kernel to assign IDs.
Fixes race conditions:
* setup starts readying while CIG is active
* CIG is activated while a setup is readying
* new stream creates IO before old stream IO is recreated
These result to kernel assigning new streams to a different CIG
(problematic on controllers that can do only one) or CIS ID conflicts.
---
profiles/audio/bap.c | 394 +++++++++++++++++++++++++++++++------------
1 file changed, 285 insertions(+), 109 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 2d8b7aa91..ffed0ae19 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -81,8 +81,10 @@ struct bap_setup {
DBusMessageIter *iter);
GIOChannel *io;
unsigned int io_id;
- bool recreate;
- bool cig_active;
+ bool want_qos;
+ bool want_io;
+ bool want_cis;
+ bool cis_active;
uint8_t sid;
bool config_pending;
bool readying;
@@ -136,6 +138,7 @@ struct bap_data {
struct queue *server_streams;
GIOChannel *listen_io;
unsigned int io_id;
+ unsigned int cig_update_id;
void *user_data;
};
@@ -145,6 +148,9 @@ static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
void *user_data);
static int bap_select_all(struct bap_data *data, bool reconfigure,
bap_select_done_t cb, void *user_data);
+static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
+ struct bt_bap_stream *stream, int defer);
+static void bap_update_cigs(struct bap_data *data);
static bool bap_data_set_user_data(struct bap_data *data, void *user_data)
{
@@ -183,6 +189,9 @@ static void bap_data_free(struct bap_data *data)
if (data->io_id)
g_source_remove(data->io_id);
+ if (data->cig_update_id)
+ g_source_remove(data->cig_update_id);
+
if (data->service && btd_service_get_user_data(data->service) == data)
btd_service_set_user_data(data->service, NULL);
@@ -740,6 +749,8 @@ static void setup_ready(struct bap_setup *setup, int code,
setup->ready_cb = NULL;
setup->ready_cb_data = NULL;
}
+
+ bap_update_cigs(setup->ep->data);
}
static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason,
@@ -753,10 +764,9 @@ static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason,
if (code)
setup_ready(setup, code, reason);
-}
-static void setup_create_io(struct bap_data *data, struct bap_setup *setup,
- struct bt_bap_stream *stream, int defer);
+ bap_update_cigs(setup->ep->data);
+}
static int setup_qos(struct bap_setup *setup)
{
@@ -802,19 +812,15 @@ static void config_cb(struct bt_bap_stream *stream,
void *user_data)
{
struct bap_setup *setup = user_data;
- int err = 0;
DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason);
setup->id = 0;
if (code)
- err = code;
- else if (!setup->config_pending)
- err = setup_qos(setup);
+ setup_ready(setup, code, reason);
- if (err)
- setup_ready(setup, err, reason);
+ bap_update_cigs(setup->ep->data);
}
static void setup_io_close(void *data, void *user_data)
@@ -838,7 +844,7 @@ static void setup_io_close(void *data, void *user_data)
g_io_channel_unref(setup->io);
setup->io = NULL;
- setup->cig_active = false;
+ setup->cis_active = false;
bt_bap_stream_io_connecting(setup->stream, -1);
}
@@ -987,6 +993,8 @@ static void setup_free(void *data)
DBG("%p", setup);
+ setup->closing = true;
+
setup_ready(setup, -ECANCELED, 0);
if (setup->closing && setup->close_cb)
@@ -1013,6 +1021,8 @@ static void setup_free(void *data)
release_stream(setup->stream);
+ bap_update_cigs(setup->ep->data);
+
free(setup);
}
@@ -2219,7 +2229,7 @@ static void setup_accept_io(struct bap_setup *setup,
}
}
- setup->cig_active = true;
+ setup->cis_active = true;
return;
@@ -2227,129 +2237,299 @@ fail:
close(fd);
}
-struct cig_busy_data {
- struct btd_adapter *adapter;
+struct find_cig_data {
+ const struct btd_adapter *adapter;
+ bool (*func)(const void *data, const void *match_data);
+ struct bap_setup *found;
+ struct queue *cigs;
uint8_t cig;
};
-static bool match_cig_active(const void *data, const void *match_data)
+static bool find_cig_readying_setup(const void *data, const void *match_data)
+{
+ struct bap_setup *setup = (void *)data;
+ struct find_cig_data *info = (void *)match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
+
+ /* Streams with automatically assigned CIG are considered to potentially
+ * belong to any CIG.
+ */
+ if (qos && qos->ucast.cig_id != info->cig &&
+ info->cig != BT_ISO_QOS_CIG_UNSET &&
+ qos->ucast.cig_id != BT_ISO_QOS_CIG_UNSET)
+ return false;
+
+ return setup->readying || setup->closing ||
+ setup->config_pending || setup->id;
+}
+
+static bool find_cig_busy_setup(const void *data, const void *match_data)
{
const struct bap_setup *setup = data;
- const struct cig_busy_data *info = match_data;
+ struct find_cig_data *info = (void *)match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
- return (setup->qos.ucast.cig_id == info->cig) && setup->cig_active;
+ if (qos && qos->ucast.cig_id != info->cig &&
+ info->cig != BT_ISO_QOS_CIG_UNSET &&
+ qos->ucast.cig_id != BT_ISO_QOS_CIG_UNSET)
+ return false;
+
+ return setup->cis_active || setup->closing ||
+ setup->config_pending || setup->id;
}
-static bool cig_busy_ep(const void *data, const void *match_data)
+static bool find_cig_enumerate_setup(const void *data, const void *match_data)
+{
+ const struct bap_setup *setup = data;
+ struct find_cig_data *info = (void *)match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
+
+ if (qos && info->cigs) {
+ queue_remove(info->cigs, UINT_TO_PTR(qos->ucast.cig_id));
+ queue_push_tail(info->cigs, UINT_TO_PTR(qos->ucast.cig_id));
+ }
+
+ return false;
+}
+
+static bool find_cig_ep(const void *data, const void *match_data)
{
const struct bap_ep *ep = data;
- const struct cig_busy_data *info = match_data;
+ struct find_cig_data *info = (void *)match_data;
- return queue_find(ep->setups, match_cig_active, info);
+ info->found = queue_find(ep->setups, info->func, match_data);
+ return info->found;
}
-static bool cig_busy_session(const void *data, const void *match_data)
+static bool find_cig_session(const void *data, const void *match_data)
{
const struct bap_data *session = data;
- const struct cig_busy_data *info = match_data;
+ const struct find_cig_data *info = match_data;
if (device_get_adapter(session->device) != info->adapter)
return false;
- return queue_find(session->snks, cig_busy_ep, match_data) ||
- queue_find(session->srcs, cig_busy_ep, match_data);
+ return queue_find(session->snks, find_cig_ep, match_data) ||
+ queue_find(session->srcs, find_cig_ep, match_data);
}
-static bool is_cig_busy(struct bap_data *data, uint8_t cig)
+static struct bap_setup *find_cig_busy(struct bap_data *data, uint8_t cig)
{
- struct cig_busy_data info;
+ struct find_cig_data info = {
+ .adapter = device_get_adapter(data->device),
+ .cig = cig,
+ .func = find_cig_busy_setup,
+ };
- if (cig == BT_ISO_QOS_CIG_UNSET)
- return false;
-
- info.adapter = device_get_adapter(data->device);
- info.cig = cig;
-
- return queue_find(sessions, cig_busy_session, &info);
+ queue_find(sessions, find_cig_session, &info);
+ return info.found;
}
-static gboolean setup_io_recreate(void *user_data)
+static struct bap_setup *find_cig_readying(struct bap_data *data, uint8_t cig)
{
- struct bap_setup *setup = user_data;
+ struct find_cig_data info = {
+ .adapter = device_get_adapter(data->device),
+ .cig = cig,
+ .func = find_cig_readying_setup,
+ };
+
+ queue_find(sessions, find_cig_session, &info);
+ return info.found;
+}
+
+static struct queue *find_cig_enumerate(struct bap_data *data)
+{
+ struct find_cig_data info = {
+ .adapter = device_get_adapter(data->device),
+ .func = find_cig_enumerate_setup,
+ .cigs = queue_new(),
+ };
+
+ queue_find(sessions, find_cig_session, &info);
+ return info.cigs;
+}
+
+struct update_cig_data {
+ struct btd_adapter *adapter;
+ void (*func)(void *data, void *match_data);
+ uint8_t cig;
+ unsigned int count;
+};
+
+static void update_cig_setup_enable(void *data, void *match_data)
+{
+ struct bap_setup *setup = data;
+ struct update_cig_data *info = match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
+
+ if (qos && qos->ucast.cig_id != info->cig)
+ return;
+ if (!stream || !setup->io || setup->closing || setup->cis_active)
+ return;
+ if (!setup->want_cis)
+ return;
+ if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_ENABLING)
+ return;
DBG("%p", setup);
- setup->io_id = 0;
+ setup->want_cis = false;
+ setup_create_io(setup->ep->data, setup, setup->stream, false);
+ info->count++;
+}
+static void update_cig_setup_io(void *data, void *match_data)
+{
+ struct bap_setup *setup = data;
+ struct update_cig_data *info = match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
+
+ if (qos && qos->ucast.cig_id != info->cig)
+ return;
+ if (!setup->want_io || !stream || setup->io || setup->closing)
+ return;
+ if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_QOS)
+ return;
+
+ DBG("%p", setup);
+
+ setup->want_io = false;
setup_create_io(setup->ep->data, setup, setup->stream, true);
+ info->count++;
+}
+
+static void update_cig_setup_qos(void *data, void *match_data)
+{
+ struct bap_setup *setup = data;
+ struct update_cig_data *info = match_data;
+ struct bt_bap_stream *stream = setup->stream;
+ int err;
+ struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream);
+
+ if (qos && qos->ucast.cig_id != info->cig)
+ return;
+ if (!setup->want_qos || !stream || setup->closing)
+ return;
+ if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_CONFIG)
+ return;
+
+ DBG("%p", setup);
+
+ setup->want_qos = false;
+ err = setup_qos(setup);
+ if (err)
+ setup_ready(setup, err, 0);
+ else
+ info->count++;
+}
+
+static void update_cig_check_ep(void *data, void *match_data)
+{
+ struct bap_ep *ep = data;
+ struct update_cig_data *info = match_data;
+
+ queue_foreach(ep->setups, info->func, match_data);
+}
+
+static void update_cig_check_session(void *data, void *match_data)
+{
+ struct bap_data *session = data;
+ struct update_cig_data *info = match_data;
+
+ if (device_get_adapter(session->device) != info->adapter)
+ return;
+
+ queue_foreach(session->snks, update_cig_check_ep, match_data);
+ queue_foreach(session->srcs, update_cig_check_ep, match_data);
+}
+
+static void bap_update_cig(void *item, void *user_data)
+{
+ unsigned int cig = PTR_TO_UINT(item);
+ struct bap_data *data = user_data;
+ struct update_cig_data info;
+ struct bap_setup *setup;
+
+ info.adapter = device_get_adapter(data->device);
+ info.count = 0;
+ info.cig = cig;
+
+ DBG("adapter %p CIG 0x%x", info.adapter, info.cig);
+
+ /* Do stream QoS & IO re-creation only when CIG is no longer
+ * busy and all pending Config/QoS requests have completed.
+ */
+ setup = find_cig_busy(data, cig);
+ if (!setup) {
+ /* Recreate IO before QoS of new streams, so that we reserve
+ * their CIS IDs in kernel before allocating new streams.
+ */
+ info.func = update_cig_setup_io;
+ queue_foreach(sessions, update_cig_check_session, &info);
+
+ info.func = update_cig_setup_qos;
+ queue_foreach(sessions, update_cig_check_session, &info);
+ } else {
+ DBG("setup %p stream %p busy", setup, setup->stream);
+ }
+
+ /* Do CIS creation only after all setups have finished readying.
+ */
+ setup = find_cig_readying(data, cig);
+ if (!setup) {
+ info.func = update_cig_setup_enable;
+ queue_foreach(sessions, update_cig_check_session, &info);
+ } else {
+ DBG("setup %p stream %p readying", setup, setup->stream);
+ }
+}
+
+static gboolean bap_update_cigs_cb(void *user_data)
+{
+ struct bap_data *data = user_data;
+ struct queue *cigs;
+ bool unset;
+
+ data->cig_update_id = 0;
+
+ cigs = find_cig_enumerate(data);
+ unset = queue_remove(cigs, UINT_TO_PTR(BT_ISO_QOS_CIG_UNSET));
+ queue_foreach(cigs, bap_update_cig, data);
+ queue_destroy(cigs, NULL);
+
+ /* Handle streams with unset CIG last, so that kernel CIG allocation
+ * knows which IDs are reserved.
+ */
+ if (unset)
+ bap_update_cig(UINT_TO_PTR(BT_ISO_QOS_CIG_UNSET), data);
return FALSE;
}
-static void setup_recreate(void *data, void *match_data)
+static void bap_update_cigs(struct bap_data *data)
{
- struct bap_setup *setup = data;
- struct cig_busy_data *info = match_data;
-
- if (setup->qos.ucast.cig_id != info->cig || !setup->recreate ||
- setup->io_id)
+ if (data->cig_update_id)
return;
- setup->recreate = false;
- setup->io_id = g_idle_add(setup_io_recreate, setup);
-}
-
-static void recreate_cig_ep(void *data, void *match_data)
-{
- struct bap_ep *ep = data;
-
- queue_foreach(ep->setups, setup_recreate, match_data);
-}
-
-static void recreate_cig_session(void *data, void *match_data)
-{
- struct bap_data *session = data;
- struct cig_busy_data *info = match_data;
-
- if (device_get_adapter(session->device) != info->adapter)
- return;
-
- queue_foreach(session->snks, recreate_cig_ep, match_data);
- queue_foreach(session->srcs, recreate_cig_ep, match_data);
-}
-
-static void recreate_cig(struct bap_setup *setup)
-{
- struct bap_data *data = setup->ep->data;
- struct cig_busy_data info;
-
- info.adapter = device_get_adapter(data->device);
- info.cig = setup->qos.ucast.cig_id;
-
- DBG("adapter %p setup %p recreate CIG %d", info.adapter, setup,
- info.cig);
-
- if (setup->qos.ucast.cig_id == BT_ISO_QOS_CIG_UNSET) {
- recreate_cig_ep(setup->ep, &info);
- return;
- }
-
- queue_foreach(sessions, recreate_cig_session, &info);
+ data->cig_update_id = g_idle_add(bap_update_cigs_cb, data);
}
static void setup_io_disconnected(int cond, void *user_data)
{
struct bap_setup *setup = user_data;
- DBG("%p recreate %s", setup, setup->recreate ? "true" : "false");
-
setup->io_id = 0;
+ DBG("%p", setup);
+
setup_io_close(setup, NULL);
- /* Check if connecting recreate IO */
- if (!is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id))
- recreate_cig(setup);
+ bap_update_cigs(setup->ep->data);
}
static void bap_connect_bcast_io_cb(GIOChannel *chan, GError *err,
@@ -2393,13 +2573,9 @@ static void setup_connect_io(struct bap_data *data, struct bap_setup *setup,
return;
}
- /* If IO channel still up or CIG is busy, wait for it to be
- * disconnected and then recreate.
- */
- if (setup->io || is_cig_busy(data, setup->qos.ucast.cig_id)) {
- DBG("setup %p stream %p defer %s wait recreate", setup, stream,
- defer ? "true" : "false");
- setup->recreate = true;
+ /* If IO channel still up */
+ if (setup->io) {
+ DBG("setup %p stream %p io already up", setup, stream);
return;
}
@@ -2433,7 +2609,7 @@ static void setup_connect_io(struct bap_data *data, struct bap_setup *setup,
setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected, setup);
setup->io = io;
- setup->cig_active = !defer;
+ setup->cis_active = !defer;
bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io));
}
@@ -2664,22 +2840,25 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state,
case BT_BAP_STREAM_STATE_CONFIG:
if (setup) {
setup->config_pending = false;
- if (!setup->id) {
- int err = setup_qos(setup);
-
- if (err)
- setup_ready(setup, err, 0);
- }
+ setup->want_qos = true;
+ bap_update_cigs(setup->ep->data);
}
break;
case BT_BAP_STREAM_STATE_QOS:
- setup_create_io(data, setup, stream, true);
- if (setup)
+ if (setup) {
+ setup->want_qos = false;
+ setup->want_io = true;
setup_ready(setup, 0, 0);
+ bap_update_cigs(setup->ep->data);
+ } else {
+ setup_create_io(data, setup, stream, true);
+ }
break;
case BT_BAP_STREAM_STATE_ENABLING:
- if (setup)
- setup_create_io(data, setup, stream, false);
+ if (setup) {
+ setup->want_cis = true;
+ bap_update_cigs(setup->ep->data);
+ }
break;
case BT_BAP_STREAM_STATE_STREAMING:
break;
@@ -3146,7 +3325,6 @@ static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd,
if (!setup)
return;
- setup->recreate = false;
qos = &setup->qos;
if (!setup->io) {
@@ -3191,8 +3369,6 @@ static void bap_connecting_bcast(struct bt_bap_stream *stream, bool state,
if (!setup)
return;
- setup->recreate = false;
-
if (!setup->io) {
io = g_io_channel_unix_new(fd);
setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected,
--
2.49.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* RE: BAP stream reconfiguration
2025-06-08 21:32 ` [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created Pauli Virtanen
@ 2025-06-08 22:55 ` bluez.test.bot
0 siblings, 0 replies; 14+ messages in thread
From: bluez.test.bot @ 2025-06-08 22:55 UTC (permalink / raw)
To: linux-bluetooth, pav
[-- Attachment #1: Type: text/plain, Size: 2365 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=969627
---Test result---
Test Summary:
CheckPatch PENDING 0.26 seconds
GitLint PENDING 0.23 seconds
BuildEll PASS 20.03 seconds
BluezMake PASS 2603.71 seconds
MakeCheck PASS 20.48 seconds
MakeDistcheck PASS 197.56 seconds
CheckValgrind PASS 273.63 seconds
CheckSmatch WARNING 301.96 seconds
bluezmakeextell PASS 130.84 seconds
IncrementalBuild PENDING 0.26 seconds
ScanBuild PASS 901.38 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
src/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structuressrc/shared/bap.c:317:25: warning: array of flexible structuressrc/shared/bap.c: note: in included file:./src/shared/ascs.h:88:25: warning: array of flexible structures
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH BlueZ v3 00/10] BAP stream reconfiguration
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
` (9 preceding siblings ...)
2025-06-08 21:32 ` [PATCH BlueZ v3 10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring Pauli Virtanen
@ 2025-06-09 19:05 ` patchwork-bot+bluetooth
2025-06-09 19:16 ` Luiz Augusto von Dentz
10 siblings, 1 reply; 14+ messages in thread
From: patchwork-bot+bluetooth @ 2025-06-09 19:05 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Mon, 9 Jun 2025 00:32:12 +0300 you wrote:
> Implement ClearConfiguration() and Reconfigure() for BAP unicast
> MediaEndpoints.
>
> v3:
>
> - Add bt_bap_stream_lock(), instead of bt_bap_stream_discard()
>
> [...]
Here is the summary with links:
- [BlueZ,v3,01/10] bap: do not try QoS before links are updated & io created
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7d8eaa56b8cb
- [BlueZ,v3,02/10] shared/bap: detach ucast io on RELEASING and unlink streams
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=2f853e4d86d8
- [BlueZ,v3,03/10] shared/bap: add client ASE reuse and upper level stream locking
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d1eb496cc605
- [BlueZ,v3,04/10] bap: lock streams when used
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=40b91712b932
- [BlueZ,v3,05/10] bap: add ready callback for setup configuration
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ebed99caa7a1
- [BlueZ,v3,06/10] bap: support removing streams with ClearConfiguration()
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9e0dc968de50
- [BlueZ,v3,07/10] bap: add callback at the end of ucast client select/config
(no matching commit)
- [BlueZ,v3,08/10] bap: implement Reconfigure()
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=128b0695e2b5
- [BlueZ,v3,09/10] bap: don't show error when releasing stream
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=1d3907561f8e
- [BlueZ,v3,10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7feff47a9fbe
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH BlueZ v3 00/10] BAP stream reconfiguration
2025-06-09 19:05 ` [PATCH BlueZ v3 00/10] BAP stream reconfiguration patchwork-bot+bluetooth
@ 2025-06-09 19:16 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2025-06-09 19:16 UTC (permalink / raw)
To: patchwork-bot+bluetooth; +Cc: Pauli Virtanen, linux-bluetooth
Hi Pauli,
On Mon, Jun 9, 2025 at 3:04 PM <patchwork-bot+bluetooth@kernel.org> wrote:
>
> Hello:
>
> This series was applied to bluetooth/bluez.git (master)
> by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
>
> On Mon, 9 Jun 2025 00:32:12 +0300 you wrote:
> > Implement ClearConfiguration() and Reconfigure() for BAP unicast
> > MediaEndpoints.
> >
> > v3:
> >
> > - Add bt_bap_stream_lock(), instead of bt_bap_stream_discard()
> >
> > [...]
>
> Here is the summary with links:
> - [BlueZ,v3,01/10] bap: do not try QoS before links are updated & io created
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7d8eaa56b8cb
> - [BlueZ,v3,02/10] shared/bap: detach ucast io on RELEASING and unlink streams
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=2f853e4d86d8
> - [BlueZ,v3,03/10] shared/bap: add client ASE reuse and upper level stream locking
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d1eb496cc605
> - [BlueZ,v3,04/10] bap: lock streams when used
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=40b91712b932
> - [BlueZ,v3,05/10] bap: add ready callback for setup configuration
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ebed99caa7a1
> - [BlueZ,v3,06/10] bap: support removing streams with ClearConfiguration()
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9e0dc968de50
> - [BlueZ,v3,07/10] bap: add callback at the end of ucast client select/config
> (no matching commit)
Did a minor change to this one:
%s/bap_select_complete_select/bap_select_complete
> - [BlueZ,v3,08/10] bap: implement Reconfigure()
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=128b0695e2b5
> - [BlueZ,v3,09/10] bap: don't show error when releasing stream
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=1d3907561f8e
> - [BlueZ,v3,10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7feff47a9fbe
>
> You are awesome, thank you!
> --
> Deet-doot-dot, I am a bot.
> https://korg.docs.kernel.org/patchwork/pwbot.html
>
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-06-09 19:16 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-08 21:32 [PATCH BlueZ v3 00/10] BAP stream reconfiguration Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 01/10] bap: do not try QoS before links are updated & io created Pauli Virtanen
2025-06-08 22:55 ` BAP stream reconfiguration bluez.test.bot
2025-06-08 21:32 ` [PATCH BlueZ v3 02/10] shared/bap: detach ucast io on RELEASING and unlink streams Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 03/10] shared/bap: add client ASE reuse and upper level stream locking Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 04/10] bap: lock streams when used Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 05/10] bap: add ready callback for setup configuration Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 06/10] bap: support removing streams with ClearConfiguration() Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 07/10] bap: add callback at the end of ucast client select/config Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 08/10] bap: implement Reconfigure() Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 09/10] bap: don't show error when releasing stream Pauli Virtanen
2025-06-08 21:32 ` [PATCH BlueZ v3 10/10] bap: delay QoS & IO creation if CIG is busy or setups configuring Pauli Virtanen
2025-06-09 19:05 ` [PATCH BlueZ v3 00/10] BAP stream reconfiguration patchwork-bot+bluetooth
2025-06-09 19:16 ` Luiz Augusto von Dentz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox