diff options
author | Duncan Sands <baldrick@free.fr> | 2006-01-13 05:06:46 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-01-31 20:23:40 -0500 |
commit | e3fb2f641f421662ebda48763f2f03cb9bd29e82 (patch) | |
tree | 2511087a08a177c27bf28207f3ab05ac3093b85d /drivers/usb | |
parent | 80aae7a17afd21f7ba900dd566fb23a2444021f8 (diff) |
[PATCH] USBATM: handle urbs containing partial cells
The receive logic has always assumed that urbs contain an integral
number of ATM cells, which is a bit naughty, though it never caused
any problems with bulk transfers. Isochronous urbs spank us soundly
for this. Fixed thanks to this patch, mostly by Stanislaw Gruszka.
Signed-off-by: Duncan Sands <baldrick@free.fr>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/atm/usbatm.c | 271 | ||||
-rw-r--r-- | drivers/usb/atm/usbatm.h | 7 |
2 files changed, 182 insertions, 96 deletions
diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index 923f2d9269b..341430fbaf9 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c | |||
@@ -296,126 +296,159 @@ static inline struct usbatm_vcc_data *usbatm_find_vcc(struct usbatm_data *instan | |||
296 | return NULL; | 296 | return NULL; |
297 | } | 297 | } |
298 | 298 | ||
299 | static void usbatm_extract_cells(struct usbatm_data *instance, | 299 | static void usbatm_extract_one_cell(struct usbatm_data *instance, unsigned char *source) |
300 | unsigned char *source, unsigned int avail_data) | ||
301 | { | 300 | { |
302 | struct usbatm_vcc_data *cached_vcc = NULL; | ||
303 | struct atm_vcc *vcc; | 301 | struct atm_vcc *vcc; |
304 | struct sk_buff *sarb; | 302 | struct sk_buff *sarb; |
305 | unsigned int stride = instance->rx_channel.stride; | 303 | short vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); |
306 | int vci, cached_vci = 0; | 304 | int vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); |
307 | short vpi, cached_vpi = 0; | 305 | u8 pti = ((source[3] & 0xe) >> 1); |
308 | u8 pti; | ||
309 | 306 | ||
310 | for (; avail_data >= stride; avail_data -= stride, source += stride) { | 307 | vdbg("%s: vpi %hd, vci %d, pti %d", __func__, vpi, vci, pti); |
311 | vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); | ||
312 | vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); | ||
313 | pti = ((source[3] & 0xe) >> 1); | ||
314 | 308 | ||
315 | vdbg("%s: vpi %hd, vci %d, pti %d", __func__, vpi, vci, pti); | 309 | if ((vci != instance->cached_vci) || (vpi != instance->cached_vpi)) { |
310 | instance->cached_vpi = vpi; | ||
311 | instance->cached_vci = vci; | ||
316 | 312 | ||
317 | if ((vci != cached_vci) || (vpi != cached_vpi)) { | 313 | instance->cached_vcc = usbatm_find_vcc(instance, vpi, vci); |
318 | cached_vpi = vpi; | ||
319 | cached_vci = vci; | ||
320 | 314 | ||
321 | cached_vcc = usbatm_find_vcc(instance, vpi, vci); | 315 | if (!instance->cached_vcc) |
316 | atm_rldbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci); | ||
317 | } | ||
322 | 318 | ||
323 | if (!cached_vcc) | 319 | if (!instance->cached_vcc) |
324 | atm_rldbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci); | 320 | return; |
325 | } | ||
326 | 321 | ||
327 | if (!cached_vcc) | 322 | vcc = instance->cached_vcc->vcc; |
328 | continue; | ||
329 | 323 | ||
330 | vcc = cached_vcc->vcc; | 324 | /* OAM F5 end-to-end */ |
325 | if (pti == ATM_PTI_E2EF5) { | ||
326 | if (printk_ratelimit()) | ||
327 | atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n", | ||
328 | __func__, vpi, vci); | ||
329 | atomic_inc(&vcc->stats->rx_err); | ||
330 | return; | ||
331 | } | ||
331 | 332 | ||
332 | /* OAM F5 end-to-end */ | 333 | sarb = instance->cached_vcc->sarb; |
333 | if (pti == ATM_PTI_E2EF5) { | ||
334 | if (printk_ratelimit()) | ||
335 | atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n", | ||
336 | __func__, vpi, vci); | ||
337 | atomic_inc(&vcc->stats->rx_err); | ||
338 | continue; | ||
339 | } | ||
340 | 334 | ||
341 | sarb = cached_vcc->sarb; | 335 | if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { |
336 | atm_rldbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n", | ||
337 | __func__, sarb->len, vcc); | ||
338 | /* discard cells already received */ | ||
339 | skb_trim(sarb, 0); | ||
340 | UDSL_ASSERT(sarb->tail + ATM_CELL_PAYLOAD <= sarb->end); | ||
341 | } | ||
342 | 342 | ||
343 | if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { | 343 | memcpy(sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); |
344 | atm_rldbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n", | 344 | __skb_put(sarb, ATM_CELL_PAYLOAD); |
345 | __func__, sarb->len, vcc); | ||
346 | /* discard cells already received */ | ||
347 | skb_trim(sarb, 0); | ||
348 | UDSL_ASSERT(sarb->tail + ATM_CELL_PAYLOAD <= sarb->end); | ||
349 | } | ||
350 | 345 | ||
351 | memcpy(sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); | 346 | if (pti & 1) { |
352 | __skb_put(sarb, ATM_CELL_PAYLOAD); | 347 | struct sk_buff *skb; |
348 | unsigned int length; | ||
349 | unsigned int pdu_length; | ||
353 | 350 | ||
354 | if (pti & 1) { | 351 | length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; |
355 | struct sk_buff *skb; | ||
356 | unsigned int length; | ||
357 | unsigned int pdu_length; | ||
358 | 352 | ||
359 | length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; | 353 | /* guard against overflow */ |
354 | if (length > ATM_MAX_AAL5_PDU) { | ||
355 | atm_rldbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n", | ||
356 | __func__, length, vcc); | ||
357 | atomic_inc(&vcc->stats->rx_err); | ||
358 | goto out; | ||
359 | } | ||
360 | 360 | ||
361 | /* guard against overflow */ | 361 | pdu_length = usbatm_pdu_length(length); |
362 | if (length > ATM_MAX_AAL5_PDU) { | ||
363 | atm_rldbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n", | ||
364 | __func__, length, vcc); | ||
365 | atomic_inc(&vcc->stats->rx_err); | ||
366 | goto out; | ||
367 | } | ||
368 | 362 | ||
369 | pdu_length = usbatm_pdu_length(length); | 363 | if (sarb->len < pdu_length) { |
364 | atm_rldbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n", | ||
365 | __func__, pdu_length, sarb->len, vcc); | ||
366 | atomic_inc(&vcc->stats->rx_err); | ||
367 | goto out; | ||
368 | } | ||
370 | 369 | ||
371 | if (sarb->len < pdu_length) { | 370 | if (crc32_be(~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { |
372 | atm_rldbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n", | 371 | atm_rldbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n", |
373 | __func__, pdu_length, sarb->len, vcc); | 372 | __func__, vcc); |
374 | atomic_inc(&vcc->stats->rx_err); | 373 | atomic_inc(&vcc->stats->rx_err); |
375 | goto out; | 374 | goto out; |
376 | } | 375 | } |
377 | 376 | ||
378 | if (crc32_be(~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { | 377 | vdbg("%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", __func__, length, pdu_length, vcc); |
379 | atm_rldbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n", | ||
380 | __func__, vcc); | ||
381 | atomic_inc(&vcc->stats->rx_err); | ||
382 | goto out; | ||
383 | } | ||
384 | 378 | ||
385 | vdbg("%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", __func__, length, pdu_length, vcc); | 379 | if (!(skb = dev_alloc_skb(length))) { |
380 | if (printk_ratelimit()) | ||
381 | atm_err(instance, "%s: no memory for skb (length: %u)!\n", | ||
382 | __func__, length); | ||
383 | atomic_inc(&vcc->stats->rx_drop); | ||
384 | goto out; | ||
385 | } | ||
386 | 386 | ||
387 | if (!(skb = dev_alloc_skb(length))) { | 387 | vdbg("%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", __func__, skb, skb->truesize); |
388 | if (printk_ratelimit()) | ||
389 | atm_err(instance, "%s: no memory for skb (length: %u)!\n", | ||
390 | __func__, length); | ||
391 | atomic_inc(&vcc->stats->rx_drop); | ||
392 | goto out; | ||
393 | } | ||
394 | 388 | ||
395 | vdbg("%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", __func__, skb, skb->truesize); | 389 | if (!atm_charge(vcc, skb->truesize)) { |
390 | atm_rldbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n", | ||
391 | __func__, skb->truesize); | ||
392 | dev_kfree_skb_any(skb); | ||
393 | goto out; /* atm_charge increments rx_drop */ | ||
394 | } | ||
396 | 395 | ||
397 | if (!atm_charge(vcc, skb->truesize)) { | 396 | memcpy(skb->data, sarb->tail - pdu_length, length); |
398 | atm_rldbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n", | 397 | __skb_put(skb, length); |
399 | __func__, skb->truesize); | ||
400 | dev_kfree_skb_any(skb); | ||
401 | goto out; /* atm_charge increments rx_drop */ | ||
402 | } | ||
403 | 398 | ||
404 | memcpy(skb->data, sarb->tail - pdu_length, length); | 399 | vdbg("%s: sending skb 0x%p, skb->len %u, skb->truesize %u", |
405 | __skb_put(skb, length); | 400 | __func__, skb, skb->len, skb->truesize); |
406 | 401 | ||
407 | vdbg("%s: sending skb 0x%p, skb->len %u, skb->truesize %u", | 402 | PACKETDEBUG(skb->data, skb->len); |
408 | __func__, skb, skb->len, skb->truesize); | ||
409 | 403 | ||
410 | PACKETDEBUG(skb->data, skb->len); | 404 | vcc->push(vcc, skb); |
411 | 405 | ||
412 | vcc->push(vcc, skb); | 406 | atomic_inc(&vcc->stats->rx); |
407 | out: | ||
408 | skb_trim(sarb, 0); | ||
409 | } | ||
410 | } | ||
413 | 411 | ||
414 | atomic_inc(&vcc->stats->rx); | 412 | static void usbatm_extract_cells(struct usbatm_data *instance, |
415 | out: | 413 | unsigned char *source, unsigned int avail_data) |
416 | skb_trim(sarb, 0); | 414 | { |
415 | unsigned int stride = instance->rx_channel.stride; | ||
416 | unsigned int buf_usage = instance->buf_usage; | ||
417 | |||
418 | /* extract cells from incoming data, taking into account that | ||
419 | * the length of avail data may not be a multiple of stride */ | ||
420 | |||
421 | if (buf_usage > 0) { | ||
422 | /* we have a partially received atm cell */ | ||
423 | unsigned char *cell_buf = instance->cell_buf; | ||
424 | unsigned int space_left = stride - buf_usage; | ||
425 | |||
426 | UDSL_ASSERT(buf_usage <= stride); | ||
427 | |||
428 | if (avail_data >= space_left) { | ||
429 | /* add new data and process cell */ | ||
430 | memcpy(cell_buf + buf_usage, source, space_left); | ||
431 | source += space_left; | ||
432 | avail_data -= space_left; | ||
433 | usbatm_extract_one_cell(instance, cell_buf); | ||
434 | instance->buf_usage = 0; | ||
435 | } else { | ||
436 | /* not enough data to fill the cell */ | ||
437 | memcpy(cell_buf + buf_usage, source, avail_data); | ||
438 | instance->buf_usage = buf_usage + avail_data; | ||
439 | return; | ||
417 | } | 440 | } |
418 | } | 441 | } |
442 | |||
443 | for (; avail_data >= stride; avail_data -= stride, source += stride) | ||
444 | usbatm_extract_one_cell(instance, source); | ||
445 | |||
446 | if (avail_data > 0) { | ||
447 | /* length was not a multiple of stride - | ||
448 | * save remaining data for next call */ | ||
449 | memcpy(instance->cell_buf, source, avail_data); | ||
450 | instance->buf_usage = avail_data; | ||
451 | } | ||
419 | } | 452 | } |
420 | 453 | ||
421 | 454 | ||
@@ -496,16 +529,40 @@ static void usbatm_rx_process(unsigned long data) | |||
496 | vdbg("%s: processing urb 0x%p", __func__, urb); | 529 | vdbg("%s: processing urb 0x%p", __func__, urb); |
497 | 530 | ||
498 | if (usb_pipeisoc(urb->pipe)) { | 531 | if (usb_pipeisoc(urb->pipe)) { |
532 | unsigned char *merge_start = NULL; | ||
533 | unsigned int merge_length = 0; | ||
534 | const unsigned int packet_size = instance->rx_channel.packet_size; | ||
499 | int i; | 535 | int i; |
500 | for (i = 0; i < urb->number_of_packets; i++) | 536 | |
501 | if (!urb->iso_frame_desc[i].status) | 537 | for (i = 0; i < urb->number_of_packets; i++) { |
502 | usbatm_extract_cells(instance, | 538 | if (!urb->iso_frame_desc[i].status) { |
503 | (u8 *)urb->transfer_buffer + urb->iso_frame_desc[i].offset, | 539 | unsigned int actual_length = urb->iso_frame_desc[i].actual_length; |
504 | urb->iso_frame_desc[i].actual_length); | 540 | |
505 | } | 541 | UDSL_ASSERT(actual_length <= packet_size); |
506 | else | 542 | |
543 | if (!merge_length) | ||
544 | merge_start = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; | ||
545 | merge_length += actual_length; | ||
546 | if (merge_length && (actual_length < packet_size)) { | ||
547 | usbatm_extract_cells(instance, merge_start, merge_length); | ||
548 | merge_length = 0; | ||
549 | } | ||
550 | } else { | ||
551 | atm_rldbg(instance, "%s: status %d in frame %d!\n", __func__, urb->status, i); | ||
552 | if (merge_length) | ||
553 | usbatm_extract_cells(instance, merge_start, merge_length); | ||
554 | merge_length = 0; | ||
555 | instance->buf_usage = 0; | ||
556 | } | ||
557 | } | ||
558 | |||
559 | if (merge_length) | ||
560 | usbatm_extract_cells(instance, merge_start, merge_length); | ||
561 | } else | ||
507 | if (!urb->status) | 562 | if (!urb->status) |
508 | usbatm_extract_cells(instance, urb->transfer_buffer, urb->actual_length); | 563 | usbatm_extract_cells(instance, urb->transfer_buffer, urb->actual_length); |
564 | else | ||
565 | instance->buf_usage = 0; | ||
509 | 566 | ||
510 | if (usbatm_submit_urb(urb)) | 567 | if (usbatm_submit_urb(urb)) |
511 | return; | 568 | return; |
@@ -797,6 +854,9 @@ static int usbatm_atm_open(struct atm_vcc *vcc) | |||
797 | vcc->dev_data = new; | 854 | vcc->dev_data = new; |
798 | 855 | ||
799 | tasklet_disable(&instance->rx_channel.tasklet); | 856 | tasklet_disable(&instance->rx_channel.tasklet); |
857 | instance->cached_vcc = new; | ||
858 | instance->cached_vpi = vpi; | ||
859 | instance->cached_vci = vci; | ||
800 | list_add(&new->list, &instance->vcc_list); | 860 | list_add(&new->list, &instance->vcc_list); |
801 | tasklet_enable(&instance->rx_channel.tasklet); | 861 | tasklet_enable(&instance->rx_channel.tasklet); |
802 | 862 | ||
@@ -836,6 +896,11 @@ static void usbatm_atm_close(struct atm_vcc *vcc) | |||
836 | down(&instance->serialize); /* vs self, usbatm_atm_open, usbatm_usb_disconnect */ | 896 | down(&instance->serialize); /* vs self, usbatm_atm_open, usbatm_usb_disconnect */ |
837 | 897 | ||
838 | tasklet_disable(&instance->rx_channel.tasklet); | 898 | tasklet_disable(&instance->rx_channel.tasklet); |
899 | if (instance->cached_vcc == vcc_data) { | ||
900 | instance->cached_vcc = NULL; | ||
901 | instance->cached_vpi = ATM_VPI_UNSPEC; | ||
902 | instance->cached_vci = ATM_VCI_UNSPEC; | ||
903 | } | ||
839 | list_del(&vcc_data->list); | 904 | list_del(&vcc_data->list); |
840 | tasklet_enable(&instance->rx_channel.tasklet); | 905 | tasklet_enable(&instance->rx_channel.tasklet); |
841 | 906 | ||
@@ -1146,6 +1211,16 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, | |||
1146 | __func__, urb->transfer_buffer, urb->transfer_buffer_length, urb); | 1211 | __func__, urb->transfer_buffer, urb->transfer_buffer_length, urb); |
1147 | } | 1212 | } |
1148 | 1213 | ||
1214 | instance->cached_vpi = ATM_VPI_UNSPEC; | ||
1215 | instance->cached_vci = ATM_VCI_UNSPEC; | ||
1216 | instance->cell_buf = kmalloc(instance->rx_channel.stride, GFP_KERNEL); | ||
1217 | |||
1218 | if (!instance->cell_buf) { | ||
1219 | dev_err(dev, "%s: no memory for cell buffer!\n", __func__); | ||
1220 | error = -ENOMEM; | ||
1221 | goto fail_unbind; | ||
1222 | } | ||
1223 | |||
1149 | if (!(instance->flags & UDSL_SKIP_HEAVY_INIT) && driver->heavy_init) { | 1224 | if (!(instance->flags & UDSL_SKIP_HEAVY_INIT) && driver->heavy_init) { |
1150 | error = usbatm_heavy_init(instance); | 1225 | error = usbatm_heavy_init(instance); |
1151 | } else { | 1226 | } else { |
@@ -1165,6 +1240,8 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, | |||
1165 | if (instance->driver->unbind) | 1240 | if (instance->driver->unbind) |
1166 | instance->driver->unbind(instance, intf); | 1241 | instance->driver->unbind(instance, intf); |
1167 | fail_free: | 1242 | fail_free: |
1243 | kfree(instance->cell_buf); | ||
1244 | |||
1168 | for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { | 1245 | for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { |
1169 | if (instance->urbs[i]) | 1246 | if (instance->urbs[i]) |
1170 | kfree(instance->urbs[i]->transfer_buffer); | 1247 | kfree(instance->urbs[i]->transfer_buffer); |
@@ -1236,6 +1313,8 @@ void usbatm_usb_disconnect(struct usb_interface *intf) | |||
1236 | usb_free_urb(instance->urbs[i]); | 1313 | usb_free_urb(instance->urbs[i]); |
1237 | } | 1314 | } |
1238 | 1315 | ||
1316 | kfree(instance->cell_buf); | ||
1317 | |||
1239 | /* ATM finalize */ | 1318 | /* ATM finalize */ |
1240 | if (instance->atm_dev) | 1319 | if (instance->atm_dev) |
1241 | atm_dev_deregister(instance->atm_dev); | 1320 | atm_dev_deregister(instance->atm_dev); |
diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h index 0e2caa0967c..bdff534b941 100644 --- a/drivers/usb/atm/usbatm.h +++ b/drivers/usb/atm/usbatm.h | |||
@@ -187,6 +187,13 @@ struct usbatm_data { | |||
187 | struct sk_buff_head sndqueue; | 187 | struct sk_buff_head sndqueue; |
188 | struct sk_buff *current_skb; /* being emptied */ | 188 | struct sk_buff *current_skb; /* being emptied */ |
189 | 189 | ||
190 | struct usbatm_vcc_data *cached_vcc; | ||
191 | int cached_vci; | ||
192 | short cached_vpi; | ||
193 | |||
194 | unsigned char *cell_buf; /* holds partial rx cell */ | ||
195 | unsigned int buf_usage; | ||
196 | |||
190 | struct urb *urbs[0]; | 197 | struct urb *urbs[0]; |
191 | }; | 198 | }; |
192 | 199 | ||