aboutsummaryrefslogtreecommitdiffstats
path: root/net/bluetooth
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2013-06-01 03:14:57 -0400
committerMarcel Holtmann <marcel@holtmann.org>2013-12-05 10:05:35 -0500
commitaac23bf636593cc2d67144aed373a46a1a5f76b1 (patch)
tree2aa549d8f8cf27b9ab9f693b6d4a6f65c54538fe /net/bluetooth
parent177f8f2b1259a1292a09a1b7563ebb90675f88ff (diff)
Bluetooth: Implement LE L2CAP reassembly
When receiving fragments over an LE Connection oriented Channel they need to be collected up and eventually merged into a single SDU. This patch adds the necessary code for collecting up the fragment skbs to the channel context and passing them to the recv() callback when the entire SDU has been received. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Diffstat (limited to 'net/bluetooth')
-rw-r--r--net/bluetooth/l2cap_core.c79
1 files changed, 76 insertions, 3 deletions
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index bdc1c40ba1b9..1c94e51a28fe 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -6818,18 +6818,91 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
6818 6818
6819static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) 6819static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
6820{ 6820{
6821 if (!chan->rx_credits) 6821 int err;
6822
6823 if (!chan->rx_credits) {
6824 BT_ERR("No credits to receive LE L2CAP data");
6822 return -ENOBUFS; 6825 return -ENOBUFS;
6826 }
6823 6827
6824 if (chan->imtu < skb->len) 6828 if (chan->imtu < skb->len) {
6829 BT_ERR("Too big LE L2CAP PDU");
6825 return -ENOBUFS; 6830 return -ENOBUFS;
6831 }
6826 6832
6827 chan->rx_credits--; 6833 chan->rx_credits--;
6828 BT_DBG("rx_credits %u -> %u", chan->rx_credits + 1, chan->rx_credits); 6834 BT_DBG("rx_credits %u -> %u", chan->rx_credits + 1, chan->rx_credits);
6829 6835
6830 l2cap_chan_le_send_credits(chan); 6836 l2cap_chan_le_send_credits(chan);
6831 6837
6832 return chan->ops->recv(chan, skb); 6838 err = 0;
6839
6840 if (!chan->sdu) {
6841 u16 sdu_len;
6842
6843 sdu_len = get_unaligned_le16(skb->data);
6844 skb_pull(skb, L2CAP_SDULEN_SIZE);
6845
6846 BT_DBG("Start of new SDU. sdu_len %u skb->len %u imtu %u",
6847 sdu_len, skb->len, chan->imtu);
6848
6849 if (sdu_len > chan->imtu) {
6850 BT_ERR("Too big LE L2CAP SDU length received");
6851 err = -EMSGSIZE;
6852 goto failed;
6853 }
6854
6855 if (skb->len > sdu_len) {
6856 BT_ERR("Too much LE L2CAP data received");
6857 err = -EINVAL;
6858 goto failed;
6859 }
6860
6861 if (skb->len == sdu_len)
6862 return chan->ops->recv(chan, skb);
6863
6864 chan->sdu = skb;
6865 chan->sdu_len = sdu_len;
6866 chan->sdu_last_frag = skb;
6867
6868 return 0;
6869 }
6870
6871 BT_DBG("SDU fragment. chan->sdu->len %u skb->len %u chan->sdu_len %u",
6872 chan->sdu->len, skb->len, chan->sdu_len);
6873
6874 if (chan->sdu->len + skb->len > chan->sdu_len) {
6875 BT_ERR("Too much LE L2CAP data received");
6876 err = -EINVAL;
6877 goto failed;
6878 }
6879
6880 append_skb_frag(chan->sdu, skb, &chan->sdu_last_frag);
6881 skb = NULL;
6882
6883 if (chan->sdu->len == chan->sdu_len) {
6884 err = chan->ops->recv(chan, chan->sdu);
6885 if (!err) {
6886 chan->sdu = NULL;
6887 chan->sdu_last_frag = NULL;
6888 chan->sdu_len = 0;
6889 }
6890 }
6891
6892failed:
6893 if (err) {
6894 kfree_skb(skb);
6895 kfree_skb(chan->sdu);
6896 chan->sdu = NULL;
6897 chan->sdu_last_frag = NULL;
6898 chan->sdu_len = 0;
6899 }
6900
6901 /* We can't return an error here since we took care of the skb
6902 * freeing internally. An error return would cause the caller to
6903 * do a double-free of the skb.
6904 */
6905 return 0;
6833} 6906}
6834 6907
6835static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, 6908static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,