summaryrefslogtreecommitdiff
path: root/libusb/os/linux_usbfs.c
diff options
context:
space:
mode:
authorDaniel Drake <dsd@gentoo.org>2009-07-09 22:09:04 +0100
committerDaniel Drake <dsd@gentoo.org>2009-07-09 22:09:04 +0100
commit126129e174062c2a27423817a459e5113f777789 (patch)
tree97fbf5eb61a27af9d2660e8f75735b5d5500bdfe /libusb/os/linux_usbfs.c
parent0334ee642b47dfe1ca9db64b22e7702ea14b3f09 (diff)
downloadlibusb-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.c46
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;