Bluetooth: Add LE flow control discipline
authorJohan Hedberg <johan.hedberg@intel.com>
Thu, 5 Dec 2013 07:45:01 +0000 (09:45 +0200)
committerMarcel Holtmann <marcel@holtmann.org>
Thu, 5 Dec 2013 15:05:34 +0000 (07:05 -0800)
This patch adds the necessary discipline for reacting to LE L2CAP
Credits packets, sending those packets, and modifying the known credits
accordingly.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
net/bluetooth/l2cap_core.c

index 4e0e2be..8a0d235 100644 (file)
@@ -2542,8 +2542,12 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
        }
 
        switch (chan->mode) {
-       case L2CAP_MODE_BASIC:
        case L2CAP_MODE_LE_FLOWCTL:
+               if (!chan->tx_credits)
+                       return -EAGAIN;
+
+               /* fall through */
+       case L2CAP_MODE_BASIC:
                /* Check outgoing MTU */
                if (len > chan->omtu)
                        return -EMSGSIZE;
@@ -5551,6 +5555,42 @@ response:
        return 0;
 }
 
+static inline int l2cap_le_credits(struct l2cap_conn *conn,
+                                  struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+                                  u8 *data)
+{
+       struct l2cap_le_credits *pkt;
+       struct l2cap_chan *chan;
+       u16 cid, credits;
+
+       if (cmd_len != sizeof(*pkt))
+               return -EPROTO;
+
+       pkt = (struct l2cap_le_credits *) data;
+       cid     = __le16_to_cpu(pkt->cid);
+       credits = __le16_to_cpu(pkt->credits);
+
+       BT_DBG("cid 0x%4.4x credits 0x%4.4x", cid, credits);
+
+       chan = l2cap_get_chan_by_dcid(conn, cid);
+       if (!chan)
+               return -EBADSLT;
+
+       chan->tx_credits += credits;
+
+       while (chan->tx_credits && !skb_queue_empty(&chan->tx_q)) {
+               l2cap_do_send(chan, skb_dequeue(&chan->tx_q));
+               chan->tx_credits--;
+       }
+
+       if (chan->tx_credits)
+               chan->ops->resume(chan);
+
+       l2cap_chan_unlock(chan);
+
+       return 0;
+}
+
 static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
                                   struct l2cap_cmd_hdr *cmd, u16 cmd_len,
                                   u8 *data)
@@ -5576,6 +5616,10 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
                err = l2cap_le_connect_req(conn, cmd, cmd_len, data);
                break;
 
+       case L2CAP_LE_CREDITS:
+               err = l2cap_le_credits(conn, cmd, cmd_len, data);
+               break;
+
        case L2CAP_DISCONN_REQ:
                err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
                break;
@@ -6636,6 +6680,22 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
        l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
 }
 
+static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
+{
+       if (!chan->rx_credits)
+               return -ENOBUFS;
+
+       if (chan->imtu < skb->len)
+               return -ENOBUFS;
+
+       chan->rx_credits--;
+       BT_DBG("rx_credits %u -> %u", chan->rx_credits + 1, chan->rx_credits);
+
+       l2cap_chan_le_send_credits(chan);
+
+       return chan->ops->recv(chan, skb);
+}
+
 static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
                               struct sk_buff *skb)
 {
@@ -6666,6 +6726,11 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
 
        switch (chan->mode) {
        case L2CAP_MODE_LE_FLOWCTL:
+               if (l2cap_le_data_rcv(chan, skb) < 0)
+                       goto drop;
+
+               goto done;
+
        case L2CAP_MODE_BASIC:
                /* If socket recv buffers overflows we drop data here
                 * which is *bad* because L2CAP has to be reliable.