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,

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.

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.

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.

> +
>  	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.

> +}
> +
>  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?

> +			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.

-- 
Pauli Virtanen




[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