Re: [PATCH 1/1] Bluetooth: ISO: Reassociate a socket with an active BIS

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

 



Hi Pauli,

> -----Original Message-----
> From: Pauli Virtanen <pav@xxxxxx>
> Sent: Tuesday, October 31, 2023 12:13 AM
> To: Iulia Tanasescu <iulia.tanasescu@xxxxxxx>; linux-
> bluetooth@xxxxxxxxxxxxxxx
> Subject: Re: [PATCH 1/1] Bluetooth: ISO: Reassociate a socket with an
> active BIS 
> 
> Hi,
> 
> ma, 2023-10-30 kello 17:43 +0200, Iulia Tanasescu kirjoitti:
> > For ISO Broadcast, all BISes from a BIG have the same lifespan - they
> > cannot be created or terminated independently from each other.
> >
> > This links together all BIS hcons that are part of the same BIG, so
> > all hcons are kept alive as long as the BIG is active.
> >
> > If multiple BIS sockets are opened for a BIG handle, and only part of
> > them are closed at some point, the associated hcons will be marked as
> > open. If new sockets will later be opened for the same BIG, they will
> > be reassociated with the open BIS hcons.
> 
> Can you explain here a bit the new BIS hci_conn refcounting scheme, it's
> not straightforward to follow? Who owns their refcounts?
> 
> >
> > All BIS hcons will be cleaned up and the BIG will be terminated when
> > the last BIS socket is closed from userspace.
> >
> > Signed-off-by: Iulia Tanasescu <iulia.tanasescu@xxxxxxx>
> > ---
> >  include/net/bluetooth/hci_core.h | 24 +++++++++++++
> >  net/bluetooth/hci_conn.c         | 27 ++++++++++++++
> >  net/bluetooth/iso.c              | 60 ++++++++++++++++++++++++++++++++
> >  3 files changed, 111 insertions(+)
> >
> > diff --git a/include/net/bluetooth/hci_core.h
> > b/include/net/bluetooth/hci_core.h
> > index 20988623c5cc..201c0809540a 100644
> > --- a/include/net/bluetooth/hci_core.h
> > +++ b/include/net/bluetooth/hci_core.h
> > @@ -1292,6 +1292,30 @@ static inline struct hci_conn
> *hci_conn_hash_lookup_big(struct hci_dev *hdev,
> >       return NULL;
> >  }
> >
> > +static inline struct hci_conn *
> > +hci_conn_hash_lookup_big_state(struct hci_dev *hdev, __u8 handle,
> > +__u16 state) {
> > +     struct hci_conn_hash *h = &hdev->conn_hash;
> > +     struct hci_conn  *c;
> > +
> > +     rcu_read_lock();
> > +
> > +     list_for_each_entry_rcu(c, &h->list, list) {
> > +             if (bacmp(&c->dst, BDADDR_ANY) || c->type != ISO_LINK ||
> > +                     c->state != state)
> > +                     continue;
> > +
> > +             if (handle == c->iso_qos.bcast.big) {
> > +                     rcu_read_unlock();
> > +                     return c;
> > +             }
> > +     }
> > +
> > +     rcu_read_unlock();
> > +
> > +     return NULL;
> > +}
> > +
> >  static inline struct hci_conn *
> >  hci_conn_hash_lookup_pa_sync_big_handle(struct hci_dev *hdev, __u8
> > big)  { diff --git a/net/bluetooth/hci_conn.c
> > b/net/bluetooth/hci_conn.c index 2cee330188ce..b8ab5c0cd48e 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -2228,7 +2228,17 @@ struct hci_conn *hci_bind_bis(struct hci_dev
> *hdev, bdaddr_t *dst,
> >                             __u8 base_len, __u8 *base)  {
> >       struct hci_conn *conn;
> > +     struct hci_conn *parent;
> >       __u8 eir[HCI_MAX_PER_AD_LENGTH];
> > +     struct hci_link *link;
> > +
> > +     /* Look for any BIS that is open for rebinding */
> > +     conn = hci_conn_hash_lookup_big_state(hdev, qos->bcast.big,
> BT_OPEN);
> > +     if (conn) {
> > +             memcpy(qos, &conn->iso_qos, sizeof(*qos));
> > +             conn->state = BT_CONNECTED;
> > +             return conn;
> > +     }
> >
> >       if (base_len && base)
> >               base_len = eir_append_service_data(eir, 0,  0x1851, @@
> > -2256,6 +2266,20 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev,
> bdaddr_t *dst,
> >       conn->iso_qos = *qos;
> >       conn->state = BT_BOUND;
> >
> > +     /* Link BISes together */
> > +     parent = hci_conn_hash_lookup_big(hdev,
> > +                                       conn->iso_qos.bcast.big);
> > +     if (parent && parent != conn) {
> > +             link = hci_conn_link(parent, conn);
> > +             if (!link) {
> > +                     hci_conn_drop(conn);
> > +                     return ERR_PTR(-ENOLINK);
> > +             }
> > +
> > +             /* Link takes the refcount */
> > +             hci_conn_drop(conn);
> > +     }
> 
> So the first hci_conn added in a BIG is assigned as "parent". How does the
> refcounting here work?
> 
> hci_conn_link takes refcount of the parent (cf. hci_conn_unlink), but it's not
> incremented here so I guess it may go negative.

When adding a new BIS hcon, hci_add_bis will take the refcount.

The first BIS in a BIG will be assigned as parent. When new BISes are added,
they will be linked to the parent and hci_conn_link takes an extra refcount
for children:
https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/tree/net/bluetooth/hci_conn.c#n1724

This is why hci_conn_drop needs to be called for children BISes, to remove
the duplicate refcount.

> 
> hci_conn_unlink does not drop child connections, and they're not dropped
> by the socket closing below unless last, so I'm not sure if they are then
> cleaned up if closing sockets in any order.

If hci_conn_unlink is called for a child hcon, the child will be unlinked
and hci_conn_drop will be called on the parent:
https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/tree/net/bluetooth/hci_conn.c#n1130

hci_conn_unlink will then be called for the parent, and all other children
hcons will be cleaned up with hci_conn_cleanup_child.

So regardless if the parent is closed first or any child, all hcons will get
to be cleaned up.

> 
> This change will also expose BIS hci_conns to hci_conn_cleanup_child,
> which was written only for unicast, and might not be right for the use case
> here.

That is right, from the tests I performed I noticed the conditions in
hci_conn_cleanup_child are also met for BIS so it will reach
hci_conn_failed, but I submitted an updated patch to add a dedicated
condition for broadcast also, to cover all cases.

> 
> > +
> >       return conn;
> >  }
> >
> > @@ -2287,6 +2311,9 @@ struct hci_conn *hci_connect_bis(struct hci_dev
> *hdev, bdaddr_t *dst,
> >       if (IS_ERR(conn))
> >               return conn;
> >
> > +     if (conn->state == BT_CONNECTED)
> > +             return conn;
> > +
> >       data.big = qos->bcast.big;
> >       data.bis = qos->bcast.bis;
> >
> > diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index
> > e01b6abe36fb..13353d7dc4b1 100644
> > --- a/net/bluetooth/iso.c
> > +++ b/net/bluetooth/iso.c
> > @@ -587,6 +587,44 @@ static struct sock *iso_get_sock_listen(bdaddr_t
> *src, bdaddr_t *dst,
> >       return sk ? sk : sk1;
> >  }
> >
> > +static struct sock *iso_get_sock_big(struct sock *match_sk, bdaddr_t *src,
> > +                                  bdaddr_t *dst, uint8_t big) {
> > +     struct sock *sk = NULL;
> > +
> > +     read_lock(&iso_sk_list.lock);
> > +
> > +     sk_for_each(sk, &iso_sk_list.head) {
> > +             if (match_sk == sk)
> > +                     continue;
> > +
> > +             /* Look for sockets that have already been
> > +              * connected to the BIG
> > +              */
> > +             if (sk->sk_state != BT_CONNECTED &&
> > +                 sk->sk_state != BT_CONNECT)
> > +                     continue;
> > +
> > +             /* Match Broadcast destination */
> > +             if (bacmp(&iso_pi(sk)->dst, dst))
> > +                     continue;
> > +
> > +             /* Match BIG handle */
> > +             if (iso_pi(sk)->qos.bcast.big != big)
> > +                     continue;
> > +
> > +             /* Match source address */
> > +             if (bacmp(&iso_pi(sk)->src, src))
> > +                     continue;
> > +
> > +             break;
> > +     }
> > +
> > +     read_unlock(&iso_sk_list.lock);
> > +
> > +     return sk;
> 
> What keeps sk alive?
> 
> This pattern is used also in iso_get_sock_listen, but I don't understand why
> it's OK. In unix/ & netrom/ & ax25/ I see sock_hold before lock release in
> similar constructs.

It seems like it might be necessary to add sock_hold to make sure the
socket is kept alive while it is being processed, so I added this update
in the new patch version.

> 
> > +}
> > +
> >  static void iso_sock_destruct(struct sock *sk)  {
> >       BT_DBG("sk %p", sk);
> > @@ -639,6 +677,28 @@ static void iso_sock_kill(struct sock *sk)
> >
> >  static void iso_sock_disconn(struct sock *sk)  {
> > +     struct sock *bis_sk;
> > +     struct hci_conn *hcon = iso_pi(sk)->conn->hcon;
> > +
> > +     if (test_bit(HCI_CONN_BIG_CREATED, &hcon->flags)) {
> > +             bis_sk = iso_get_sock_big(sk, &iso_pi(sk)->src,
> > +                                       &iso_pi(sk)->dst,
> > +                                       iso_pi(sk)->qos.bcast.big);
> > +
> > +             /* If there are any other connected sockets for the
> > +              * same BIG, just delete the sk and leave the bis
> > +              * hcon active, in case later rebinding is needed.
> > +              */
> > +             if (bis_sk) {
> > +                     hcon->state = BT_OPEN;
> > +                     iso_pi(sk)->conn->hcon = NULL;
> > +                     release_sock(sk);
> > +                     iso_conn_del(hcon,
> > + bt_to_errno(hcon->abort_reason));
> 
> iso_conn_del must be called with hdev->lock held, that's assumed in
> iso_connect*.
> 
> Locks must be taken in order hci_dev_lock > lock_sock > iso_conn_lock.
> 
> Could this use iso_chan_del instead (plus maybe iso_sock_clear_timer)?
> Lifetime of iso_conn currently follows hcon, not sure if that needs to be
> changed?

I agree, I could use iso_chan_del here, so only the socket is cleaned up,
and the ISO conn will remain allocated as long as hcon is alive. I updated
in the new patch.

> 
> > +                     lock_sock(sk);
> > +                     return;
> > +             }
> > +     }
> > +
> >       sk->sk_state = BT_DISCONN;
> >       iso_sock_set_timer(sk, ISO_DISCONN_TIMEOUT);
> >       iso_conn_lock(iso_pi(sk)->conn);
> 
> Not related to this patch precisely, but I suspect the disconnect timeout is
> something that is not useful for ISO sockets and maybe we should remove it,
> since closing sockets is used for CIG/BIG management.

I think the disconnect timeout makes more sense for CIS management, because
when a CIS socket is closed, HCI_OP_DISCONNECT is first sent to controller
to disconnect the CIS, and only after HCI_EV_DISCONN_COMPLETE is received,
the hcon will be cleaned up and the CIG will be removed.

> 
> --
> Pauli Virtanen

Regards,
Iulia




[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