diff options
author | Johan Hedberg <johan.hedberg@intel.com> | 2013-05-31 10:54:51 -0400 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2013-12-05 10:05:35 -0500 |
commit | 177f8f2b1259a1292a09a1b7563ebb90675f88ff (patch) | |
tree | 652b44c896a77522d8357b72c629221f2384d3ce | |
parent | 837776f7904024df451422f32b09c67e88ae2aa2 (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.c | 127 |
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 | ||
1198 | static 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 | |||
1197 | static void l2cap_chan_ready(struct l2cap_chan *chan) | 1207 | static 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 | ||
2537 | static 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 | |||
2580 | static 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 | |||
2524 | int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, | 2620 | int 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) |