]> Pileus Git - ~andy/linux/blobdiff - net/bluetooth/l2cap_core.c
Bluetooth: improve readability of l2cap_seq_list code
[~andy/linux] / net / bluetooth / l2cap_core.c
index 1192c943bf8e88c6c9ef15691564a8396f0a4de7..1e12d6d58e84cbb388d4c5a844c94b970055f7d6 100644 (file)
@@ -4,6 +4,7 @@
    Copyright (C) 2009-2010 Gustavo F. Padovan <gustavo@padovan.org>
    Copyright (C) 2010 Google Inc.
    Copyright (C) 2011 ProFUSION Embedded Systems
+   Copyright (c) 2012 Code Aurora Forum.  All rights reserved.
 
    Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
 
@@ -309,14 +310,16 @@ static inline u16 l2cap_seq_list_pop(struct l2cap_seq_list *seq_list)
 
 static void l2cap_seq_list_clear(struct l2cap_seq_list *seq_list)
 {
-       if (seq_list->head != L2CAP_SEQ_LIST_CLEAR) {
-               u16 i;
-               for (i = 0; i <= seq_list->mask; i++)
-                       seq_list->list[i] = L2CAP_SEQ_LIST_CLEAR;
+       u16 i;
 
-               seq_list->head = L2CAP_SEQ_LIST_CLEAR;
-               seq_list->tail = L2CAP_SEQ_LIST_CLEAR;
-       }
+       if (seq_list->head == L2CAP_SEQ_LIST_CLEAR)
+               return;
+
+       for (i = 0; i <= seq_list->mask; i++)
+               seq_list->list[i] = L2CAP_SEQ_LIST_CLEAR;
+
+       seq_list->head = L2CAP_SEQ_LIST_CLEAR;
+       seq_list->tail = L2CAP_SEQ_LIST_CLEAR;
 }
 
 static void l2cap_seq_list_append(struct l2cap_seq_list *seq_list, u16 seq)
@@ -325,15 +328,16 @@ static void l2cap_seq_list_append(struct l2cap_seq_list *seq_list, u16 seq)
 
        /* All appends happen in constant time */
 
-       if (seq_list->list[seq & mask] == L2CAP_SEQ_LIST_CLEAR) {
-               if (seq_list->tail == L2CAP_SEQ_LIST_CLEAR)
-                       seq_list->head = seq;
-               else
-                       seq_list->list[seq_list->tail & mask] = seq;
+       if (seq_list->list[seq & mask] != L2CAP_SEQ_LIST_CLEAR)
+               return;
 
-               seq_list->tail = seq;
-               seq_list->list[seq & mask] = L2CAP_SEQ_LIST_TAIL;
-       }
+       if (seq_list->tail == L2CAP_SEQ_LIST_CLEAR)
+               seq_list->head = seq;
+       else
+               seq_list->list[seq_list->tail & mask] = seq;
+
+       seq_list->tail = seq;
+       seq_list->list[seq & mask] = L2CAP_SEQ_LIST_TAIL;
 }
 
 static void l2cap_chan_timeout(struct work_struct *work)
@@ -1256,6 +1260,7 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
 
        /* Kill channels */
        list_for_each_entry_safe(chan, l, &conn->chan_l, list) {
+               l2cap_chan_hold(chan);
                l2cap_chan_lock(chan);
 
                l2cap_chan_del(chan, err);
@@ -1263,6 +1268,7 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
                l2cap_chan_unlock(chan);
 
                chan->ops->close(chan->data);
+               l2cap_chan_put(chan);
        }
 
        mutex_unlock(&conn->chan_lock);
@@ -1631,6 +1637,7 @@ static void l2cap_streaming_send(struct l2cap_chan *chan)
        while ((skb = skb_dequeue(&chan->tx_q))) {
                control = __get_control(chan, skb->data + L2CAP_HDR_SIZE);
                control |= __set_txseq(chan, chan->next_tx_seq);
+               control |= __set_ctrl_sar(chan, bt_cb(skb)->control.sar);
                __put_control(chan, control, skb->data + L2CAP_HDR_SIZE);
 
                if (chan->fcs == L2CAP_FCS_CRC16) {
@@ -1703,6 +1710,9 @@ static int l2cap_ertm_send(struct l2cap_chan *chan)
        if (chan->state != BT_CONNECTED)
                return -ENOTCONN;
 
+       if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state))
+               return 0;
+
        while ((skb = chan->tx_send_head) && (!l2cap_tx_window_full(chan))) {
 
                if (bt_cb(skb)->control.retries == chan->remote_max_tx &&
@@ -1723,6 +1733,7 @@ static int l2cap_ertm_send(struct l2cap_chan *chan)
 
                control |= __set_reqseq(chan, chan->buffer_seq);
                control |= __set_txseq(chan, chan->next_tx_seq);
+               control |= __set_ctrl_sar(chan, bt_cb(skb)->control.sar);
 
                __put_control(chan, control, tx_skb->data + L2CAP_HDR_SIZE);
 
@@ -1828,13 +1839,17 @@ static inline int l2cap_skbuff_fromiovec(struct l2cap_chan *chan,
        /* Continuation fragments (no L2CAP header) */
        frag = &skb_shinfo(skb)->frag_list;
        while (len) {
+               struct sk_buff *tmp;
+
                count = min_t(unsigned int, conn->mtu, len);
 
-               *frag = chan->ops->alloc_skb(chan, count,
-                                            msg->msg_flags & MSG_DONTWAIT);
+               tmp = chan->ops->alloc_skb(chan, count,
+                                          msg->msg_flags & MSG_DONTWAIT);
+               if (IS_ERR(tmp))
+                       return PTR_ERR(tmp);
+
+               *frag = tmp;
 
-               if (IS_ERR(*frag))
-                       return PTR_ERR(*frag);
                if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count))
                        return -EFAULT;
 
@@ -1843,6 +1858,9 @@ static inline int l2cap_skbuff_fromiovec(struct l2cap_chan *chan,
                sent += count;
                len  -= count;
 
+               skb->len += (*frag)->len;
+               skb->data_len += (*frag)->len;
+
                frag = &(*frag)->next;
        }
 
@@ -1872,8 +1890,8 @@ static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan,
        /* Create L2CAP header */
        lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
        lh->cid = cpu_to_le16(chan->dcid);
-       lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
-       put_unaligned(chan->psm, skb_put(skb, 2));
+       lh->len = cpu_to_le16(len + L2CAP_PSMLEN_SIZE);
+       put_unaligned(chan->psm, skb_put(skb, L2CAP_PSMLEN_SIZE));
 
        err = l2cap_skbuff_fromiovec(chan, msg, len, count, skb);
        if (unlikely(err < 0)) {
@@ -1889,14 +1907,14 @@ static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan,
 {
        struct l2cap_conn *conn = chan->conn;
        struct sk_buff *skb;
-       int err, count, hlen = L2CAP_HDR_SIZE;
+       int err, count;
        struct l2cap_hdr *lh;
 
        BT_DBG("chan %p len %d", chan, (int)len);
 
-       count = min_t(unsigned int, (conn->mtu - hlen), len);
+       count = min_t(unsigned int, (conn->mtu - L2CAP_HDR_SIZE), len);
 
-       skb = chan->ops->alloc_skb(chan, count + hlen,
+       skb = chan->ops->alloc_skb(chan, count + L2CAP_HDR_SIZE,
                                   msg->msg_flags & MSG_DONTWAIT);
        if (IS_ERR(skb))
                return skb;
@@ -1906,7 +1924,7 @@ static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan,
        /* Create L2CAP header */
        lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
        lh->cid = cpu_to_le16(chan->dcid);
-       lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
+       lh->len = cpu_to_le16(len);
 
        err = l2cap_skbuff_fromiovec(chan, msg, len, count, skb);
        if (unlikely(err < 0)) {
@@ -1918,7 +1936,7 @@ static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan,
 
 static struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan,
                                                struct msghdr *msg, size_t len,
-                                               u32 control, u16 sdulen)
+                                               u16 sdulen)
 {
        struct l2cap_conn *conn = chan->conn;
        struct sk_buff *skb;
@@ -1953,7 +1971,7 @@ static struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan,
        lh->cid = cpu_to_le16(chan->dcid);
        lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
 
-       __put_control(chan, control, skb_put(skb, __ctrl_size(chan)));
+       __put_control(chan, 0, skb_put(skb, __ctrl_size(chan)));
 
        if (sdulen)
                put_unaligned_le16(sdulen, skb_put(skb, L2CAP_SDULEN_SIZE));
@@ -1971,57 +1989,78 @@ static struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan,
        return skb;
 }
 
-static int l2cap_sar_segment_sdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
+static int l2cap_segment_sdu(struct l2cap_chan *chan,
+                            struct sk_buff_head *seg_queue,
+                            struct msghdr *msg, size_t len)
 {
        struct sk_buff *skb;
-       struct sk_buff_head sar_queue;
-       u32 control;
-       size_t size = 0;
+       u16 sdu_len;
+       size_t pdu_len;
+       int err = 0;
+       u8 sar;
 
-       skb_queue_head_init(&sar_queue);
-       control = __set_ctrl_sar(chan, L2CAP_SAR_START);
-       skb = l2cap_create_iframe_pdu(chan, msg, chan->remote_mps, control, len);
-       if (IS_ERR(skb))
-               return PTR_ERR(skb);
+       BT_DBG("chan %p, msg %p, len %d", chan, msg, (int)len);
+
+       /* It is critical that ERTM PDUs fit in a single HCI fragment,
+        * so fragmented skbs are not used.  The HCI layer's handling
+        * of fragmented skbs is not compatible with ERTM's queueing.
+        */
 
-       __skb_queue_tail(&sar_queue, skb);
-       len -= chan->remote_mps;
-       size += chan->remote_mps;
+       /* PDU size is derived from the HCI MTU */
+       pdu_len = chan->conn->mtu;
 
-       while (len > 0) {
-               size_t buflen;
+       pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD);
 
-               if (len > chan->remote_mps) {
-                       control = __set_ctrl_sar(chan, L2CAP_SAR_CONTINUE);
-                       buflen = chan->remote_mps;
-               } else {
-                       control = __set_ctrl_sar(chan, L2CAP_SAR_END);
-                       buflen = len;
-               }
+       /* Adjust for largest possible L2CAP overhead. */
+       pdu_len -= L2CAP_EXT_HDR_SIZE + L2CAP_FCS_SIZE;
+
+       /* Remote device may have requested smaller PDUs */
+       pdu_len = min_t(size_t, pdu_len, chan->remote_mps);
+
+       if (len <= pdu_len) {
+               sar = L2CAP_SAR_UNSEGMENTED;
+               sdu_len = 0;
+               pdu_len = len;
+       } else {
+               sar = L2CAP_SAR_START;
+               sdu_len = len;
+               pdu_len -= L2CAP_SDULEN_SIZE;
+       }
+
+       while (len > 0) {
+               skb = l2cap_create_iframe_pdu(chan, msg, pdu_len, sdu_len);
 
-               skb = l2cap_create_iframe_pdu(chan, msg, buflen, control, 0);
                if (IS_ERR(skb)) {
-                       skb_queue_purge(&sar_queue);
+                       __skb_queue_purge(seg_queue);
                        return PTR_ERR(skb);
                }
 
-               __skb_queue_tail(&sar_queue, skb);
-               len -= buflen;
-               size += buflen;
+               bt_cb(skb)->control.sar = sar;
+               __skb_queue_tail(seg_queue, skb);
+
+               len -= pdu_len;
+               if (sdu_len) {
+                       sdu_len = 0;
+                       pdu_len += L2CAP_SDULEN_SIZE;
+               }
+
+               if (len <= pdu_len) {
+                       sar = L2CAP_SAR_END;
+                       pdu_len = len;
+               } else {
+                       sar = L2CAP_SAR_CONTINUE;
+               }
        }
-       skb_queue_splice_tail(&sar_queue, &chan->tx_q);
-       if (chan->tx_send_head == NULL)
-               chan->tx_send_head = sar_queue.next;
 
-       return size;
+       return err;
 }
 
 int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
                                                                u32 priority)
 {
        struct sk_buff *skb;
-       u32 control;
        int err;
+       struct sk_buff_head seg_queue;
 
        /* Connectionless channel */
        if (chan->chan_type == L2CAP_CHAN_CONN_LESS) {
@@ -2050,42 +2089,47 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
 
        case L2CAP_MODE_ERTM:
        case L2CAP_MODE_STREAMING:
-               /* Entire SDU fits into one PDU */
-               if (len <= chan->remote_mps) {
-                       control = __set_ctrl_sar(chan, L2CAP_SAR_UNSEGMENTED);
-                       skb = l2cap_create_iframe_pdu(chan, msg, len, control,
-                                                                       0);
-                       if (IS_ERR(skb))
-                               return PTR_ERR(skb);
+               /* Check outgoing MTU */
+               if (len > chan->omtu) {
+                       err = -EMSGSIZE;
+                       break;
+               }
 
-                       __skb_queue_tail(&chan->tx_q, skb);
+               __skb_queue_head_init(&seg_queue);
 
-                       if (chan->tx_send_head == NULL)
-                               chan->tx_send_head = skb;
+               /* Do segmentation before calling in to the state machine,
+                * since it's possible to block while waiting for memory
+                * allocation.
+                */
+               err = l2cap_segment_sdu(chan, &seg_queue, msg, len);
 
-               } else {
-                       /* Segment SDU into multiples PDUs */
-                       err = l2cap_sar_segment_sdu(chan, msg, len);
-                       if (err < 0)
-                               return err;
+               /* The channel could have been closed while segmenting,
+                * check that it is still connected.
+                */
+               if (chan->state != BT_CONNECTED) {
+                       __skb_queue_purge(&seg_queue);
+                       err = -ENOTCONN;
                }
 
-               if (chan->mode == L2CAP_MODE_STREAMING) {
-                       l2cap_streaming_send(chan);
-                       err = len;
+               if (err)
                        break;
-               }
 
-               if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) &&
-                               test_bit(CONN_WAIT_F, &chan->conn_state)) {
-                       err = len;
-                       break;
-               }
+               if (chan->mode == L2CAP_MODE_ERTM && chan->tx_send_head == NULL)
+                       chan->tx_send_head = seg_queue.next;
+               skb_queue_splice_tail_init(&seg_queue, &chan->tx_q);
+
+               if (chan->mode == L2CAP_MODE_ERTM)
+                       err = l2cap_ertm_send(chan);
+               else
+                       l2cap_streaming_send(chan);
 
-               err = l2cap_ertm_send(chan);
                if (err >= 0)
                        err = len;
 
+               /* If the skbs were not queued for sending, they'll still be in
+                * seg_queue and need to be purged.
+                */
+               __skb_queue_purge(&seg_queue);
                break;
 
        default:
@@ -2315,6 +2359,8 @@ static inline int l2cap_ertm_init(struct l2cap_chan *chan)
        chan->sdu_last_frag = NULL;
        chan->sdu_len = 0;
 
+       skb_queue_head_init(&chan->tx_q);
+
        if (chan->mode != L2CAP_MODE_ERTM)
                return 0;
 
@@ -2326,7 +2372,6 @@ static inline int l2cap_ertm_init(struct l2cap_chan *chan)
        INIT_DELAYED_WORK(&chan->ack_timer, l2cap_ack_timeout);
 
        skb_queue_head_init(&chan->srej_q);
-       skb_queue_head_init(&chan->tx_q);
 
        INIT_LIST_HEAD(&chan->srej_l);
        err = l2cap_seq_list_init(&chan->srej_list, chan->tx_win);
@@ -3375,11 +3420,13 @@ static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd
        sk->sk_shutdown = SHUTDOWN_MASK;
        release_sock(sk);
 
+       l2cap_chan_hold(chan);
        l2cap_chan_del(chan, ECONNRESET);
 
        l2cap_chan_unlock(chan);
 
        chan->ops->close(chan->data);
+       l2cap_chan_put(chan);
 
        mutex_unlock(&conn->chan_lock);
 
@@ -3407,11 +3454,13 @@ static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd
 
        l2cap_chan_lock(chan);
 
+       l2cap_chan_hold(chan);
        l2cap_chan_del(chan, 0);
 
        l2cap_chan_unlock(chan);
 
        chan->ops->close(chan->data);
+       l2cap_chan_put(chan);
 
        mutex_unlock(&conn->chan_lock);
 
@@ -4877,6 +4926,11 @@ int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 
                if (!status && (chan->state == BT_CONNECTED ||
                                                chan->state == BT_CONFIG)) {
+                       struct sock *sk = chan->sk;
+
+                       bt_sk(sk)->suspended = false;
+                       sk->sk_state_change(sk);
+
                        l2cap_check_encryption(chan, encrypt);
                        l2cap_chan_unlock(chan);
                        continue;