Bluetooth: ISO: Notify user space about failed bis connections

Some use cases require the user to be informed if BIG synchronization
fails. This commit makes it so that even if the BIG sync established
event arrives with error status, a new hconn is added for each BIS,
and the iso layer is notified about the failed connections.

Unsuccesful bis connections will be marked using the
HCI_CONN_BIG_SYNC_FAILED flag. From the iso layer, the POLLERR event
is triggered on the newly allocated bis sockets, before adding them
to the accept list of the parent socket.

From user space, a new fd for each failed bis connection will be
obtained by calling accept. The user should check for the POLLERR
event on the new socket, to determine if the connection was successful
or not.

The HCI_CONN_BIG_SYNC flag has been added to mark whether the BIG sync
has been successfully established. This flag is checked at bis cleanup,
so the HCI LE BIG Terminate Sync command is only issued if needed.

The BT_SK_BIG_SYNC flag indicates if BIG create sync has been called
for a listening socket, to avoid issuing the command everytime a BIGInfo
advertising report is received.

Signed-off-by: Iulia Tanasescu <iulia.tanasescu@nxp.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Iulia Tanasescu 2023-07-03 10:02:38 +03:00 committed by Luiz Augusto von Dentz
parent e160a8f4e9
commit f777d88278
5 changed files with 93 additions and 38 deletions

View File

@ -976,6 +976,8 @@ enum {
HCI_CONN_PER_ADV,
HCI_CONN_BIG_CREATED,
HCI_CONN_CREATE_CIS,
HCI_CONN_BIG_SYNC,
HCI_CONN_BIG_SYNC_FAILED,
};
static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
@ -1286,6 +1288,29 @@ 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_any_dst(struct hci_dev *hdev,
__u8 handle)
{
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 (c->type != ISO_LINK)
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_state(struct hci_dev *hdev,
__u8 type, __u16 state)
{

View File

@ -735,6 +735,7 @@ struct iso_list_data {
int count;
struct iso_cig_params pdu;
bool big_term;
bool big_sync_term;
};
static void bis_list(struct hci_conn *conn, void *data)
@ -752,17 +753,6 @@ static void bis_list(struct hci_conn *conn, void *data)
d->count++;
}
static void find_bis(struct hci_conn *conn, void *data)
{
struct iso_list_data *d = data;
/* Ignore unicast */
if (bacmp(&conn->dst, BDADDR_ANY))
return;
d->count++;
}
static int terminate_big_sync(struct hci_dev *hdev, void *data)
{
struct iso_list_data *d = data;
@ -815,31 +805,26 @@ static int big_terminate_sync(struct hci_dev *hdev, void *data)
bt_dev_dbg(hdev, "big 0x%2.2x sync_handle 0x%4.4x", d->big,
d->sync_handle);
/* Check if ISO connection is a BIS and terminate BIG if there are
* no other connections using it.
*/
hci_conn_hash_list_state(hdev, find_bis, ISO_LINK, BT_CONNECTED, d);
if (d->count)
return 0;
if (d->big_sync_term)
hci_le_big_terminate_sync(hdev, d->big);
return hci_le_pa_terminate_sync(hdev, d->sync_handle);
}
static int hci_le_big_terminate(struct hci_dev *hdev, u8 big, u16 sync_handle)
static int hci_le_big_terminate(struct hci_dev *hdev, u8 big, struct hci_conn *conn)
{
struct iso_list_data *d;
int ret;
bt_dev_dbg(hdev, "big 0x%2.2x sync_handle 0x%4.4x", big, sync_handle);
bt_dev_dbg(hdev, "big 0x%2.2x sync_handle 0x%4.4x", big, conn->sync_handle);
d = kzalloc(sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->big = big;
d->sync_handle = sync_handle;
d->sync_handle = conn->sync_handle;
d->big_sync_term = test_and_clear_bit(HCI_CONN_BIG_SYNC, &conn->flags);
ret = hci_cmd_sync_queue(hdev, big_terminate_sync, d,
terminate_big_destroy);
@ -875,8 +860,14 @@ static void bis_cleanup(struct hci_conn *conn)
hci_le_terminate_big(hdev, conn);
} else {
bis = hci_conn_hash_lookup_big_any_dst(hdev,
conn->iso_qos.bcast.big);
if (bis)
return;
hci_le_big_terminate(hdev, conn->iso_qos.bcast.big,
conn->sync_handle);
conn);
}
}

View File

@ -7039,9 +7039,6 @@ static void hci_le_big_sync_established_evt(struct hci_dev *hdev, void *data,
flex_array_size(ev, bis, ev->num_bis)))
return;
if (ev->status)
return;
hci_dev_lock(hdev);
for (i = 0; i < ev->num_bis; i++) {
@ -7065,8 +7062,24 @@ static void hci_le_big_sync_established_evt(struct hci_dev *hdev, void *data,
bis->iso_qos.bcast.in.latency = le16_to_cpu(ev->interval) * 125 / 100;
bis->iso_qos.bcast.in.sdu = le16_to_cpu(ev->max_pdu);
if (!ev->status) {
set_bit(HCI_CONN_BIG_SYNC, &bis->flags);
hci_iso_setup_path(bis);
}
}
/* In case BIG sync failed, notify each failed connection to
* the user after all hci connections have been added
*/
if (ev->status)
for (i = 0; i < ev->num_bis; i++) {
u16 handle = le16_to_cpu(ev->bis[i]);
bis = hci_conn_hash_lookup_handle(hdev, handle);
set_bit(HCI_CONN_BIG_SYNC_FAILED, &bis->flags);
hci_connect_cfm(bis, ev->status);
}
hci_dev_unlock(hdev);
}

View File

@ -5395,6 +5395,14 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
return err;
case BT_CONNECT2:
return hci_reject_conn_sync(hdev, conn, reason);
case BT_OPEN:
/* Cleanup bises that failed to be established */
if (test_and_clear_bit(HCI_CONN_BIG_SYNC_FAILED, &conn->flags)) {
hci_dev_lock(hdev);
hci_conn_failed(conn, reason);
hci_dev_unlock(hdev);
}
break;
default:
conn->state = BT_CLOSED;
break;

View File

@ -48,6 +48,11 @@ static void iso_sock_kill(struct sock *sk);
#define EIR_SERVICE_DATA_LENGTH 4
#define BASE_MAX_LENGTH (HCI_MAX_PER_AD_LENGTH - EIR_SERVICE_DATA_LENGTH)
/* iso_pinfo flags values */
enum {
BT_SK_BIG_SYNC,
};
struct iso_pinfo {
struct bt_sock bt;
bdaddr_t src;
@ -58,7 +63,7 @@ struct iso_pinfo {
__u8 bc_num_bis;
__u8 bc_bis[ISO_MAX_NUM_BIS];
__u16 sync_handle;
__u32 flags;
unsigned long flags;
struct bt_iso_qos qos;
bool qos_user_set;
__u8 base_len;
@ -1555,6 +1560,12 @@ static void iso_conn_ready(struct iso_conn *conn)
hci_conn_hold(hcon);
iso_chan_add(conn, sk, parent);
if (ev && ((struct hci_evt_le_big_sync_estabilished *)ev)->status) {
/* Trigger error signal on child socket */
sk->sk_err = ECONNREFUSED;
sk->sk_error_report(sk);
}
if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags))
sk->sk_state = BT_CONNECT2;
else
@ -1623,6 +1634,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
if (ev2->num_bis < iso_pi(sk)->bc_num_bis)
iso_pi(sk)->bc_num_bis = ev2->num_bis;
if (!test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) {
err = hci_le_big_create_sync(hdev,
&iso_pi(sk)->qos,
iso_pi(sk)->sync_handle,
@ -1634,6 +1646,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
sk = NULL;
}
}
}
} else {
sk = iso_get_sock_listen(&hdev->bdaddr, BDADDR_ANY, NULL, NULL);
}
@ -1674,7 +1687,12 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status)
BT_DBG("hcon %p bdaddr %pMR status %d", hcon, &hcon->dst, status);
if (!status) {
/* Similar to the success case, if HCI_CONN_BIG_SYNC_FAILED is set,
* queue the failed bis connection into the accept queue of the
* listening socket and wake up userspace, to inform the user about
* the BIG sync failed event.
*/
if (!status || test_bit(HCI_CONN_BIG_SYNC_FAILED, &hcon->flags)) {
struct iso_conn *conn;
conn = iso_conn_add(hcon);