diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index b04a5a02ecf3..3e65fcc8c9af 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -43,6 +43,8 @@ static DEFINE_IDA(sock_cookie_ida); static atomic_t monitor_promisc = ATOMIC_INIT(0); +static DEFINE_MUTEX(dev_event_lock); + /* ----- HCI socket interface ----- */ /* Socket info */ @@ -57,6 +59,7 @@ struct hci_pinfo { unsigned long flags; __u32 cookie; char comm[TASK_COMM_LEN]; + unsigned int event_in_progress; }; void hci_sock_set_flag(struct sock *sk, int nr) @@ -758,10 +761,15 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event) if (event == HCI_DEV_UNREG) { struct sock *sk; + int put_count = 0; /* Detach sockets from device */ + mutex_lock(&dev_event_lock); read_lock(&hci_sk_list.lock); sk_for_each(sk, &hci_sk_list.head) { + read_unlock(&hci_sk_list.lock); + hci_pi(sk)->event_in_progress++; + mutex_unlock(&dev_event_lock); lock_sock(sk); if (hci_pi(sk)->hdev == hdev) { hci_pi(sk)->hdev = NULL; @@ -769,11 +777,17 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event) sk->sk_state = BT_OPEN; sk->sk_state_change(sk); - hci_dev_put(hdev); + put_count++; } release_sock(sk); + mutex_lock(&dev_event_lock); + hci_pi(sk)->event_in_progress--; + read_lock(&hci_sk_list.lock); } read_unlock(&hci_sk_list.lock); + mutex_unlock(&dev_event_lock); + while (put_count--) + hci_dev_put(hdev); } } @@ -838,6 +852,26 @@ static int hci_sock_release(struct socket *sock) if (!sk) return 0; + /* + * Wait for sk_for_each() in hci_sock_dev_event() to stop accessing + * this sk before unlinking. Need to unlink before lock_sock(), for + * hci_sock_dev_event() calls lock_sock() after incrementing + * event_in_progress counter. + */ + while (1) { + bool unlinked = true; + + mutex_lock(&dev_event_lock); + if (!hci_pi(sk)->event_in_progress) + bt_sock_unlink(&hci_sk_list, sk); + else + unlinked = false; + mutex_unlock(&dev_event_lock); + if (unlinked) + break; + schedule_timeout_uninterruptible(1); + } + lock_sock(sk); switch (hci_pi(sk)->channel) { @@ -859,8 +893,6 @@ static int hci_sock_release(struct socket *sock) break; } - bt_sock_unlink(&hci_sk_list, sk); - hdev = hci_pi(sk)->hdev; if (hdev) { if (hci_pi(sk)->channel == HCI_CHANNEL_USER) { @@ -2049,6 +2081,7 @@ static int hci_sock_create(struct net *net, struct socket *sock, int protocol, sock->state = SS_UNCONNECTED; sk->sk_state = BT_OPEN; + hci_pi(sk)->event_in_progress = 0; bt_sock_link(&hci_sk_list, sk); return 0; }