diff options
author | Mat Martineau <mathewm@codeaurora.org> | 2011-07-22 17:54:00 -0400 |
---|---|---|
committer | Gustavo F. Padovan <gustavo@padovan.org> | 2011-09-27 17:16:18 -0400 |
commit | 84084a3197a9fdec10fa542c0df11928a784e7fc (patch) | |
tree | 4da560352bb5af8418d22fc65e516004d2e666b5 /net | |
parent | 5b668eb3270f3f9c13ddf6e4fb57bf20c83dccff (diff) |
Bluetooth: Perform L2CAP SDU reassembly without copying data
Use sk_buff fragment capabilities to link together incoming skbs
instead of allocating a new skb for reassembly and copying.
The new reassembly code works equally well for ERTM and streaming
mode, so there is now one reassembly function instead of two.
Signed-off-by: Mat Martineau <mathewm@codeaurora.org>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
Diffstat (limited to 'net')
-rw-r--r-- | net/bluetooth/l2cap_core.c | 243 |
1 files changed, 80 insertions, 163 deletions
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1611b3544bb1..12b1e742d706 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c | |||
@@ -3128,102 +3128,104 @@ static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb, | |||
3128 | return 0; | 3128 | return 0; |
3129 | } | 3129 | } |
3130 | 3130 | ||
3131 | static int l2cap_ertm_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) | 3131 | static void append_skb_frag(struct sk_buff *skb, |
3132 | struct sk_buff *new_frag, struct sk_buff **last_frag) | ||
3132 | { | 3133 | { |
3133 | struct sk_buff *_skb; | 3134 | /* skb->len reflects data in skb as well as all fragments |
3134 | int err; | 3135 | * skb->data_len reflects only data in fragments |
3136 | */ | ||
3137 | if (!skb_has_frag_list(skb)) | ||
3138 | skb_shinfo(skb)->frag_list = new_frag; | ||
3139 | |||
3140 | new_frag->next = NULL; | ||
3141 | |||
3142 | (*last_frag)->next = new_frag; | ||
3143 | *last_frag = new_frag; | ||
3144 | |||
3145 | skb->len += new_frag->len; | ||
3146 | skb->data_len += new_frag->len; | ||
3147 | skb->truesize += new_frag->truesize; | ||
3148 | } | ||
3149 | |||
3150 | static int l2cap_reassemble_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) | ||
3151 | { | ||
3152 | int err = -EINVAL; | ||
3135 | 3153 | ||
3136 | switch (control & L2CAP_CTRL_SAR) { | 3154 | switch (control & L2CAP_CTRL_SAR) { |
3137 | case L2CAP_SDU_UNSEGMENTED: | 3155 | case L2CAP_SDU_UNSEGMENTED: |
3138 | if (test_bit(CONN_SAR_SDU, &chan->conn_state)) | 3156 | if (chan->sdu) |
3139 | goto drop; | 3157 | break; |
3140 | 3158 | ||
3141 | return chan->ops->recv(chan->data, skb); | 3159 | err = chan->ops->recv(chan->data, skb); |
3160 | break; | ||
3142 | 3161 | ||
3143 | case L2CAP_SDU_START: | 3162 | case L2CAP_SDU_START: |
3144 | if (test_bit(CONN_SAR_SDU, &chan->conn_state)) | 3163 | if (chan->sdu) |
3145 | goto drop; | 3164 | break; |
3146 | 3165 | ||
3147 | chan->sdu_len = get_unaligned_le16(skb->data); | 3166 | chan->sdu_len = get_unaligned_le16(skb->data); |
3167 | skb_pull(skb, 2); | ||
3148 | 3168 | ||
3149 | if (chan->sdu_len > chan->imtu) | 3169 | if (chan->sdu_len > chan->imtu) { |
3150 | goto disconnect; | 3170 | err = -EMSGSIZE; |
3151 | 3171 | break; | |
3152 | chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); | 3172 | } |
3153 | if (!chan->sdu) | ||
3154 | return -ENOMEM; | ||
3155 | 3173 | ||
3156 | /* pull sdu_len bytes only after alloc, because of Local Busy | 3174 | if (skb->len >= chan->sdu_len) |
3157 | * condition we have to be sure that this will be executed | 3175 | break; |
3158 | * only once, i.e., when alloc does not fail */ | ||
3159 | skb_pull(skb, 2); | ||
3160 | 3176 | ||
3161 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | 3177 | chan->sdu = skb; |
3178 | chan->sdu_last_frag = skb; | ||
3162 | 3179 | ||
3163 | set_bit(CONN_SAR_SDU, &chan->conn_state); | 3180 | skb = NULL; |
3164 | chan->partial_sdu_len = skb->len; | 3181 | err = 0; |
3165 | break; | 3182 | break; |
3166 | 3183 | ||
3167 | case L2CAP_SDU_CONTINUE: | 3184 | case L2CAP_SDU_CONTINUE: |
3168 | if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) | ||
3169 | goto disconnect; | ||
3170 | |||
3171 | if (!chan->sdu) | 3185 | if (!chan->sdu) |
3172 | goto disconnect; | 3186 | break; |
3173 | 3187 | ||
3174 | chan->partial_sdu_len += skb->len; | 3188 | append_skb_frag(chan->sdu, skb, |
3175 | if (chan->partial_sdu_len > chan->sdu_len) | 3189 | &chan->sdu_last_frag); |
3176 | goto drop; | 3190 | skb = NULL; |
3177 | 3191 | ||
3178 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | 3192 | if (chan->sdu->len >= chan->sdu_len) |
3193 | break; | ||
3179 | 3194 | ||
3195 | err = 0; | ||
3180 | break; | 3196 | break; |
3181 | 3197 | ||
3182 | case L2CAP_SDU_END: | 3198 | case L2CAP_SDU_END: |
3183 | if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) | ||
3184 | goto disconnect; | ||
3185 | |||
3186 | if (!chan->sdu) | 3199 | if (!chan->sdu) |
3187 | goto disconnect; | 3200 | break; |
3188 | |||
3189 | chan->partial_sdu_len += skb->len; | ||
3190 | |||
3191 | if (chan->partial_sdu_len > chan->imtu) | ||
3192 | goto drop; | ||
3193 | 3201 | ||
3194 | if (chan->partial_sdu_len != chan->sdu_len) | 3202 | append_skb_frag(chan->sdu, skb, |
3195 | goto drop; | 3203 | &chan->sdu_last_frag); |
3204 | skb = NULL; | ||
3196 | 3205 | ||
3197 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | 3206 | if (chan->sdu->len != chan->sdu_len) |
3207 | break; | ||
3198 | 3208 | ||
3199 | _skb = skb_clone(chan->sdu, GFP_ATOMIC); | 3209 | err = chan->ops->recv(chan->data, chan->sdu); |
3200 | if (!_skb) { | ||
3201 | return -ENOMEM; | ||
3202 | } | ||
3203 | 3210 | ||
3204 | err = chan->ops->recv(chan->data, _skb); | 3211 | if (!err) { |
3205 | if (err < 0) { | 3212 | /* Reassembly complete */ |
3206 | kfree_skb(_skb); | 3213 | chan->sdu = NULL; |
3207 | return err; | 3214 | chan->sdu_last_frag = NULL; |
3215 | chan->sdu_len = 0; | ||
3208 | } | 3216 | } |
3209 | |||
3210 | clear_bit(CONN_SAR_SDU, &chan->conn_state); | ||
3211 | |||
3212 | kfree_skb(chan->sdu); | ||
3213 | break; | 3217 | break; |
3214 | } | 3218 | } |
3215 | 3219 | ||
3216 | kfree_skb(skb); | 3220 | if (err) { |
3217 | return 0; | 3221 | kfree_skb(skb); |
3218 | 3222 | kfree_skb(chan->sdu); | |
3219 | drop: | 3223 | chan->sdu = NULL; |
3220 | kfree_skb(chan->sdu); | 3224 | chan->sdu_last_frag = NULL; |
3221 | chan->sdu = NULL; | 3225 | chan->sdu_len = 0; |
3226 | } | ||
3222 | 3227 | ||
3223 | disconnect: | 3228 | return err; |
3224 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); | ||
3225 | kfree_skb(skb); | ||
3226 | return 0; | ||
3227 | } | 3229 | } |
3228 | 3230 | ||
3229 | static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan) | 3231 | static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan) |
@@ -3277,99 +3279,6 @@ void l2cap_chan_busy(struct l2cap_chan *chan, int busy) | |||
3277 | } | 3279 | } |
3278 | } | 3280 | } |
3279 | 3281 | ||
3280 | static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) | ||
3281 | { | ||
3282 | struct sk_buff *_skb; | ||
3283 | int err = -EINVAL; | ||
3284 | |||
3285 | /* | ||
3286 | * TODO: We have to notify the userland if some data is lost with the | ||
3287 | * Streaming Mode. | ||
3288 | */ | ||
3289 | |||
3290 | switch (control & L2CAP_CTRL_SAR) { | ||
3291 | case L2CAP_SDU_UNSEGMENTED: | ||
3292 | if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { | ||
3293 | kfree_skb(chan->sdu); | ||
3294 | break; | ||
3295 | } | ||
3296 | |||
3297 | err = chan->ops->recv(chan->data, skb); | ||
3298 | if (!err) | ||
3299 | return 0; | ||
3300 | |||
3301 | break; | ||
3302 | |||
3303 | case L2CAP_SDU_START: | ||
3304 | if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { | ||
3305 | kfree_skb(chan->sdu); | ||
3306 | break; | ||
3307 | } | ||
3308 | |||
3309 | chan->sdu_len = get_unaligned_le16(skb->data); | ||
3310 | skb_pull(skb, 2); | ||
3311 | |||
3312 | if (chan->sdu_len > chan->imtu) { | ||
3313 | err = -EMSGSIZE; | ||
3314 | break; | ||
3315 | } | ||
3316 | |||
3317 | chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); | ||
3318 | if (!chan->sdu) { | ||
3319 | err = -ENOMEM; | ||
3320 | break; | ||
3321 | } | ||
3322 | |||
3323 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | ||
3324 | |||
3325 | set_bit(CONN_SAR_SDU, &chan->conn_state); | ||
3326 | chan->partial_sdu_len = skb->len; | ||
3327 | err = 0; | ||
3328 | break; | ||
3329 | |||
3330 | case L2CAP_SDU_CONTINUE: | ||
3331 | if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) | ||
3332 | break; | ||
3333 | |||
3334 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | ||
3335 | |||
3336 | chan->partial_sdu_len += skb->len; | ||
3337 | if (chan->partial_sdu_len > chan->sdu_len) | ||
3338 | kfree_skb(chan->sdu); | ||
3339 | else | ||
3340 | err = 0; | ||
3341 | |||
3342 | break; | ||
3343 | |||
3344 | case L2CAP_SDU_END: | ||
3345 | if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) | ||
3346 | break; | ||
3347 | |||
3348 | memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); | ||
3349 | |||
3350 | clear_bit(CONN_SAR_SDU, &chan->conn_state); | ||
3351 | chan->partial_sdu_len += skb->len; | ||
3352 | |||
3353 | if (chan->partial_sdu_len > chan->imtu) | ||
3354 | goto drop; | ||
3355 | |||
3356 | if (chan->partial_sdu_len == chan->sdu_len) { | ||
3357 | _skb = skb_clone(chan->sdu, GFP_ATOMIC); | ||
3358 | err = chan->ops->recv(chan->data, _skb); | ||
3359 | if (err < 0) | ||
3360 | kfree_skb(_skb); | ||
3361 | } | ||
3362 | err = 0; | ||
3363 | |||
3364 | drop: | ||
3365 | kfree_skb(chan->sdu); | ||
3366 | break; | ||
3367 | } | ||
3368 | |||
3369 | kfree_skb(skb); | ||
3370 | return err; | ||
3371 | } | ||
3372 | |||
3373 | static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) | 3282 | static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) |
3374 | { | 3283 | { |
3375 | struct sk_buff *skb; | 3284 | struct sk_buff *skb; |
@@ -3384,7 +3293,7 @@ static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) | |||
3384 | 3293 | ||
3385 | skb = skb_dequeue(&chan->srej_q); | 3294 | skb = skb_dequeue(&chan->srej_q); |
3386 | control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT; | 3295 | control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT; |
3387 | err = l2cap_ertm_reassembly_sdu(chan, skb, control); | 3296 | err = l2cap_reassemble_sdu(chan, skb, control); |
3388 | 3297 | ||
3389 | if (err < 0) { | 3298 | if (err < 0) { |
3390 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); | 3299 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); |
@@ -3544,7 +3453,7 @@ expected: | |||
3544 | return 0; | 3453 | return 0; |
3545 | } | 3454 | } |
3546 | 3455 | ||
3547 | err = l2cap_ertm_reassembly_sdu(chan, skb, rx_control); | 3456 | err = l2cap_reassemble_sdu(chan, skb, rx_control); |
3548 | chan->buffer_seq = (chan->buffer_seq + 1) % 64; | 3457 | chan->buffer_seq = (chan->buffer_seq + 1) % 64; |
3549 | if (err < 0) { | 3458 | if (err < 0) { |
3550 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); | 3459 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); |
@@ -3860,12 +3769,20 @@ static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk | |||
3860 | 3769 | ||
3861 | tx_seq = __get_txseq(control); | 3770 | tx_seq = __get_txseq(control); |
3862 | 3771 | ||
3863 | if (chan->expected_tx_seq == tx_seq) | 3772 | if (chan->expected_tx_seq != tx_seq) { |
3864 | chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; | 3773 | /* Frame(s) missing - must discard partial SDU */ |
3865 | else | 3774 | kfree_skb(chan->sdu); |
3866 | chan->expected_tx_seq = (tx_seq + 1) % 64; | 3775 | chan->sdu = NULL; |
3776 | chan->sdu_last_frag = NULL; | ||
3777 | chan->sdu_len = 0; | ||
3867 | 3778 | ||
3868 | l2cap_streaming_reassembly_sdu(chan, skb, control); | 3779 | /* TODO: Notify userland of missing data */ |
3780 | } | ||
3781 | |||
3782 | chan->expected_tx_seq = (tx_seq + 1) % 64; | ||
3783 | |||
3784 | if (l2cap_reassemble_sdu(chan, skb, control) == -EMSGSIZE) | ||
3785 | l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); | ||
3869 | 3786 | ||
3870 | goto done; | 3787 | goto done; |
3871 | 3788 | ||