aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2013-05-31 10:54:51 -0400
committerMarcel Holtmann <marcel@holtmann.org>2013-12-05 10:05:35 -0500
commit177f8f2b1259a1292a09a1b7563ebb90675f88ff (patch)
tree652b44c896a77522d8357b72c629221f2384d3ce
parent837776f7904024df451422f32b09c67e88ae2aa2 (diff)
Bluetooth: Add LE L2CAP segmentation support for outgoing data
This patch adds segmentation support for outgoing data packets. Packets are segmented based on the MTU and MPS values. The l2cap_chan struct already contains many helpful variables from BR/EDR Enhanced L2CAP which can be used for this. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
-rw-r--r--net/bluetooth/l2cap_core.c127
1 files changed, 126 insertions, 1 deletions
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index aaa98a2318ca..bdc1c40ba1b9 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -607,6 +607,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
607 break; 607 break;
608 608
609 case L2CAP_MODE_LE_FLOWCTL: 609 case L2CAP_MODE_LE_FLOWCTL:
610 skb_queue_purge(&chan->tx_q);
610 break; 611 break;
611 612
612 case L2CAP_MODE_ERTM: 613 case L2CAP_MODE_ERTM:
@@ -1194,12 +1195,24 @@ static void l2cap_move_done(struct l2cap_chan *chan)
1194 } 1195 }
1195} 1196}
1196 1197
1198static void l2cap_le_flowctl_start(struct l2cap_chan *chan)
1199{
1200 chan->sdu = NULL;
1201 chan->sdu_last_frag = NULL;
1202 chan->sdu_len = 0;
1203
1204 skb_queue_head_init(&chan->tx_q);
1205}
1206
1197static void l2cap_chan_ready(struct l2cap_chan *chan) 1207static void l2cap_chan_ready(struct l2cap_chan *chan)
1198{ 1208{
1199 /* This clears all conf flags, including CONF_NOT_COMPLETE */ 1209 /* This clears all conf flags, including CONF_NOT_COMPLETE */
1200 chan->conf_state = 0; 1210 chan->conf_state = 0;
1201 __clear_chan_timer(chan); 1211 __clear_chan_timer(chan);
1202 1212
1213 if (chan->mode == L2CAP_MODE_LE_FLOWCTL)
1214 l2cap_le_flowctl_start(chan);
1215
1203 chan->state = BT_CONNECTED; 1216 chan->state = BT_CONNECTED;
1204 1217
1205 chan->ops->ready(chan); 1218 chan->ops->ready(chan);
@@ -2521,6 +2534,89 @@ static int l2cap_segment_sdu(struct l2cap_chan *chan,
2521 return 0; 2534 return 0;
2522} 2535}
2523 2536
2537static struct sk_buff *l2cap_create_le_flowctl_pdu(struct l2cap_chan *chan,
2538 struct msghdr *msg,
2539 size_t len, u16 sdulen)
2540{
2541 struct l2cap_conn *conn = chan->conn;
2542 struct sk_buff *skb;
2543 int err, count, hlen;
2544 struct l2cap_hdr *lh;
2545
2546 BT_DBG("chan %p len %zu", chan, len);
2547
2548 if (!conn)
2549 return ERR_PTR(-ENOTCONN);
2550
2551 hlen = L2CAP_HDR_SIZE;
2552
2553 if (sdulen)
2554 hlen += L2CAP_SDULEN_SIZE;
2555
2556 count = min_t(unsigned int, (conn->mtu - hlen), len);
2557
2558 skb = chan->ops->alloc_skb(chan, count + hlen,
2559 msg->msg_flags & MSG_DONTWAIT);
2560 if (IS_ERR(skb))
2561 return skb;
2562
2563 /* Create L2CAP header */
2564 lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
2565 lh->cid = cpu_to_le16(chan->dcid);
2566 lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
2567
2568 if (sdulen)
2569 put_unaligned_le16(sdulen, skb_put(skb, L2CAP_SDULEN_SIZE));
2570
2571 err = l2cap_skbuff_fromiovec(chan, msg, len, count, skb);
2572 if (unlikely(err < 0)) {
2573 kfree_skb(skb);
2574 return ERR_PTR(err);
2575 }
2576
2577 return skb;
2578}
2579
2580static int l2cap_segment_le_sdu(struct l2cap_chan *chan,
2581 struct sk_buff_head *seg_queue,
2582 struct msghdr *msg, size_t len)
2583{
2584 struct sk_buff *skb;
2585 size_t pdu_len;
2586 u16 sdu_len;
2587
2588 BT_DBG("chan %p, msg %p, len %zu", chan, msg, len);
2589
2590 pdu_len = chan->conn->mtu - L2CAP_HDR_SIZE;
2591
2592 pdu_len = min_t(size_t, pdu_len, chan->remote_mps);
2593
2594 sdu_len = len;
2595 pdu_len -= L2CAP_SDULEN_SIZE;
2596
2597 while (len > 0) {
2598 if (len <= pdu_len)
2599 pdu_len = len;
2600
2601 skb = l2cap_create_le_flowctl_pdu(chan, msg, pdu_len, sdu_len);
2602 if (IS_ERR(skb)) {
2603 __skb_queue_purge(seg_queue);
2604 return PTR_ERR(skb);
2605 }
2606
2607 __skb_queue_tail(seg_queue, skb);
2608
2609 len -= pdu_len;
2610
2611 if (sdu_len) {
2612 sdu_len = 0;
2613 pdu_len += L2CAP_SDULEN_SIZE;
2614 }
2615 }
2616
2617 return 0;
2618}
2619
2524int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, 2620int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
2525 u32 priority) 2621 u32 priority)
2526{ 2622{
@@ -2543,10 +2639,39 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
2543 2639
2544 switch (chan->mode) { 2640 switch (chan->mode) {
2545 case L2CAP_MODE_LE_FLOWCTL: 2641 case L2CAP_MODE_LE_FLOWCTL:
2642 /* Check outgoing MTU */
2643 if (len > chan->omtu)
2644 return -EMSGSIZE;
2645
2546 if (!chan->tx_credits) 2646 if (!chan->tx_credits)
2547 return -EAGAIN; 2647 return -EAGAIN;
2548 2648
2549 /* fall through */ 2649 __skb_queue_head_init(&seg_queue);
2650
2651 err = l2cap_segment_le_sdu(chan, &seg_queue, msg, len);
2652
2653 if (chan->state != BT_CONNECTED) {
2654 __skb_queue_purge(&seg_queue);
2655 err = -ENOTCONN;
2656 }
2657
2658 if (err)
2659 return err;
2660
2661 skb_queue_splice_tail_init(&seg_queue, &chan->tx_q);
2662
2663 while (chan->tx_credits && !skb_queue_empty(&chan->tx_q)) {
2664 l2cap_do_send(chan, skb_dequeue(&chan->tx_q));
2665 chan->tx_credits--;
2666 }
2667
2668 if (!chan->tx_credits)
2669 chan->ops->suspend(chan);
2670
2671 err = len;
2672
2673 break;
2674
2550 case L2CAP_MODE_BASIC: 2675 case L2CAP_MODE_BASIC:
2551 /* Check outgoing MTU */ 2676 /* Check outgoing MTU */
2552 if (len > chan->omtu) 2677 if (len > chan->omtu)