Bluetooth: fix error return code in rfcomm_add_listener()
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / bluetooth / rfcomm / core.c
index b23e2713fea829157959b80802671e79934e38d4..ca957d34b0c89fa29341a179ee16e325ac226e55 100644 (file)
@@ -69,7 +69,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
                                                        u8 sec_level,
                                                        int *err);
 static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst);
-static void rfcomm_session_del(struct rfcomm_session *s);
+static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s);
 
 /* ---- RFCOMM frame parsing macros ---- */
 #define __get_dlci(b)     ((b & 0xfc) >> 2)
@@ -108,12 +108,6 @@ static void rfcomm_schedule(void)
        wake_up_process(rfcomm_thread);
 }
 
-static void rfcomm_session_put(struct rfcomm_session *s)
-{
-       if (atomic_dec_and_test(&s->refcnt))
-               rfcomm_session_del(s);
-}
-
 /* ---- RFCOMM FCS computation ---- */
 
 /* reversed, 8-bit, poly=0x07 */
@@ -249,16 +243,14 @@ static void rfcomm_session_set_timer(struct rfcomm_session *s, long timeout)
 {
        BT_DBG("session %p state %ld timeout %ld", s, s->state, timeout);
 
-       if (!mod_timer(&s->timer, jiffies + timeout))
-               rfcomm_session_hold(s);
+       mod_timer(&s->timer, jiffies + timeout);
 }
 
 static void rfcomm_session_clear_timer(struct rfcomm_session *s)
 {
        BT_DBG("session %p state %ld", s, s->state);
 
-       if (del_timer(&s->timer))
-               rfcomm_session_put(s);
+       del_timer_sync(&s->timer);
 }
 
 /* ---- RFCOMM DLCs ---- */
@@ -336,8 +328,6 @@ static void rfcomm_dlc_link(struct rfcomm_session *s, struct rfcomm_dlc *d)
 {
        BT_DBG("dlc %p session %p", d, s);
 
-       rfcomm_session_hold(s);
-
        rfcomm_session_clear_timer(s);
        rfcomm_dlc_hold(d);
        list_add(&d->list, &s->dlcs);
@@ -356,8 +346,6 @@ static void rfcomm_dlc_unlink(struct rfcomm_dlc *d)
 
        if (list_empty(&s->dlcs))
                rfcomm_session_set_timer(s, RFCOMM_IDLE_TIMEOUT);
-
-       rfcomm_session_put(s);
 }
 
 static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci)
@@ -493,12 +481,34 @@ static int __rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
 
 int rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
 {
-       int r;
+       int r = 0;
+       struct rfcomm_dlc *d_list;
+       struct rfcomm_session *s, *s_list;
+
+       BT_DBG("dlc %p state %ld dlci %d err %d", d, d->state, d->dlci, err);
 
        rfcomm_lock();
 
-       r = __rfcomm_dlc_close(d, err);
+       s = d->session;
+       if (!s)
+               goto no_session;
+
+       /* after waiting on the mutex check the session still exists
+        * then check the dlc still exists
+        */
+       list_for_each_entry(s_list, &session_list, list) {
+               if (s_list == s) {
+                       list_for_each_entry(d_list, &s->dlcs, list) {
+                               if (d_list == d) {
+                                       r = __rfcomm_dlc_close(d, err);
+                                       break;
+                               }
+                       }
+                       break;
+               }
+       }
 
+no_session:
        rfcomm_unlock();
        return r;
 }
@@ -609,7 +619,7 @@ static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state)
        return s;
 }
 
-static void rfcomm_session_del(struct rfcomm_session *s)
+static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s)
 {
        int state = s->state;
 
@@ -617,15 +627,14 @@ static void rfcomm_session_del(struct rfcomm_session *s)
 
        list_del(&s->list);
 
-       if (state == BT_CONNECTED)
-               rfcomm_send_disc(s, 0);
-
        rfcomm_session_clear_timer(s);
        sock_release(s->sock);
        kfree(s);
 
        if (state != BT_LISTEN)
                module_put(THIS_MODULE);
+
+       return NULL;
 }
 
 static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst)
@@ -644,17 +653,16 @@ static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst)
        return NULL;
 }
 
-static void rfcomm_session_close(struct rfcomm_session *s, int err)
+static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s,
+                                                  int err)
 {
        struct rfcomm_dlc *d;
        struct list_head *p, *n;
 
-       BT_DBG("session %p state %ld err %d", s, s->state, err);
-
-       rfcomm_session_hold(s);
-
        s->state = BT_CLOSED;
 
+       BT_DBG("session %p state %ld err %d", s, s->state, err);
+
        /* Close all dlcs */
        list_for_each_safe(p, n, &s->dlcs) {
                d = list_entry(p, struct rfcomm_dlc, list);
@@ -663,7 +671,7 @@ static void rfcomm_session_close(struct rfcomm_session *s, int err)
        }
 
        rfcomm_session_clear_timer(s);
-       rfcomm_session_put(s);
+       return rfcomm_session_del(s);
 }
 
 static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
@@ -715,8 +723,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
        if (*err == 0 || *err == -EINPROGRESS)
                return s;
 
-       rfcomm_session_del(s);
-       return NULL;
+       return rfcomm_session_del(s);
 
 failed:
        sock_release(sock);
@@ -1105,7 +1112,7 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr)
 }
 
 /* ---- RFCOMM frame reception ---- */
-static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
+static struct rfcomm_session *rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
 {
        BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
 
@@ -1114,7 +1121,7 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
                struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci);
                if (!d) {
                        rfcomm_send_dm(s, dlci);
-                       return 0;
+                       return s;
                }
 
                switch (d->state) {
@@ -1150,25 +1157,14 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
                        break;
 
                case BT_DISCONN:
-                       /* rfcomm_session_put is called later so don't do
-                        * anything here otherwise we will mess up the session
-                        * reference counter:
-                        *
-                        * (a) when we are the initiator dlc_unlink will drive
-                        * the reference counter to 0 (there is no initial put
-                        * after session_add)
-                        *
-                        * (b) when we are not the initiator rfcomm_rx_process
-                        * will explicitly call put to balance the initial hold
-                        * done after session add.
-                        */
+                       s = rfcomm_session_close(s, ECONNRESET);
                        break;
                }
        }
-       return 0;
+       return s;
 }
 
-static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
+static struct rfcomm_session *rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
 {
        int err = 0;
 
@@ -1192,13 +1188,13 @@ static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
                else
                        err = ECONNRESET;
 
-               s->state = BT_CLOSED;
-               rfcomm_session_close(s, err);
+               s = rfcomm_session_close(s, err);
        }
-       return 0;
+       return s;
 }
 
-static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci)
+static struct rfcomm_session *rfcomm_recv_disc(struct rfcomm_session *s,
+                                              u8 dlci)
 {
        int err = 0;
 
@@ -1227,11 +1223,9 @@ static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci)
                else
                        err = ECONNRESET;
 
-               s->state = BT_CLOSED;
-               rfcomm_session_close(s, err);
+               s = rfcomm_session_close(s, err);
        }
-
-       return 0;
+       return s;
 }
 
 void rfcomm_dlc_accept(struct rfcomm_dlc *d)
@@ -1652,11 +1646,18 @@ drop:
        return 0;
 }
 
-static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
+static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s,
+                                               struct sk_buff *skb)
 {
        struct rfcomm_hdr *hdr = (void *) skb->data;
        u8 type, dlci, fcs;
 
+       if (!s) {
+               /* no session, so free socket data */
+               kfree_skb(skb);
+               return s;
+       }
+
        dlci = __get_dlci(hdr->addr);
        type = __get_type(hdr->ctrl);
 
@@ -1667,7 +1668,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
        if (__check_fcs(skb->data, type, fcs)) {
                BT_ERR("bad checksum in packet");
                kfree_skb(skb);
-               return -EILSEQ;
+               return s;
        }
 
        if (__test_ea(hdr->len))
@@ -1683,22 +1684,23 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
 
        case RFCOMM_DISC:
                if (__test_pf(hdr->ctrl))
-                       rfcomm_recv_disc(s, dlci);
+                       s = rfcomm_recv_disc(s, dlci);
                break;
 
        case RFCOMM_UA:
                if (__test_pf(hdr->ctrl))
-                       rfcomm_recv_ua(s, dlci);
+                       s = rfcomm_recv_ua(s, dlci);
                break;
 
        case RFCOMM_DM:
-               rfcomm_recv_dm(s, dlci);
+               s = rfcomm_recv_dm(s, dlci);
                break;
 
        case RFCOMM_UIH:
-               if (dlci)
-                       return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb);
-
+               if (dlci) {
+                       rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb);
+                       return s;
+               }
                rfcomm_recv_mcc(s, skb);
                break;
 
@@ -1707,7 +1709,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
                break;
        }
        kfree_skb(skb);
-       return 0;
+       return s;
 }
 
 /* ---- Connection and data processing ---- */
@@ -1844,7 +1846,7 @@ static void rfcomm_process_dlcs(struct rfcomm_session *s)
        }
 }
 
-static void rfcomm_process_rx(struct rfcomm_session *s)
+static struct rfcomm_session *rfcomm_process_rx(struct rfcomm_session *s)
 {
        struct socket *sock = s->sock;
        struct sock *sk = sock->sk;
@@ -1856,17 +1858,15 @@ static void rfcomm_process_rx(struct rfcomm_session *s)
        while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
                skb_orphan(skb);
                if (!skb_linearize(skb))
-                       rfcomm_recv_frame(s, skb);
+                       s = rfcomm_recv_frame(s, skb);
                else
                        kfree_skb(skb);
        }
 
-       if (sk->sk_state == BT_CLOSED) {
-               if (!s->initiator)
-                       rfcomm_session_put(s);
+       if (s && (sk->sk_state == BT_CLOSED))
+               s = rfcomm_session_close(s, sk->sk_err);
 
-               rfcomm_session_close(s, sk->sk_err);
-       }
+       return s;
 }
 
 static void rfcomm_accept_connection(struct rfcomm_session *s)
@@ -1891,8 +1891,6 @@ static void rfcomm_accept_connection(struct rfcomm_session *s)
 
        s = rfcomm_session_add(nsock, BT_OPEN);
        if (s) {
-               rfcomm_session_hold(s);
-
                /* We should adjust MTU on incoming sessions.
                 * L2CAP MTU minus UIH header and FCS. */
                s->mtu = min(l2cap_pi(nsock->sk)->chan->omtu,
@@ -1903,7 +1901,7 @@ static void rfcomm_accept_connection(struct rfcomm_session *s)
                sock_release(nsock);
 }
 
-static void rfcomm_check_connection(struct rfcomm_session *s)
+static struct rfcomm_session *rfcomm_check_connection(struct rfcomm_session *s)
 {
        struct sock *sk = s->sock->sk;
 
@@ -1921,10 +1919,10 @@ static void rfcomm_check_connection(struct rfcomm_session *s)
                break;
 
        case BT_CLOSED:
-               s->state = BT_CLOSED;
-               rfcomm_session_close(s, sk->sk_err);
+               s = rfcomm_session_close(s, sk->sk_err);
                break;
        }
+       return s;
 }
 
 static void rfcomm_process_sessions(void)
@@ -1940,7 +1938,6 @@ static void rfcomm_process_sessions(void)
                if (test_and_clear_bit(RFCOMM_TIMED_OUT, &s->flags)) {
                        s->state = BT_DISCONN;
                        rfcomm_send_disc(s, 0);
-                       rfcomm_session_put(s);
                        continue;
                }
 
@@ -1949,21 +1946,18 @@ static void rfcomm_process_sessions(void)
                        continue;
                }
 
-               rfcomm_session_hold(s);
-
                switch (s->state) {
                case BT_BOUND:
-                       rfcomm_check_connection(s);
+                       s = rfcomm_check_connection(s);
                        break;
 
                default:
-                       rfcomm_process_rx(s);
+                       s = rfcomm_process_rx(s);
                        break;
                }
 
-               rfcomm_process_dlcs(s);
-
-               rfcomm_session_put(s);
+               if (s)
+                       rfcomm_process_dlcs(s);
        }
 
        rfcomm_unlock();
@@ -2010,10 +2004,11 @@ static int rfcomm_add_listener(bdaddr_t *ba)
 
        /* Add listening session */
        s = rfcomm_session_add(sock, BT_LISTEN);
-       if (!s)
+       if (!s) {
+               err = -ENOMEM;
                goto failed;
+       }
 
-       rfcomm_session_hold(s);
        return 0;
 failed:
        sock_release(sock);
@@ -2071,8 +2066,6 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
        if (!s)
                return;
 
-       rfcomm_session_hold(s);
-
        list_for_each_safe(p, n, &s->dlcs) {
                d = list_entry(p, struct rfcomm_dlc, list);
 
@@ -2104,8 +2097,6 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
                        set_bit(RFCOMM_AUTH_REJECT, &d->flags);
        }
 
-       rfcomm_session_put(s);
-
        rfcomm_schedule();
 }