diff options
Diffstat (limited to 'net/bluetooth/iso.c')
-rw-r--r-- | net/bluetooth/iso.c | 196 |
1 files changed, 157 insertions, 39 deletions
diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 7a83e400ac77..43d0ebe11100 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -35,6 +35,7 @@ struct iso_conn { struct sk_buff *rx_skb; __u32 rx_len; __u16 tx_sn; + struct kref ref; }; #define iso_conn_lock(c) spin_lock(&(c)->lock) @@ -93,6 +94,49 @@ static struct sock *iso_get_sock(bdaddr_t *src, bdaddr_t *dst, #define ISO_CONN_TIMEOUT (HZ * 40) #define ISO_DISCONN_TIMEOUT (HZ * 2) +static void iso_conn_free(struct kref *ref) +{ + struct iso_conn *conn = container_of(ref, struct iso_conn, ref); + + BT_DBG("conn %p", conn); + + if (conn->sk) + iso_pi(conn->sk)->conn = NULL; + + if (conn->hcon) { + conn->hcon->iso_data = NULL; + hci_conn_drop(conn->hcon); + } + + /* Ensure no more work items will run since hci_conn has been dropped */ + disable_delayed_work_sync(&conn->timeout_work); + + kfree(conn); +} + +static void iso_conn_put(struct iso_conn *conn) +{ + if (!conn) + return; + + BT_DBG("conn %p refcnt %d", conn, kref_read(&conn->ref)); + + kref_put(&conn->ref, iso_conn_free); +} + +static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn) +{ + if (!conn) + return NULL; + + BT_DBG("conn %p refcnt %u", conn, kref_read(&conn->ref)); + + if (!kref_get_unless_zero(&conn->ref)) + return NULL; + + return conn; +} + static struct sock *iso_sock_hold(struct iso_conn *conn) { if (!conn || !bt_sock_linked(&iso_sk_list, conn->sk)) @@ -109,9 +153,14 @@ static void iso_sock_timeout(struct work_struct *work) timeout_work.work); struct sock *sk; + conn = iso_conn_hold_unless_zero(conn); + if (!conn) + return; + iso_conn_lock(conn); sk = iso_sock_hold(conn); iso_conn_unlock(conn); + iso_conn_put(conn); if (!sk) return; @@ -149,9 +198,14 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) { struct iso_conn *conn = hcon->iso_data; + conn = iso_conn_hold_unless_zero(conn); if (conn) { - if (!conn->hcon) + if (!conn->hcon) { + iso_conn_lock(conn); conn->hcon = hcon; + iso_conn_unlock(conn); + } + iso_conn_put(conn); return conn; } @@ -159,6 +213,7 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) if (!conn) return NULL; + kref_init(&conn->ref); spin_lock_init(&conn->lock); INIT_DELAYED_WORK(&conn->timeout_work, iso_sock_timeout); @@ -178,17 +233,15 @@ static void iso_chan_del(struct sock *sk, int err) struct sock *parent; conn = iso_pi(sk)->conn; + iso_pi(sk)->conn = NULL; BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { iso_conn_lock(conn); conn->sk = NULL; - iso_pi(sk)->conn = NULL; iso_conn_unlock(conn); - - if (conn->hcon) - hci_conn_drop(conn->hcon); + iso_conn_put(conn); } sk->sk_state = BT_CLOSED; @@ -210,6 +263,7 @@ static void iso_conn_del(struct hci_conn *hcon, int err) struct iso_conn *conn = hcon->iso_data; struct sock *sk; + conn = iso_conn_hold_unless_zero(conn); if (!conn) return; @@ -219,20 +273,18 @@ static void iso_conn_del(struct hci_conn *hcon, int err) iso_conn_lock(conn); sk = iso_sock_hold(conn); iso_conn_unlock(conn); + iso_conn_put(conn); - if (sk) { - lock_sock(sk); - iso_sock_clear_timer(sk); - iso_chan_del(sk, err); - release_sock(sk); - sock_put(sk); + if (!sk) { + iso_conn_put(conn); + return; } - /* Ensure no more work items will run before freeing conn. */ - cancel_delayed_work_sync(&conn->timeout_work); - - hcon->iso_data = NULL; - kfree(conn); + lock_sock(sk); + iso_sock_clear_timer(sk); + iso_chan_del(sk, err); + release_sock(sk); + sock_put(sk); } static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, @@ -652,6 +704,8 @@ static void iso_sock_destruct(struct sock *sk) { BT_DBG("sk %p", sk); + iso_conn_put(iso_pi(sk)->conn); + skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); } @@ -711,6 +765,7 @@ static void iso_sock_disconn(struct sock *sk) */ if (bis_sk) { hcon->state = BT_OPEN; + hcon->iso_data = NULL; iso_pi(sk)->conn->hcon = NULL; iso_sock_clear_timer(sk); iso_chan_del(sk, bt_to_errno(hcon->abort_reason)); @@ -720,7 +775,6 @@ static void iso_sock_disconn(struct sock *sk) } sk->sk_state = BT_DISCONN; - iso_sock_set_timer(sk, ISO_DISCONN_TIMEOUT); iso_conn_lock(iso_pi(sk)->conn); hci_conn_drop(iso_pi(sk)->conn->hcon); iso_pi(sk)->conn->hcon = NULL; @@ -1075,6 +1129,7 @@ static int iso_listen_bis(struct sock *sk) return -EHOSTUNREACH; hci_dev_lock(hdev); + lock_sock(sk); /* Fail if user set invalid QoS */ if (iso_pi(sk)->qos_user_set && !check_bcast_qos(&iso_pi(sk)->qos)) { @@ -1104,10 +1159,10 @@ static int iso_listen_bis(struct sock *sk) goto unlock; } - hci_dev_put(hdev); - unlock: + release_sock(sk); hci_dev_unlock(hdev); + hci_dev_put(hdev); return err; } @@ -1134,6 +1189,7 @@ static int iso_sock_listen(struct socket *sock, int backlog) BT_DBG("sk %p backlog %d", sk, backlog); + sock_hold(sk); lock_sock(sk); if (sk->sk_state != BT_BOUND) { @@ -1146,10 +1202,16 @@ static int iso_sock_listen(struct socket *sock, int backlog) goto done; } - if (!bacmp(&iso_pi(sk)->dst, BDADDR_ANY)) + if (!bacmp(&iso_pi(sk)->dst, BDADDR_ANY)) { err = iso_listen_cis(sk); - else + } else { + /* Drop sock lock to avoid potential + * deadlock with the hdev lock. + */ + release_sock(sk); err = iso_listen_bis(sk); + lock_sock(sk); + } if (err) goto done; @@ -1161,6 +1223,7 @@ static int iso_sock_listen(struct socket *sock, int backlog) done: release_sock(sk); + sock_put(sk); return err; } @@ -1172,7 +1235,11 @@ static int iso_sock_accept(struct socket *sock, struct socket *newsock, long timeo; int err = 0; - lock_sock(sk); + /* Use explicit nested locking to avoid lockdep warnings generated + * because the parent socket and the child socket are locked on the + * same thread. + */ + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); timeo = sock_rcvtimeo(sk, arg->flags & O_NONBLOCK); @@ -1203,7 +1270,7 @@ static int iso_sock_accept(struct socket *sock, struct socket *newsock, release_sock(sk); timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo); - lock_sock(sk); + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); } remove_wait_queue(sk_sleep(sk), &wait); @@ -1338,6 +1405,14 @@ static void iso_conn_big_sync(struct sock *sk) if (!hdev) return; + /* hci_le_big_create_sync requires hdev lock to be held, since + * it enqueues the HCI LE BIG Create Sync command via + * hci_cmd_sync_queue_once, which checks hdev flags that might + * change. + */ + hci_dev_lock(hdev); + lock_sock(sk); + if (!test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) { err = hci_le_big_create_sync(hdev, iso_pi(sk)->conn->hcon, &iso_pi(sk)->qos, @@ -1348,6 +1423,9 @@ static void iso_conn_big_sync(struct sock *sk) bt_dev_err(hdev, "hci_le_big_create_sync: %d", err); } + + release_sock(sk); + hci_dev_unlock(hdev); } static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, @@ -1355,39 +1433,57 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, { struct sock *sk = sock->sk; struct iso_pinfo *pi = iso_pi(sk); + bool early_ret = false; + int err = 0; BT_DBG("sk %p", sk); if (test_and_clear_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { + sock_hold(sk); lock_sock(sk); + switch (sk->sk_state) { case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { + release_sock(sk); iso_conn_big_sync(sk); + lock_sock(sk); + sk->sk_state = BT_LISTEN; } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; } - release_sock(sk); - return 0; + + early_ret = true; + break; case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { + release_sock(sk); iso_conn_big_sync(sk); + lock_sock(sk); + sk->sk_state = BT_LISTEN; - release_sock(sk); - return 0; + early_ret = true; } - release_sock(sk); break; case BT_CONNECT: release_sock(sk); - return iso_connect_cis(sk); + err = iso_connect_cis(sk); + lock_sock(sk); + + early_ret = true; + break; default: - release_sock(sk); break; } + + release_sock(sk); + sock_put(sk); + + if (early_ret) + return err; } return bt_sock_recvmsg(sock, msg, len, flags); @@ -1503,7 +1599,7 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; } - err = bt_copy_from_sockptr(&opt, sizeof(opt), optval, optlen); + err = copy_safe_from_sockptr(&opt, sizeof(opt), optval, optlen); if (err) break; @@ -1514,7 +1610,7 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; case BT_PKT_STATUS: - err = bt_copy_from_sockptr(&opt, sizeof(opt), optval, optlen); + err = copy_safe_from_sockptr(&opt, sizeof(opt), optval, optlen); if (err) break; @@ -1533,7 +1629,7 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; } - err = bt_copy_from_sockptr(&qos, sizeof(qos), optval, optlen); + err = copy_safe_from_sockptr(&qos, sizeof(qos), optval, optlen); if (err) break; @@ -1554,8 +1650,8 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; } - err = bt_copy_from_sockptr(iso_pi(sk)->base, optlen, optval, - optlen); + err = copy_safe_from_sockptr(iso_pi(sk)->base, optlen, optval, + optlen); if (err) break; @@ -1733,6 +1829,13 @@ static bool iso_match_big(struct sock *sk, void *data) return ev->handle == iso_pi(sk)->qos.bcast.big; } +static bool iso_match_big_hcon(struct sock *sk, void *data) +{ + struct hci_conn *hcon = data; + + return hcon->iso_qos.bcast.big == iso_pi(sk)->qos.bcast.big; +} + static bool iso_match_pa_sync_flag(struct sock *sk, void *data) { return test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags); @@ -1756,8 +1859,16 @@ static void iso_conn_ready(struct iso_conn *conn) if (!hcon) return; - if (test_bit(HCI_CONN_BIG_SYNC, &hcon->flags) || - test_bit(HCI_CONN_BIG_SYNC_FAILED, &hcon->flags)) { + if (test_bit(HCI_CONN_BIG_SYNC, &hcon->flags)) { + /* A BIS slave hcon is notified to the ISO layer + * after the Command Complete for the LE Setup + * ISO Data Path command is received. Get the + * parent socket that matches the hcon BIG handle. + */ + parent = iso_get_sock(&hcon->src, &hcon->dst, + BT_LISTEN, iso_match_big_hcon, + hcon); + } else if (test_bit(HCI_CONN_BIG_SYNC_FAILED, &hcon->flags)) { ev = hci_recv_event_data(hcon->hdev, HCI_EVT_LE_BIG_SYNC_ESTABILISHED); @@ -1824,7 +1935,6 @@ static void iso_conn_ready(struct iso_conn *conn) if (!bacmp(&hcon->dst, BDADDR_ANY)) { bacpy(&hcon->dst, &iso_pi(parent)->dst); hcon->dst_type = iso_pi(parent)->dst_type; - hcon->sync_handle = iso_pi(parent)->sync_handle; } if (ev3) { @@ -1942,6 +2052,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) if (sk) { int err; + struct hci_conn *hcon = iso_pi(sk)->conn->hcon; iso_pi(sk)->qos.bcast.encryption = ev2->encryption; @@ -1950,7 +2061,8 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) if (!test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags) && !test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) { - err = hci_le_big_create_sync(hdev, NULL, + err = hci_le_big_create_sync(hdev, + hcon, &iso_pi(sk)->qos, iso_pi(sk)->sync_handle, iso_pi(sk)->bc_num_bis, @@ -2039,6 +2151,11 @@ done: return HCI_LM_ACCEPT; } +static bool iso_match(struct hci_conn *hcon) +{ + return hcon->type == ISO_LINK || hcon->type == LE_LINK; +} + static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) { if (hcon->type != ISO_LINK) { @@ -2220,6 +2337,7 @@ drop: static struct hci_cb iso_cb = { .name = "ISO", + .match = iso_match, .connect_cfm = iso_connect_cfm, .disconn_cfm = iso_disconn_cfm, }; |