Re: [PATCH BlueZ] transport: don't disconnect A2DP if canceling Acquire() with Release()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Luiz,

ma, 2024-10-28 kello 10:39 -0400, Luiz Augusto von Dentz kirjoitti:
> On Sat, Oct 26, 2024 at 6:41 AM Pauli Virtanen <pav@xxxxxx> wrote:
> > 
> > User can cancel transport acquire by calling Release() while Acquire()
> > is in progress. This calls a2dp_cancel() which sends AVDTP_ABORT_CMD,
> > forcing AVDTP state transition to IDLE, and disconnecting A2DP profile,
> > which is not desired.
> > 
> > Fix by instead waiting until START completes and then send SUSPEND. Make
> > Release() reply only after the whole sequence completes.
> 
> Hmm, but assumes the client is not reconfiguring to another endpoint
> or just giving up on the transport since it doesn't intend to use it
> anymore, anyway we can't really send anything else other than abort if
> we don't want to wait so I think this would be better to be handled by

I guess you have in mind

	Acquire(t1)
	Release(t1)  # canceling acquire
	SetConfiguration(ep2)

I don't think this works right in current BlueZ master branch either,
the Release() results to removing the DBus endpoints & AVDTP
disconnect, so nothing to SetConfigure() after abort completes.
SetConfiguration() probably fails also if called while Abort_Cfm is
pending, since it cannot then close the existing stream.

I think (but did not test) that with the patch here, it would end up
doing START -> CLOSE -> reconfigure, so indeed has to wait for START
completion.

> the application if it intends to suspend then it should wait acquire
> to complete and then release.

I think it's a bug in BlueZ that calling Release() on transport result
to disconnect of A2DP, instead of suspend.

I have another version of this patch that does send ABORT, but recalls
it was due to canceled suspend and reconnects the sink/source when
getting to IDLE.

> > Fix also sending error reply to the canceled Acquire() call, which was
> > missing previously.
> > ---
> > 
> > Notes:
> >     In theory we could also send ABORT and reconfigure the SEP again after
> >     that. It's more hairy though as with how the code currently works, we
> >     may need to complete discovery first. This is a corner case anyway, so
> >     just waiting a bit should be easier.
> > 
> >  profiles/audio/transport.c | 152 +++++++++++++++++++++++++++----------
> >  1 file changed, 110 insertions(+), 42 deletions(-)
> > 
> > diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
> > index 0f7909a94..4d5afe022 100644
> > --- a/profiles/audio/transport.c
> > +++ b/profiles/audio/transport.c
> > @@ -88,6 +88,9 @@ struct a2dp_transport {
> >         uint16_t                delay;
> >         int8_t                  volume;
> >         guint                   watch;
> > +       guint                   resume_id;
> > +       gboolean                cancel_resume;
> > +       guint                   cancel_id;
> >  };
> > 
> >  struct bap_transport {
> > @@ -393,22 +396,82 @@ static void *transport_a2dp_get_stream(struct media_transport *transport)
> >         return a2dp_sep_get_stream(sep);
> >  }
> > 
> > +static void a2dp_suspend_complete(struct avdtp *session, int err,
> > +                                                       void *user_data)
> > +{
> > +       struct media_owner *owner = user_data;
> > +       struct media_transport *transport = owner->transport;
> > +       struct a2dp_transport *a2dp = transport->data;
> > +       struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
> > +
> > +       /* Release always succeeds */
> > +       if (owner->pending) {
> > +               owner->pending->id = 0;
> > +               media_request_reply(owner->pending, 0);
> > +               media_owner_remove(owner);
> > +       }
> > +
> > +       a2dp_sep_unlock(sep, a2dp->session);
> > +       transport_set_state(transport, TRANSPORT_STATE_IDLE);
> > +       media_transport_remove_owner(transport);
> > +}
> > +
> > +static guint transport_a2dp_suspend(struct media_transport *transport,
> > +                                               struct media_owner *owner)
> > +{
> > +       struct a2dp_transport *a2dp = transport->data;
> > +       struct media_endpoint *endpoint = transport->endpoint;
> > +       struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
> > +
> > +       if (a2dp->cancel_resume)
> > +               return a2dp->resume_id;
> > +
> > +       if (owner != NULL)
> > +               return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete,
> > +                                                                       owner);
> > +
> > +       transport_set_state(transport, TRANSPORT_STATE_IDLE);
> > +       a2dp_sep_unlock(sep, a2dp->session);
> > +
> > +       return 0;
> > +}
> > +
> > +static gboolean a2dp_cancel_resume_cb(void *user_data)
> > +{
> > +       struct media_owner *owner = user_data;
> > +       struct media_transport *transport = owner->transport;
> > +       struct a2dp_transport *a2dp = transport->data;
> > +
> > +       a2dp->cancel_id = 0;
> > +       a2dp->cancel_resume = FALSE;
> > +       owner->pending->id = transport_a2dp_suspend(transport, owner);
> > +       return FALSE;
> > +}
> > +
> >  static void a2dp_resume_complete(struct avdtp *session, int err,
> >                                                         void *user_data)
> >  {
> >         struct media_owner *owner = user_data;
> >         struct media_request *req = owner->pending;
> >         struct media_transport *transport = owner->transport;
> > +       struct a2dp_transport *a2dp = transport->data;
> >         struct avdtp_stream *stream;
> >         int fd;
> >         uint16_t imtu, omtu;
> >         gboolean ret;
> > 
> > +       a2dp->resume_id = 0;
> >         req->id = 0;
> > 
> >         if (err)
> >                 goto fail;
> > 
> > +       if (a2dp->cancel_resume) {
> > +               DBG("cancel resume");
> > +               a2dp->cancel_id = g_idle_add(a2dp_cancel_resume_cb, owner);
> > +               return;
> > +       }
> > +
> >         stream = transport_a2dp_get_stream(transport);
> >         if (stream == NULL)
> >                 goto fail;
> > @@ -445,15 +508,21 @@ static guint transport_a2dp_resume(struct media_transport *transport,
> >         struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
> >         guint id;
> > 
> > +       if (a2dp->resume_id || a2dp->cancel_id)
> > +               return -EBUSY;
> > +
> >         if (a2dp->session == NULL) {
> >                 a2dp->session = a2dp_avdtp_get(transport->device);
> >                 if (a2dp->session == NULL)
> >                         return 0;
> >         }
> > 
> > -       if (state_in_use(transport->state))
> > -               return a2dp_resume(a2dp->session, sep, a2dp_resume_complete,
> > +       if (state_in_use(transport->state)) {
> > +               id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete,
> >                                                                         owner);
> > +               a2dp->resume_id = id;
> > +               return id;
> > +       }
> > 
> >         if (a2dp_sep_lock(sep, a2dp->session) == FALSE)
> >                 return 0;
> > @@ -468,51 +537,47 @@ static guint transport_a2dp_resume(struct media_transport *transport,
> >         if (transport->state == TRANSPORT_STATE_IDLE)
> >                 transport_set_state(transport, TRANSPORT_STATE_REQUESTING);
> > 
> > +       a2dp->resume_id = id;
> >         return id;
> >  }
> > 
> > -static void a2dp_suspend_complete(struct avdtp *session, int err,
> > -                                                       void *user_data)
> > -{
> > -       struct media_owner *owner = user_data;
> > -       struct media_transport *transport = owner->transport;
> > -       struct a2dp_transport *a2dp = transport->data;
> > -       struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
> > -
> > -       /* Release always succeeds */
> > -       if (owner->pending) {
> > -               owner->pending->id = 0;
> > -               media_request_reply(owner->pending, 0);
> > -               media_owner_remove(owner);
> > -       }
> > -
> > -       a2dp_sep_unlock(sep, a2dp->session);
> > -       transport_set_state(transport, TRANSPORT_STATE_IDLE);
> > -       media_transport_remove_owner(transport);
> > -}
> > -
> > -static guint transport_a2dp_suspend(struct media_transport *transport,
> > -                                               struct media_owner *owner)
> > -{
> > -       struct a2dp_transport *a2dp = transport->data;
> > -       struct media_endpoint *endpoint = transport->endpoint;
> > -       struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
> > -
> > -       if (owner != NULL)
> > -               return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete,
> > -                                                                       owner);
> > -
> > -       transport_set_state(transport, TRANSPORT_STATE_IDLE);
> > -       a2dp_sep_unlock(sep, a2dp->session);
> > -
> > -       return 0;
> > -}
> > -
> >  static void transport_a2dp_cancel(struct media_transport *transport, guint id)
> >  {
> > +       struct a2dp_transport *a2dp = transport->data;
> > +
> > +       if (a2dp->resume_id && a2dp->resume_id == id) {
> > +               /* a2dp_cancel() results to ABORT->IDLE->disconnect. Canceling
> > +                * START can be triggered by user via Release(), and it's better
> > +                * to not drop the A2DP connection then, so we just suspend
> > +                * after resume completes.
> > +                */
> > +               a2dp->cancel_resume = TRUE;
> > +               return;
> > +       }
> > +
> >         a2dp_cancel(id);
> >  }
> > 
> > +static void transport_a2dp_remove_owner(struct media_transport *transport,
> > +                                       struct media_owner *owner)
> > +{
> > +       struct a2dp_transport *a2dp = transport->data;
> > +
> > +       /* Clean up callbacks that refer to the owner */
> > +
> > +       if (a2dp->cancel_id) {
> > +               g_source_remove(a2dp->cancel_id);
> > +               a2dp->cancel_id = 0;
> > +       }
> > +
> > +       if (a2dp->resume_id) {
> > +               a2dp_cancel(a2dp->resume_id);
> > +               a2dp->resume_id = 0;
> > +       }
> > +
> > +       a2dp->cancel_resume = FALSE;
> > +}
> > +
> >  static int8_t transport_a2dp_get_volume(struct media_transport *transport)
> >  {
> >         struct a2dp_transport *a2dp = transport->data;
> > @@ -773,10 +838,12 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
> > 
> >                 member = dbus_message_get_member(owner->pending->msg);
> >                 /* Cancel Acquire request if that exist */
> > -               if (g_str_equal(member, "Acquire"))
> > +               if (g_str_equal(member, "Acquire")) {
> > +                       media_request_reply(owner->pending, ECANCELED);
> >                         media_owner_remove(owner);
> > -               else
> > +               } else {
> >                         return btd_error_in_progress(msg);
> > +               }
> >         }
> > 
> >         transport_set_state(transport, TRANSPORT_STATE_SUSPENDING);
> > @@ -2189,7 +2256,8 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
> >  }
> > 
> >  #define A2DP_OPS(_uuid, _init, _set_volume, _destroy) \
> > -       TRANSPORT_OPS(_uuid, transport_a2dp_properties, NULL, NULL, _init, \
> > +       TRANSPORT_OPS(_uuid, transport_a2dp_properties, NULL, \
> > +                       transport_a2dp_remove_owner, _init, \
> >                         transport_a2dp_resume, transport_a2dp_suspend, \
> >                         transport_a2dp_cancel, NULL, \
> >                         transport_a2dp_get_stream, transport_a2dp_get_volume, \
> > --
> > 2.47.0
> > 
> > 
> 
> 






[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux