diff options
author | Daniel Drake <dsd@gentoo.org> | 2009-07-09 22:09:04 +0100 |
---|---|---|
committer | Daniel Drake <dsd@gentoo.org> | 2009-07-09 22:09:04 +0100 |
commit | 126129e174062c2a27423817a459e5113f777789 (patch) | |
tree | 97fbf5eb61a27af9d2660e8f75735b5d5500bdfe /libusb/os/linux_usbfs.c | |
parent | 0334ee642b47dfe1ca9db64b22e7702ea14b3f09 (diff) | |
download | libusb-126129e174062c2a27423817a459e5113f777789.tar.gz |
Linux: try harder not to lose any data
We would previously lose any data that was present on a cancelled URB.
Work harder to make sure this doesn't happen.
Diffstat (limited to 'libusb/os/linux_usbfs.c')
-rw-r--r-- | libusb/os/linux_usbfs.c | 46 |
1 files changed, 35 insertions, 11 deletions
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index f9899cf..1280188 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -1710,22 +1710,41 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, tpriv->num_retired++; - if (urb->status == 0 || - (urb->status == -EOVERFLOW && urb->actual_length > 0)) - itransfer->transferred += urb->actual_length; - if (tpriv->reap_action != NORMAL) { /* cancelled, submit_fail, or completed early */ - if (urb->status == 0 && tpriv->reap_action == COMPLETED_EARLY) { - /* FIXME we could solve this extreme corner case with a memmove - * or something */ - usbi_warn(ITRANSFER_CTX(itransfer), "SOME DATA LOST! " - "(completed early but remaining urb completed)"); + usbi_dbg("abnormal reap: urb status %d", urb->status); + + /* even though we're in the process of cancelling, it's possible that + * we may receive some data in these URBs that we don't want to lose. + * examples: + * 1. while the kernel is cancelling all the packets that make up an + * URB, a few of them might complete. so we get back a successful + * cancellation *and* some data. + * 2. we receive a short URB which marks the early completion condition, + * so we start cancelling the remaining URBs. however, we're too + * slow and another URB completes (or at least completes partially). + * + * When this happens, our objectives are not to lose any "surplus" data, + * and also to stick it at the end of the previously-received data + * (closing any holes), so that libusb reports the total amount of + * transferred data and presents it in a contiguous chunk. + */ + if (urb->actual_length > 0) { + struct libusb_transfer *transfer = + __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + unsigned char *target = transfer->buffer + itransfer->transferred; + usbi_dbg("received %d bytes of surplus data", urb->actual_length); + if (urb->buffer != target) { + usbi_dbg("moving surplus data from offset %d to offset %d", + (unsigned char *) urb->buffer - transfer->buffer, + target - transfer->buffer); + memmove(target, urb->buffer, urb->actual_length); + } + itransfer->transferred += urb->actual_length; } - usbi_dbg("CANCEL: urb status %d", urb->status); if (tpriv->num_retired == num_urbs) { - usbi_dbg("CANCEL: last URB handled, reporting"); + usbi_dbg("abnormal reap: last URB handled, reporting"); if (tpriv->reap_action == CANCELLED) { free(tpriv->urbs); tpriv->urbs = NULL; @@ -1739,6 +1758,11 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, return 0; } + if (urb->status == 0 || + (urb->status == -EOVERFLOW && urb->actual_length > 0)) + itransfer->transferred += urb->actual_length; + + switch (urb->status) { case 0: break; |