* [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration
@ 2025-05-04 16:01 Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 02/11] org.bluez.MediaEndpoint: add client role Reconfigure Pauli Virtanen
` (11 more replies)
0 siblings, 12 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Allow user to remove specific streams by calling
ClearConfiguration(transport_path) on the endpoint. If the path is the
endpoint path instead, clear all streams associated with the endpoint.
---
Notes:
Whole patch series:
Pipewire side: https://gitlab.freedesktop.org/pvir/pipewire/-/commits/bap-codec-switch-select
Tested with
- Sony Linkbuds S: switching between AC 6(ii), 7(ii), 8(ii), 11(ii)
- Earfun Air Pro 3: switching between AC 6(i), 11(i)
TODO:
- Enabling/Disabling while Reconfigure/ClearConfiguration is in progress
should be handled better.
Stream QoS should be delayed until CIG is no longer busy.
doc/org.bluez.MediaEndpoint.rst | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
index f2b830ab0..b81106f0b 100644
--- a/doc/org.bluez.MediaEndpoint.rst
+++ b/doc/org.bluez.MediaEndpoint.rst
@@ -109,6 +109,12 @@ void ClearConfiguration(object transport)
Clear transport configuration.
+ **Server role:** [ISO only]
+
+ Close the stream associated with the given transport. If the
+ path given is the path of this endpoint, all its streams are
+ closed.
+
void Release()
``````````````
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH BlueZ v2 02/11] org.bluez.MediaEndpoint: add client role Reconfigure
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 03/11] shared/bap: bap_abort_stream_req() should cancel also current req Pauli Virtanen
` (10 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add a simple way for the sound server to reconfigure a BAP unicast
endpoint, by calling org.bluez.MediaEndpoint.Reconfigure().
This shall destroy all streams of the endpoint, and restart the
SelectProperties() configuration flow from the beginning.
Since it may be necessary to reconfigure multiple endpoints at once to
correctly make bidirectional CIS, add Defer argument to just mark eps
for configuration.
In future, org.bluez.MediaEndpoint.SetConfiguration() could be changed
to handle unicast in the same way as broadcast: only create streams.
This allows sound server to have full control over stream configuration
itself, and not rely on bt_bap_select().
---
doc/org.bluez.MediaEndpoint.rst | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
index b81106f0b..ea555a7d3 100644
--- a/doc/org.bluez.MediaEndpoint.rst
+++ b/doc/org.bluez.MediaEndpoint.rst
@@ -115,6 +115,28 @@ void ClearConfiguration(object transport)
path given is the path of this endpoint, all its streams are
closed.
+void Reconfigure(dict properties)
+`````````````````````````````````
+
+ [ISO only]
+
+ Reconfigure a BAP unicast endpoint. This closes all existing
+ streams of the endpoint, and restarts the configuration
+ selection flow which e.g. triggers calls to *SelectProperties*
+ allowing the sound server to modify the configuration.
+
+ The following arguments are taken in *properties*:
+
+ :boolean Defer [optional]:
+
+ If True, mark endpoint for reconfiguration, but
+ postpone it until a non-deferred *Reconfigure()*
+ operation is made on an endpoint of the same device.
+
+ This is necessary to use when reconfiguring source and
+ sink streams with the intention that they be combined
+ into the same CIG, possibly forming bidirectional CIS.
+
void Release()
``````````````
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH BlueZ v2 03/11] shared/bap: bap_abort_stream_req() should cancel also current req
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 02/11] org.bluez.MediaEndpoint: add client role Reconfigure Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage Pauli Virtanen
` (9 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
After bap_abort_stream_req() no req callbacks for stream shall be
called, so it has to fail also the currently in-flight request.
---
src/shared/bap.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index 2a08f3eea..976e3c0b1 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -1298,6 +1298,13 @@ static void bap_abort_stream_req(struct bt_bap *bap,
struct bt_bap_stream *stream)
{
queue_remove_all(bap->reqs, match_req_stream, stream, bap_req_abort);
+
+ if (bap->req && bap->req->stream == stream) {
+ struct bt_bap_req *req = bap->req;
+
+ bap->req = NULL;
+ bap_req_complete(req, NULL);
+ }
}
static void bt_bap_stream_unref(void *data)
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 02/11] org.bluez.MediaEndpoint: add client role Reconfigure Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 03/11] shared/bap: bap_abort_stream_req() should cancel also current req Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-06 16:56 ` Luiz Augusto von Dentz
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 05/11] bap: discard stream when no longer in use Pauli Virtanen
` (8 subsequent siblings)
11 siblings, 1 reply; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
The ucast client stream design has issues:
* Assuming lpac & rpac pair uniquely identifies a stream. False for
AC6(i) and other multi-stream configs.
* No way for ASE in Config state be configured with different codec.
* Assuming ASE enters idle state at end of stream life cycle. 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.
* Assuming Unicast Client upper layer always creates streams for all
non-idle ASEs. False, as when switching between duplex & sink-only
modes, only some streams shall be used regardless of whether Server is
caching config or not.
In practice, these currently prevent reconfiguring ASEs on devices which
cache codec config, and multi-stream configs work only accidentally (and
fail if server does Config codec itself, like some devices do).
Minimally fixed design for Unicast Client streams:
Streams correspond 1-to-1 to ASEs in non-IDLE state.
Track explicitly which streams upper level is using:
- bt_bap_stream_new() always makes new stream with client_active=true
- bt_bap_stream_discard() releases stream and marks client_active=false
Streams (ASEs) with no active client may be reused when upper level asks
for a new stream.
Streams with no active client may have their lpac changed. The
need_reconfig state is a bit ugly, but not really avoidable.
Streams with no active client shall be ignored when constructing
bidirectional CIS pairs.
Streams shall clear transport and detach io on RELEASING. (cf BAP v1.0.2
§5.6.6). Also unlink them since QoS is gone at that point.
Streams shall have transport only for >= QOS state. (Server streams
work differently, which makes sense since upper level cannot acquire
them before they are pending.)
---
src/shared/bap.c | 170 +++++++++++++++++++++++++++++++++--------------
src/shared/bap.h | 2 +
2 files changed, 123 insertions(+), 49 deletions(-)
diff --git a/src/shared/bap.c b/src/shared/bap.c
index 976e3c0b1..456450d40 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 client_active;
+ bool need_reconfig;
void *user_data;
};
@@ -1488,6 +1490,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;
}
}
@@ -1898,6 +1907,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 */
@@ -2300,7 +2312,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;
}
@@ -2583,6 +2594,9 @@ static int bap_ucast_io_link(struct bt_bap_stream *stream,
stream->ep->dir == link->ep->dir)
return -EINVAL;
+ if (stream->client && !(stream->client_active && link->client_active))
+ return -EINVAL;
+
if (!stream->links)
stream->links = queue_new();
@@ -2603,6 +2617,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;
@@ -2708,7 +2746,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,
@@ -2718,7 +2756,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,
@@ -5015,6 +5053,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)
@@ -5922,43 +5962,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;
@@ -6136,6 +6139,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->client_active)
+ 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,
@@ -6153,21 +6198,28 @@ 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);
+ }
+ stream->client_active = true;
return stream;
}
@@ -6187,6 +6239,26 @@ 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_discard(struct bt_bap_stream *stream)
+{
+ if (!stream || !stream->client)
+ return;
+
+ stream->client_active = false;
+
+ switch (stream->ep->state) {
+ case BT_BAP_STREAM_STATE_IDLE:
+ case BT_BAP_STREAM_STATE_RELEASING:
+ break;
+ case BT_BAP_STREAM_STATE_CONFIG:
+ if (stream->ep->old_state == BT_BAP_STREAM_STATE_RELEASING)
+ break;
+ /* Fallthrough */
+ default:
+ bt_bap_stream_release(stream, NULL, NULL);
+ }
+}
+
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..5949eb4b1 100644
--- a/src/shared/bap.h
+++ b/src/shared/bap.h
@@ -183,6 +183,8 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
struct bt_bap_qos *pqos,
struct iovec *data);
+void bt_bap_stream_discard(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] 15+ messages in thread
* [RFC PATCH BlueZ v2 05/11] bap: discard stream when no longer in use
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (2 preceding siblings ...)
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 06/11] bap: do not try QoS before links are updated & io created Pauli Virtanen
` (7 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Indicate to lower layer that the stream will not be used any more.
---
profiles/audio/bap.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index ee7c8bc49..dcef98148 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -860,6 +860,8 @@ static void setup_free(void *data)
if (setup->destroy)
setup->destroy(setup);
+ bt_bap_stream_discard(setup->stream);
+
free(setup);
}
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH BlueZ v2 06/11] bap: do not try QoS before links are updated & io created
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (3 preceding siblings ...)
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 05/11] bap: discard stream when no longer in use Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 07/11] bap: add ready callback for setup configuration Pauli Virtanen
` (6 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 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 dcef98148..9b3bd405f 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;
}
@@ -986,6 +1018,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) {
@@ -1005,6 +1038,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);
@@ -1403,6 +1437,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) {
@@ -1807,9 +1842,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;
@@ -2189,25 +2221,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] 15+ messages in thread
* [RFC PATCH BlueZ v2 07/11] bap: add ready callback for setup configuration
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (4 preceding siblings ...)
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 06/11] bap: do not try QoS before links are updated & io created Pauli Virtanen
@ 2025-05-04 16:01 ` Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 08/11] bap: support removing streams with ClearConfiguration() Pauli Virtanen
` (5 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:01 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 | 176 +++++++++++++++++++++++++++----------------
1 file changed, 110 insertions(+), 66 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 9b3bd405f..64dee8f9d 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;
@@ -80,7 +85,8 @@ struct bap_setup {
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 +122,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 +707,28 @@ fail:
return -EINVAL;
}
+static void setup_ready(struct bap_setup *setup, int code,
+ uint8_t reason)
+{
+ if (!setup->ready_cb)
+ return;
+
+ 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 +776,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)
@@ -864,22 +867,16 @@ static struct bap_setup *setup_new(struct bap_ep *ep)
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);
@@ -974,6 +971,34 @@ 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 (!data->msg) {
+ free(data);
+ return;
+ }
+
+ 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)
{
@@ -981,6 +1006,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);
@@ -1015,35 +1041,22 @@ 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_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);
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;
@@ -1421,11 +1434,17 @@ 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->ready_cb)
+ return -EBUSY;
+
+ setup->ready_cb = cb;
+ setup->ready_cb_data = user_data;
+
DBG("setup %p caps %p metadata %p", setup, setup->caps,
setup->metadata);
@@ -1437,27 +1456,45 @@ static void setup_config(void *data, void *user_data)
ep->rpac, &setup->qos,
setup->caps);
- 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);
+ 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,
@@ -2223,11 +2260,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] 15+ messages in thread
* [RFC PATCH BlueZ v2 08/11] bap: support removing streams with ClearConfiguration()
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (5 preceding siblings ...)
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 07/11] bap: add ready callback for setup configuration Pauli Virtanen
@ 2025-05-04 16:02 ` Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 09/11] bap: add callback at the end of ucast client select/config Pauli Virtanen
` (4 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:02 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Implement removing streams via ClearConfiguration().
---
profiles/audio/bap.c | 154 +++++++++++++++++++++++++++++++++++--
profiles/audio/transport.c | 17 ++++
profiles/audio/transport.h | 1 +
3 files changed, 166 insertions(+), 6 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 64dee8f9d..c9755ad96 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;
@@ -81,12 +84,15 @@ struct bap_setup {
bool cig_active;
uint8_t sid;
bool config_pending;
+ 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);
};
@@ -741,6 +747,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)
@@ -817,12 +825,87 @@ 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 int setup_close(struct bap_setup *setup, bap_setup_close_func_t cb,
+ void *user_data)
{
- if (!ep)
+ if (setup->closing)
+ return -EBUSY;
+
+ DBG("%p", setup);
+
+ setup->close_cb = cb;
+ setup->close_cb_data = user_data;
+ setup->closing = true;
+
+ if (!setup->stream) {
+ setup_free(setup);
+ return 0;
+ }
+
+ bt_bap_stream_discard(setup->stream);
+ 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)
@@ -872,6 +955,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;
@@ -1022,7 +1108,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);
@@ -1062,6 +1148,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;
@@ -1234,6 +1365,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) },
{ },
};
@@ -1244,10 +1378,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);
}
@@ -1441,6 +1574,8 @@ static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
if (setup->ready_cb)
return -EBUSY;
+ if (setup->closing)
+ return -EINVAL;
setup->ready_cb = cb;
setup->ready_cb_data = user_data;
@@ -2249,6 +2384,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 3d1f55b70..b399d0f35 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] 15+ messages in thread
* [RFC PATCH BlueZ v2 09/11] bap: add callback at the end of ucast client select/config
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (6 preceding siblings ...)
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 08/11] bap: support removing streams with ClearConfiguration() Pauli Virtanen
@ 2025-05-04 16:02 ` Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 10/11] bap: implement Reconfigure() Pauli Virtanen
` (3 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:02 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 | 173 +++++++++++++++++++++++++++++++++----------
1 file changed, 134 insertions(+), 39 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index c9755ad96..89900602f 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -66,10 +66,12 @@
#define MEDIA_INTERFACE "org.bluez.Media1"
struct bap_setup;
+struct bap_select;
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;
@@ -96,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;
@@ -105,6 +116,7 @@ struct bap_ep {
uint16_t supported_context;
uint16_t context;
struct queue *setups;
+ struct bap_select *select;
};
struct bap_data {
@@ -122,7 +134,6 @@ struct bap_data {
struct queue *server_streams;
GIOChannel *listen_io;
unsigned int io_id;
- int selecting;
void *user_data;
};
@@ -1614,14 +1625,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--;
}
}
@@ -1629,7 +1663,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,
@@ -1637,11 +1701,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;
}
@@ -1650,38 +1714,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;
@@ -1703,9 +1744,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;
}
@@ -1723,9 +1812,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,
@@ -1752,6 +1848,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);
@@ -1761,8 +1858,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)
@@ -2716,8 +2812,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] 15+ messages in thread
* [RFC PATCH BlueZ v2 10/11] bap: implement Reconfigure()
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (7 preceding siblings ...)
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 09/11] bap: add callback at the end of ucast client select/config Pauli Virtanen
@ 2025-05-04 16:02 ` Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 11/11] bap: delay recreating IO after ongoing configuration Pauli Virtanen
` (2 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:02 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 89900602f..20d610e6f 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)
{
@@ -1204,6 +1207,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;
@@ -1379,6 +1508,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) },
{ },
};
@@ -1749,6 +1882,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) {
@@ -1761,15 +1899,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();
@@ -1858,7 +1999,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)
@@ -2812,7 +2953,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] 15+ messages in thread
* [RFC PATCH BlueZ v2 11/11] bap: delay recreating IO after ongoing configuration
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (8 preceding siblings ...)
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 10/11] bap: implement Reconfigure() Pauli Virtanen
@ 2025-05-04 16:02 ` Pauli Virtanen
2025-05-04 17:36 ` [RFC,BlueZ,v2,01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration bluez.test.bot
2025-05-06 17:20 ` [RFC PATCH BlueZ v2 01/11] " patchwork-bot+bluetooth
11 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-04 16:02 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Delay creating IO if setup reconfiguration is ongoing.
TODO: this is not fully right, also new stream QoS should be delayed if
CIG is active, so that it doesn't get assigned into a new CIG which
controllers may not support
---
profiles/audio/bap.c | 108 ++++++++++++++++++++++++++++++++-----------
1 file changed, 81 insertions(+), 27 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 20d610e6f..ce1c349c6 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -86,6 +86,7 @@ struct bap_setup {
bool cig_active;
uint8_t sid;
bool config_pending;
+ bool readying;
bool closing;
struct iovec *caps;
struct iovec *metadata;
@@ -727,15 +728,23 @@ fail:
return -EINVAL;
}
+static void setup_recreate_cig(struct bap_setup *setup);
+
static void setup_ready(struct bap_setup *setup, int code,
uint8_t reason)
{
- if (!setup->ready_cb)
+ if (!setup->readying)
return;
- setup->ready_cb(setup, code, reason, setup->ready_cb_data);
- setup->ready_cb = NULL;
- setup->ready_cb_data = NULL;
+ 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;
+ }
+
+ setup_recreate_cig(setup);
}
static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason,
@@ -770,7 +779,7 @@ static int setup_qos(struct bap_setup *setup)
setup_create_io(data, setup, stream, true);
if (!setup->io) {
- error("Unable to create io");
+ DBG("io pending");
goto error;
}
@@ -1741,6 +1750,8 @@ static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb,
if (!setup->id)
return -EINVAL;
+ setup->readying = true;
+
switch (bt_bap_stream_get_type(setup->stream)) {
case BT_BAP_STREAM_TYPE_UCAST:
setup->config_pending = true;
@@ -2215,7 +2226,11 @@ static bool match_cig_active(const void *data, const void *match_data)
const struct bap_setup *setup = data;
const struct cig_busy_data *info = match_data;
- return (setup->qos.ucast.cig_id == info->cig) && setup->cig_active;
+ if (info->cig != BT_ISO_QOS_CIG_UNSET &&
+ setup->qos.ucast.cig_id != info->cig)
+ return false;
+
+ return setup->cig_active || setup->readying;
}
static bool cig_busy_ep(const void *data, const void *match_data)
@@ -2242,6 +2257,10 @@ static bool is_cig_busy(struct bap_data *data, uint8_t cig)
{
struct cig_busy_data info;
+ /* TODO: this is not quite right --- it may results to allocation of a
+ * new CIG which won't work if controller supports only one. Instead we
+ * should be delaying stream QoS until CIG deactivates.
+ */
if (cig == BT_ISO_QOS_CIG_UNSET)
return false;
@@ -2251,30 +2270,22 @@ static bool is_cig_busy(struct bap_data *data, uint8_t cig)
return queue_find(sessions, cig_busy_session, &info);
}
-static gboolean setup_io_recreate(void *user_data)
-{
- struct bap_setup *setup = user_data;
-
- DBG("%p", setup);
-
- setup->io_id = 0;
-
- setup_create_io(setup->ep->data, setup, setup->stream, true);
-
- return FALSE;
-}
-
static void setup_recreate(void *data, void *match_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 (info->cig != BT_ISO_QOS_CIG_UNSET &&
+ setup->qos.ucast.cig_id != BT_ISO_QOS_CIG_UNSET &&
+ setup->qos.ucast.cig_id != info->cig)
+ return;
+ if (!setup->recreate || !setup->stream)
return;
+ DBG("%p", setup);
+
setup->recreate = false;
- setup->io_id = g_idle_add(setup_io_recreate, setup);
+ setup_create_io(setup->ep->data, setup, setup->stream, true);
}
static void recreate_cig_ep(void *data, void *match_data)
@@ -2284,6 +2295,34 @@ static void recreate_cig_ep(void *data, void *match_data)
queue_foreach(ep->setups, setup_recreate, match_data);
}
+static void setup_reenable(void *data, void *match_data)
+{
+ struct bap_setup *setup = data;
+ struct cig_busy_data *info = match_data;
+
+ if (info->cig != BT_ISO_QOS_CIG_UNSET &&
+ setup->qos.ucast.cig_id != BT_ISO_QOS_CIG_UNSET &&
+ setup->qos.ucast.cig_id != info->cig)
+ return;
+ if (!setup->recreate || !setup->stream)
+ return;
+
+ DBG("%p", setup);
+
+ switch (bt_bap_stream_get_state(setup->stream)) {
+ case BT_BAP_STREAM_STATE_ENABLING:
+ setup_create_io(setup->ep->data, setup, setup->stream, false);
+ break;
+ }
+}
+
+static void reenable_cig_ep(void *data, void *match_data)
+{
+ struct bap_ep *ep = data;
+
+ queue_foreach(ep->setups, setup_reenable, match_data);
+}
+
static void recreate_cig_session(void *data, void *match_data)
{
struct bap_data *session = data;
@@ -2294,13 +2333,19 @@ static void recreate_cig_session(void *data, void *match_data)
queue_foreach(session->snks, recreate_cig_ep, match_data);
queue_foreach(session->srcs, recreate_cig_ep, match_data);
+
+ queue_foreach(session->snks, reenable_cig_ep, match_data);
+ queue_foreach(session->srcs, reenable_cig_ep, match_data);
}
-static void recreate_cig(struct bap_setup *setup)
+static void setup_recreate_cig(struct bap_setup *setup)
{
struct bap_data *data = setup->ep->data;
struct cig_busy_data info;
+ if (is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id))
+ return;
+
info.adapter = device_get_adapter(data->device);
info.cig = setup->qos.ucast.cig_id;
@@ -2309,25 +2354,34 @@ static void recreate_cig(struct bap_setup *setup)
if (setup->qos.ucast.cig_id == BT_ISO_QOS_CIG_UNSET) {
recreate_cig_ep(setup->ep, &info);
+ reenable_cig_ep(setup->ep, &info);
return;
}
queue_foreach(sessions, recreate_cig_session, &info);
}
+static gboolean recreate_cig_cb(void *user_data)
+{
+ struct bap_setup *setup = user_data;
+
+ setup->io_id = 0;
+ setup_recreate_cig(setup);
+ return FALSE;
+}
+
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 recreate %s", setup, setup->recreate ? "true" : "false");
+
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);
+ setup->io_id = g_idle_add(recreate_cig_cb, setup);
}
static void bap_connect_bcast_io_cb(GIOChannel *chan, GError *err,
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* RE: [RFC,BlueZ,v2,01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (9 preceding siblings ...)
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 11/11] bap: delay recreating IO after ongoing configuration Pauli Virtanen
@ 2025-05-04 17:36 ` bluez.test.bot
2025-05-06 17:20 ` [RFC PATCH BlueZ v2 01/11] " patchwork-bot+bluetooth
11 siblings, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2025-05-04 17:36 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=959452
---Test result---
Test Summary:
CheckPatch PENDING 0.26 seconds
GitLint PENDING 0.25 seconds
BuildEll PASS 20.62 seconds
BluezMake PASS 2621.64 seconds
MakeCheck PASS 20.06 seconds
MakeDistcheck PASS 197.84 seconds
CheckValgrind PASS 274.37 seconds
CheckSmatch WARNING 303.13 seconds
bluezmakeextell PASS 127.66 seconds
IncrementalBuild PENDING 0.30 seconds
ScanBuild PASS 898.75 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] 15+ messages in thread
* Re: [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage Pauli Virtanen
@ 2025-05-06 16:56 ` Luiz Augusto von Dentz
2025-05-18 18:08 ` Pauli Virtanen
0 siblings, 1 reply; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2025-05-06 16:56 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hi Pauli,
On Sun, May 4, 2025 at 12:02 PM Pauli Virtanen <pav@iki.fi> wrote:
>
> The ucast client stream design has issues:
>
> * Assuming lpac & rpac pair uniquely identifies a stream. False for
> AC6(i) and other multi-stream configs.
>
> * No way for ASE in Config state be configured with different codec.
>
> * Assuming ASE enters idle state at end of stream life cycle. 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.
>
> * Assuming Unicast Client upper layer always creates streams for all
> non-idle ASEs. False, as when switching between duplex & sink-only
> modes, only some streams shall be used regardless of whether Server is
> caching config or not.
I think it would make sense to fix these points one by one, or you
tried that and ended up running into some problems?
> In practice, these currently prevent reconfiguring ASEs on devices which
> cache codec config, and multi-stream configs work only accidentally (and
> fail if server does Config codec itself, like some devices do).
>
> Minimally fixed design for Unicast Client streams:
>
> Streams correspond 1-to-1 to ASEs in non-IDLE state.
>
> Track explicitly which streams upper level is using:
>
> - bt_bap_stream_new() always makes new stream with client_active=true
> - bt_bap_stream_discard() releases stream and marks client_active=false
This perhaps could be done differently, perhaps having a lock flag, so
if the stream is locked it means its configuration cannot be changed.
> Streams (ASEs) with no active client may be reused when upper level asks
> for a new stream.
>
> Streams with no active client may have their lpac changed. The
> need_reconfig state is a bit ugly, but not really avoidable.
Do we have a situation where client_active=false (lock=false) and
need_reconfig!=true? In other words could we just use the
client_active/lock flag instead of introducing yet another flag?
> Streams with no active client shall be ignored when constructing
> bidirectional CIS pairs.
>
> Streams shall clear transport and detach io on RELEASING. (cf BAP v1.0.2
> §5.6.6). Also unlink them since QoS is gone at that point.
I'd consider fixing this as a separate patch as well.
> Streams shall have transport only for >= QOS state. (Server streams
> work differently, which makes sense since upper level cannot acquire
> them before they are pending.)
Sounds good, that said we might want to check if there aren't missing
tests that would cover such conditions, there is a whole session of
the BAP testing spec dedicated for streaming configuration (AC-#), if
will probably need some changes to test-bap to create socketpair to
emulate CIS since those tests require CIS/BIS in order to pass.
> ---
> src/shared/bap.c | 170 +++++++++++++++++++++++++++++++++--------------
> src/shared/bap.h | 2 +
> 2 files changed, 123 insertions(+), 49 deletions(-)
>
> diff --git a/src/shared/bap.c b/src/shared/bap.c
> index 976e3c0b1..456450d40 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 client_active;
> + bool need_reconfig;
> void *user_data;
> };
>
> @@ -1488,6 +1490,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;
> }
> }
>
> @@ -1898,6 +1907,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 */
> @@ -2300,7 +2312,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;
> }
>
> @@ -2583,6 +2594,9 @@ static int bap_ucast_io_link(struct bt_bap_stream *stream,
> stream->ep->dir == link->ep->dir)
> return -EINVAL;
>
> + if (stream->client && !(stream->client_active && link->client_active))
> + return -EINVAL;
> +
> if (!stream->links)
> stream->links = queue_new();
>
> @@ -2603,6 +2617,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;
> @@ -2708,7 +2746,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,
> @@ -2718,7 +2756,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,
> @@ -5015,6 +5053,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)
> @@ -5922,43 +5962,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;
> @@ -6136,6 +6139,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->client_active)
> + 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,
> @@ -6153,21 +6198,28 @@ 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);
> + }
>
> + stream->client_active = true;
> return stream;
> }
>
> @@ -6187,6 +6239,26 @@ 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_discard(struct bt_bap_stream *stream)
> +{
> + if (!stream || !stream->client)
> + return;
> +
> + stream->client_active = false;
> +
> + switch (stream->ep->state) {
> + case BT_BAP_STREAM_STATE_IDLE:
> + case BT_BAP_STREAM_STATE_RELEASING:
> + break;
> + case BT_BAP_STREAM_STATE_CONFIG:
> + if (stream->ep->old_state == BT_BAP_STREAM_STATE_RELEASING)
> + break;
> + /* Fallthrough */
> + default:
> + bt_bap_stream_release(stream, NULL, NULL);
> + }
> +}
> +
> 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..5949eb4b1 100644
> --- a/src/shared/bap.h
> +++ b/src/shared/bap.h
> @@ -183,6 +183,8 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
> struct bt_bap_qos *pqos,
> struct iovec *data);
>
> +void bt_bap_stream_discard(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
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
` (10 preceding siblings ...)
2025-05-04 17:36 ` [RFC,BlueZ,v2,01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration bluez.test.bot
@ 2025-05-06 17:20 ` patchwork-bot+bluetooth
11 siblings, 0 replies; 15+ messages in thread
From: patchwork-bot+bluetooth @ 2025-05-06 17:20 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 Sun, 4 May 2025 19:01:53 +0300 you wrote:
> Allow user to remove specific streams by calling
> ClearConfiguration(transport_path) on the endpoint. If the path is the
> endpoint path instead, clear all streams associated with the endpoint.
> ---
>
> Notes:
> Whole patch series:
>
> [...]
Here is the summary with links:
- [RFC,BlueZ,v2,01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=63b07d1cb639
- [RFC,BlueZ,v2,02/11] org.bluez.MediaEndpoint: add client role Reconfigure
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=8d0f178974f8
- [RFC,BlueZ,v2,03/11] shared/bap: bap_abort_stream_req() should cancel also current req
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=62f0b9b539bc
- [RFC,BlueZ,v2,04/11] shared/bap: fix ucast client ASE usage
(no matching commit)
- [RFC,BlueZ,v2,05/11] bap: discard stream when no longer in use
(no matching commit)
- [RFC,BlueZ,v2,06/11] bap: do not try QoS before links are updated & io created
(no matching commit)
- [RFC,BlueZ,v2,07/11] bap: add ready callback for setup configuration
(no matching commit)
- [RFC,BlueZ,v2,08/11] bap: support removing streams with ClearConfiguration()
(no matching commit)
- [RFC,BlueZ,v2,09/11] bap: add callback at the end of ucast client select/config
(no matching commit)
- [RFC,BlueZ,v2,10/11] bap: implement Reconfigure()
(no matching commit)
- [RFC,BlueZ,v2,11/11] bap: delay recreating IO after ongoing configuration
(no matching commit)
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] 15+ messages in thread
* Re: [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage
2025-05-06 16:56 ` Luiz Augusto von Dentz
@ 2025-05-18 18:08 ` Pauli Virtanen
0 siblings, 0 replies; 15+ messages in thread
From: Pauli Virtanen @ 2025-05-18 18:08 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
ti, 2025-05-06 kello 12:56 -0400, Luiz Augusto von Dentz kirjoitti:
> Hi Pauli,
>
> On Sun, May 4, 2025 at 12:02 PM Pauli Virtanen <pav@iki.fi> wrote:
> >
> > The ucast client stream design has issues:
> >
> > * Assuming lpac & rpac pair uniquely identifies a stream. False for
> > AC6(i) and other multi-stream configs.
> >
> > * No way for ASE in Config state be configured with different codec.
> >
> > * Assuming ASE enters idle state at end of stream life cycle. 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.
> >
> > * Assuming Unicast Client upper layer always creates streams for all
> > non-idle ASEs. False, as when switching between duplex & sink-only
> > modes, only some streams shall be used regardless of whether Server is
> > caching config or not.
>
> I think it would make sense to fix these points one by one, or you
> tried that and ended up running into some problems?
Adding the client_active/lock mechanism and using it in stream linking
& ASE selection solves most of those.
Not clear that aside from the close+unlink on release, the rest can be
split to working standalone changes.
> > In practice, these currently prevent reconfiguring ASEs on devices which
> > cache codec config, and multi-stream configs work only accidentally (and
> > fail if server does Config codec itself, like some devices do).
> >
> > Minimally fixed design for Unicast Client streams:
> >
> > Streams correspond 1-to-1 to ASEs in non-IDLE state.
> >
> > Track explicitly which streams upper level is using:
> >
> > - bt_bap_stream_new() always makes new stream with client_active=true
> > - bt_bap_stream_discard() releases stream and marks client_active=false
>
> This perhaps could be done differently, perhaps having a lock flag, so
> if the stream is locked it means its configuration cannot be changed.
It could be called bt_bap_stream_lock/unlock() instead.
The "configuration cannot be changed" in this case would mean only that
bt_bap_stream_new() does not reuse that particular stream.
It's not clear eg. blocking upper layer from bt_bap_config_stream() on
locked stream is useful, similarly blocking server-side config updates
> > Streams (ASEs) with no active client may be reused when upper level asks
> > for a new stream.
> >
> > Streams with no active client may have their lpac changed. The
> > need_reconfig state is a bit ugly, but not really avoidable.
>
> Do we have a situation where client_active=false (lock=false) and
> need_reconfig!=true? In other words could we just use the
> client_active/lock flag instead of introducing yet another flag?
It occurs if stream is discarded/unlocked, before transition to CONFIG
is made.
The state indicates the configuration in bt_bap_stream (lpac etc.) is
out of sync with the ASE, and upper layer must bt_bap_stream_config().
> > Streams with no active client shall be ignored when constructing
> > bidirectional CIS pairs.
> >
> > Streams shall clear transport and detach io on RELEASING. (cf BAP v1.0.2
> > §5.6.6). Also unlink them since QoS is gone at that point.
>
> I'd consider fixing this as a separate patch as well.
I'll split this to separate patch.
> > Streams shall have transport only for >= QOS state. (Server streams
> > work differently, which makes sense since upper level cannot acquire
> > them before they are pending.)
>
> Sounds good, that said we might want to check if there aren't missing
> tests that would cover such conditions, there is a whole session of
> the BAP testing spec dedicated for streaming configuration (AC-#), if
> will probably need some changes to test-bap to create socketpair to
> emulate CIS since those tests require CIS/BIS in order to pass.
Tests would be good, yes.
I don't see the BAP testing spec contain tests that explicitly do
client reconfiguring. A simple smoketest for Releasing is specified.
I'll do some more manual testing before v3.
> > ---
> > src/shared/bap.c | 170 +++++++++++++++++++++++++++++++++--------------
> > src/shared/bap.h | 2 +
> > 2 files changed, 123 insertions(+), 49 deletions(-)
> >
> > diff --git a/src/shared/bap.c b/src/shared/bap.c
> > index 976e3c0b1..456450d40 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 client_active;
> > + bool need_reconfig;
> > void *user_data;
> > };
> >
> > @@ -1488,6 +1490,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;
> > }
> > }
> >
> > @@ -1898,6 +1907,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 */
> > @@ -2300,7 +2312,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;
> > }
> >
> > @@ -2583,6 +2594,9 @@ static int bap_ucast_io_link(struct bt_bap_stream *stream,
> > stream->ep->dir == link->ep->dir)
> > return -EINVAL;
> >
> > + if (stream->client && !(stream->client_active && link->client_active))
> > + return -EINVAL;
> > +
> > if (!stream->links)
> > stream->links = queue_new();
> >
> > @@ -2603,6 +2617,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;
> > @@ -2708,7 +2746,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,
> > @@ -2718,7 +2756,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,
> > @@ -5015,6 +5053,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)
> > @@ -5922,43 +5962,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;
> > @@ -6136,6 +6139,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->client_active)
> > + 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,
> > @@ -6153,21 +6198,28 @@ 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);
> > + }
> >
> > + stream->client_active = true;
> > return stream;
> > }
> >
> > @@ -6187,6 +6239,26 @@ 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_discard(struct bt_bap_stream *stream)
> > +{
> > + if (!stream || !stream->client)
> > + return;
> > +
> > + stream->client_active = false;
> > +
> > + switch (stream->ep->state) {
> > + case BT_BAP_STREAM_STATE_IDLE:
> > + case BT_BAP_STREAM_STATE_RELEASING:
> > + break;
> > + case BT_BAP_STREAM_STATE_CONFIG:
> > + if (stream->ep->old_state == BT_BAP_STREAM_STATE_RELEASING)
> > + break;
> > + /* Fallthrough */
> > + default:
> > + bt_bap_stream_release(stream, NULL, NULL);
> > + }
> > +}
> > +
> > 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..5949eb4b1 100644
> > --- a/src/shared/bap.h
> > +++ b/src/shared/bap.h
> > @@ -183,6 +183,8 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
> > struct bt_bap_qos *pqos,
> > struct iovec *data);
> >
> > +void bt_bap_stream_discard(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 [flat|nested] 15+ messages in thread
end of thread, other threads:[~2025-05-18 18:16 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-04 16:01 [RFC PATCH BlueZ v2 01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 02/11] org.bluez.MediaEndpoint: add client role Reconfigure Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 03/11] shared/bap: bap_abort_stream_req() should cancel also current req Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 04/11] shared/bap: fix ucast client ASE usage Pauli Virtanen
2025-05-06 16:56 ` Luiz Augusto von Dentz
2025-05-18 18:08 ` Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 05/11] bap: discard stream when no longer in use Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 06/11] bap: do not try QoS before links are updated & io created Pauli Virtanen
2025-05-04 16:01 ` [RFC PATCH BlueZ v2 07/11] bap: add ready callback for setup configuration Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 08/11] bap: support removing streams with ClearConfiguration() Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 09/11] bap: add callback at the end of ucast client select/config Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 10/11] bap: implement Reconfigure() Pauli Virtanen
2025-05-04 16:02 ` [RFC PATCH BlueZ v2 11/11] bap: delay recreating IO after ongoing configuration Pauli Virtanen
2025-05-04 17:36 ` [RFC,BlueZ,v2,01/11] org.bluez.MediaEndpoint: removing BAP streams with ClearConfiguration bluez.test.bot
2025-05-06 17:20 ` [RFC PATCH BlueZ v2 01/11] " patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox