Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 306926
b: refs/heads/master
c: 94122bb
h: refs/heads/master
v: v3
  • Loading branch information
Mat Martineau authored and Gustavo Padovan committed May 9, 2012
1 parent c3a7b23 commit f0fef0a
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 59 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: daf6a78c161fccd058ca2f1b21e757ebaa2e9909
refs/heads/master: 94122bbe9c8c4ad7ba9f02f9a30bfc95672c404e
1 change: 1 addition & 0 deletions trunk/include/net/bluetooth/l2cap.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#define L2CAP_DEFAULT_MAX_SDU_SIZE 0xFFFF
#define L2CAP_DEFAULT_SDU_ITIME 0xFFFFFFFF
#define L2CAP_DEFAULT_ACC_LAT 0xFFFFFFFF
#define L2CAP_BREDR_MAX_PAYLOAD 1019 /* 3-DH5 packet */

#define L2CAP_DISC_TIMEOUT msecs_to_jiffies(100)
#define L2CAP_DISC_REJ_TIMEOUT msecs_to_jiffies(5000)
Expand Down
144 changes: 86 additions & 58 deletions trunk/net/bluetooth/l2cap_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,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) {
Expand Down Expand Up @@ -1706,6 +1707,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 &&
Expand All @@ -1726,6 +1730,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);

Expand Down Expand Up @@ -1921,7 +1926,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;
Expand Down Expand Up @@ -1956,7 +1961,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));
Expand All @@ -1974,57 +1979,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);

__skb_queue_tail(&sar_queue, skb);
len -= chan->remote_mps;
size += chan->remote_mps;
/* 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.
*/

while (len > 0) {
size_t buflen;
/* PDU size is derived from the HCI MTU */
pdu_len = chan->conn->mtu;

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;
}
pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD);

/* 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) {
Expand Down Expand Up @@ -2053,42 +2079,44 @@ 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;
}
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:
Expand Down

0 comments on commit f0fef0a

Please sign in to comment.