Linux: fire multiple URBs at once for split transfers
authorDaniel Drake <dsd@gentoo.org>
Fri, 11 Apr 2008 20:40:07 +0000 (21:40 +0100)
committerDaniel Drake <dsd@gentoo.org>
Fri, 11 Apr 2008 20:42:29 +0000 (21:42 +0100)
This results in a significant performance increase for bulk transfers
larger than 16kb, and fixes a bug where data would be spliced and mixed
between two simultaneously submitted transfers to the same endpoint.

It also allows isochronous transfers larger than 16kb to be submitted.

This commit also improves cancellation - the library now understands
what is going on.

TODO
libusb/io.c
libusb/libusbi.h
libusb/os/linux_usbfs.c
libusb/os/linux_usbfs.h

diff --git a/TODO b/TODO
index b9bf475..bf4d27c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,13 +1,11 @@
 for 1.0
 =======
-cancellation race concerns - better tracking of kernel feedback?
 thread safety
 error codes
 fixme review
 review functionality missing over 0.1
 endianness of control setup, issues when resubmitting transfers
 doxygen warnings
-isochronous sync I/O?
 
 1.0 API style/naming points to reconsider
 =========================================
@@ -19,3 +17,4 @@ optional timerfd support (runtime detection)
 notifications of hotplugged/unplugged devices
 use poll() rather than select()?
 offer API to create/destroy handle_events thread
+isochronous sync I/O?
index 9b99f82..4e79368 100644 (file)
@@ -799,6 +799,7 @@ void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
                __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
        uint8_t flags;
 
+       list_del(&itransfer->list);
        if (status == LIBUSB_TRANSFER_SILENT_COMPLETION)
                return;
 
index 3cc33bf..c589ef6 100644 (file)
@@ -283,6 +283,8 @@ struct usbi_os_backend {
        size_t transfer_priv_size;
 
        /* number of additional bytes for os_priv for each iso packet */
+       /* FIXME: linux can't use this any more. if other OS's cannot either,
+        * then remove this */
        size_t add_iso_packet_size;
 };
 
index 0b746a2..5746063 100644 (file)
@@ -46,6 +46,31 @@ struct linux_device_handle_priv {
        int fd;
 };
 
+enum reap_action {
+       NORMAL = 0,
+       /* submission failed after the first URB, so await cancellation/completion
+        * of all the others */
+       SUBMIT_FAILED,
+
+       /* cancelled by user or timeout */
+       CANCELLED,
+};
+
+struct linux_transfer_priv {
+       union {
+               struct usbfs_urb *urbs;
+               struct usbfs_urb **iso_urbs;
+       };
+
+       enum reap_action reap_action;
+       int num_urbs;
+       unsigned int awaiting_reap;
+       unsigned int awaiting_discard;
+
+       /* next iso packet in user-supplied transfer to be populated */
+       int iso_packet_offset;
+};
+
 static struct linux_device_priv *__device_priv(struct libusb_device *dev)
 {
        return (struct linux_device_priv *) dev->os_priv;
@@ -416,127 +441,433 @@ static void op_destroy_device(struct libusb_device *dev)
                free(nodepath);
 }
 
-static int submit_transfer(struct usbi_transfer *itransfer)
+static void free_iso_urbs(struct linux_transfer_priv *tpriv)
+{
+       int i;
+       for (i = 0; i < tpriv->num_urbs; i++) {
+               struct usbfs_urb *urb = tpriv->iso_urbs[i];
+               if (!urb)
+                       break;
+               free(urb);
+       }
+
+       free(tpriv->iso_urbs);
+}
+
+static int submit_bulk_transfer(struct usbi_transfer *itransfer,
+       unsigned char urb_type)
 {
        struct libusb_transfer *transfer =
                __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-       struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
        struct linux_device_handle_priv *dpriv =
                __device_handle_priv(transfer->dev_handle);
-       int to_be_transferred = transfer->length - itransfer->transferred;
+       struct usbfs_urb *urbs;
        int r;
+       int i;
+       size_t alloc_size;
+
+       /* usbfs places a 16kb limit on bulk URBs. we divide up larger requests
+        * into smaller units to meet such restriction, then fire off all the
+        * units at once. it would be simpler if we just fired one unit at a time,
+        * but there is a big performance gain through doing it this way. */
+       int num_urbs = (transfer->length / MAX_BULK_BUFFER_LENGTH) + 1;
+       usbi_dbg("need %d urbs for new transfer with length %d", num_urbs,
+               transfer->length);
+       alloc_size = num_urbs * sizeof(struct usbfs_urb);
+       urbs = malloc(alloc_size);
+       if (!urbs)
+               return -ENOMEM;
+       memset(urbs, 0, alloc_size);
+       tpriv->urbs = urbs;
+       tpriv->num_urbs = num_urbs;
+       tpriv->awaiting_discard = 0;
+       tpriv->awaiting_reap = 0;
+       tpriv->reap_action = NORMAL;
+
+       for (i = 0; i < num_urbs; i++) {
+               struct usbfs_urb *urb = &urbs[i];
+               urb->usercontext = itransfer;
+               urb->type = urb_type;
+               urb->endpoint = transfer->endpoint;
+               urb->buffer = transfer->buffer + (i * MAX_BULK_BUFFER_LENGTH);
+               if (i == num_urbs - 1)
+                       urb->buffer_length = transfer->length % MAX_BULK_BUFFER_LENGTH;
+               else
+                       urb->buffer_length = MAX_BULK_BUFFER_LENGTH;
+
+               r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
+               if (r < 0) {
+                       int j;
+                       usbi_err("submiturb failed error %d errno=%d", r, errno);
+       
+                       /* if the first URB submission fails, we can simply free up and
+                        * return failure immediately. */
+                       if (i == 0) {
+                               usbi_dbg("first URB failed, easy peasy");
+                               free(urbs);
+                               return r;
+                       }
+
+                       /* if it's not the first URB that failed, the situation is a bit
+                        * tricky. we must discard all previous URBs. there are
+                        * complications:
+                        *  - discarding is asynchronous - discarded urbs will be reaped
+                        *    later. the user must not have freed the transfer when the
+                        *    discarded URBs are reaped, otherwise libusb will be using
+                        *    freed memory.
+                        *  - the earlier URBs may have completed successfully and we do
+                        *    not want to throw away any data.
+                        * so, in this case we discard all the previous URBs BUT we report
+                        * that the transfer was submitted successfully. then later when
+                        * the final discard completes we can report error to the user.
+                        */
+                       tpriv->reap_action = SUBMIT_FAILED;
+                       for (j = 0; j < i; j++) {
+                               int tmp = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, &urbs[j]);
+                               if (tmp == 0)
+                                       tpriv->awaiting_discard++;
+                               else if (tmp == -EINVAL)
+                                       tpriv->awaiting_reap++;
+                               else
+                                       usbi_warn("unrecognised discard return %d", tmp);
+                       }
+
+                       usbi_dbg("reporting successful submission but waiting for %d "
+                               "discards and %d reaps before reporting error",
+                               tpriv->awaiting_discard, tpriv->awaiting_reap);
+                       return 0;
+               }
+       }
 
-       urb->buffer = transfer->buffer + itransfer->transferred;
-       if (urb->type == USBFS_URB_TYPE_ISO) {
-               /* FIXME: iso stuff needs reworking. if a big transfer is submitted,
-                * split it up into multiple URBs. */
-               urb->buffer_length = to_be_transferred;
-       } else {
-               urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH);
+       return 0;
+}
+
+static int submit_iso_transfer(struct usbi_transfer *itransfer)
+{
+       struct libusb_transfer *transfer =
+               __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       struct linux_device_handle_priv *dpriv =
+               __device_handle_priv(transfer->dev_handle);
+       struct usbfs_urb **urbs;
+       size_t alloc_size;
+       int num_packets = transfer->num_iso_packets;
+       int i;
+       int this_urb_len = 0;
+       int num_urbs = 1;
+       int packet_offset = 0;
+       unsigned int packet_len;
+       unsigned char *urb_buffer = transfer->buffer;
+
+       /* usbfs places a 32kb limit on iso URBs. we divide up larger requests
+        * into smaller units to meet such restriction, then fire off all the
+        * units at once. it would be simpler if we just fired one unit at a time,
+        * but there is a big performance gain through doing it this way. */
+
+       /* calculate how many URBs we need */
+       for (i = 0; i < num_packets; i++) {
+               int space_remaining = MAX_ISO_BUFFER_LENGTH - this_urb_len;
+               packet_len = transfer->iso_packet_desc[i].length;
+
+               if (packet_len > space_remaining) {
+                       num_urbs++;
+                       this_urb_len = packet_len;
+               } else {
+                       this_urb_len += packet_len;
+               }
        }
+       usbi_dbg("need %d 32k URBs for transfer", num_urbs);
+
+       alloc_size = num_urbs * sizeof(*urbs);
+       urbs = malloc(alloc_size);
+       if (!urbs)
+               return -ENOMEM;
+       memset(urbs, 0, alloc_size);
+
+       tpriv->iso_urbs = urbs;
+       tpriv->num_urbs = num_urbs;
+       tpriv->awaiting_discard = 0;
+       tpriv->awaiting_reap = 0;
+       tpriv->reap_action = NORMAL;
+       tpriv->iso_packet_offset = 0;
+
+       /* allocate + initialize each URB with the correct number of packets */
+       for (i = 0; i < num_urbs; i++) {
+               struct usbfs_urb *urb;
+               int space_remaining_in_urb = MAX_ISO_BUFFER_LENGTH;
+               int urb_packet_offset = 0;
+               unsigned char *urb_buffer_orig = urb_buffer;
+               int j;
+               int k;
+
+               /* swallow up all the packets we can fit into this URB */
+               while (packet_offset < transfer->num_iso_packets) {
+                       packet_len = transfer->iso_packet_desc[packet_offset].length;
+                       if (packet_len <= space_remaining_in_urb) {
+                               /* throw it in */
+                               urb_packet_offset++;
+                               packet_offset++;
+                               space_remaining_in_urb -= packet_len;
+                               urb_buffer += packet_len;
+                       } else {
+                               /* it can't fit, save it for the next URB */
+                               break;
+                       }
+               }
 
-       /* FIXME: for requests that we have to split into multiple URBs, we should
-        * submit all the URBs instantly: submit, submit, submit, reap, reap, reap
-        * rather than: submit, reap, submit, reap, submit, reap
-        * this will improve performance and fix bugs concerning behaviour when
-        * the user submits two similar multiple-urb requests */
-       usbi_dbg("transferring %d from %d bytes", urb->buffer_length,
-               to_be_transferred);
+               alloc_size = sizeof(*urb)
+                       + (urb_packet_offset * sizeof(struct usbfs_iso_packet_desc));
+               urb = malloc(alloc_size);
+               if (!urb) {
+                       free_iso_urbs(tpriv);
+                       return -ENOMEM;
+               }
+               memset(urb, 0, alloc_size);
+               urbs[i] = urb;
+
+               /* populate packet lengths */
+               for (j = 0, k = packet_offset - urb_packet_offset;
+                               k < packet_offset; k++, j++) {
+                       packet_len = transfer->iso_packet_desc[k].length;
+                       urb->iso_frame_desc[j].length = packet_len;
+               }
 
-       r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
-       if (r < 0)
-               usbi_err("submiturb failed error %d errno=%d", r, errno);
+               urb->usercontext = itransfer;
+               urb->type = USBFS_URB_TYPE_ISO;
+               /* FIXME: interface for non-ASAP data? */
+               urb->flags = USBFS_URB_ISO_ASAP;
+               urb->endpoint = transfer->endpoint;
+               urb->number_of_packets = urb_packet_offset;
+               urb->buffer = urb_buffer_orig;
+       }
 
-       return r;
+       /* submit URBs */
+       for (i = 0; i < num_urbs; i++) {
+               int r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]);
+               if (r < 0) {
+                       int j;
+                       usbi_err("submiturb failed error %d errno=%d", r, errno);
+
+                       /* if the first URB submission fails, we can simply free up and
+                        * return failure immediately. */
+                       if (i == 0) {
+                               usbi_dbg("first URB failed, easy peasy");
+                               free_iso_urbs(tpriv);
+                               return r;
+                       }
+
+                       /* if it's not the first URB that failed, the situation is a bit
+                        * tricky. we must discard all previous URBs. there are
+                        * complications:
+                        *  - discarding is asynchronous - discarded urbs will be reaped
+                        *    later. the user must not have freed the transfer when the
+                        *    discarded URBs are reaped, otherwise libusb will be using
+                        *    freed memory.
+                        *  - the earlier URBs may have completed successfully and we do
+                        *    not want to throw away any data.
+                        * so, in this case we discard all the previous URBs BUT we report
+                        * that the transfer was submitted successfully. then later when
+                        * the final discard completes we can report error to the user.
+                        */
+                       tpriv->reap_action = SUBMIT_FAILED;
+                       for (j = 0; j < i; j++) {
+                               int tmp = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urbs[j]);
+                               if (tmp == 0)
+                                       tpriv->awaiting_discard++;
+                               else if (tmp == -EINVAL)
+                                       tpriv->awaiting_reap++;
+                               else
+                                       usbi_warn("unrecognised discard return %d", tmp);
+                       }
+
+                       usbi_dbg("reporting successful submission but waiting for %d "
+                               "discards and %d reaps before reporting error",
+                               tpriv->awaiting_discard, tpriv->awaiting_reap);
+                       return 0;
+               }
+       }
+
+       return 0;
 }
 
-static void fill_iso_packet_descriptors(struct usbfs_urb *urb,
-       struct usbi_transfer *itransfer)
+static int submit_control_transfer(struct usbi_transfer *itransfer)
 {
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
        struct libusb_transfer *transfer =
                __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-       int i;
+       struct linux_device_handle_priv *dpriv =
+               __device_handle_priv(transfer->dev_handle);
+       struct usbfs_urb *urb;
+       int r;
+
+       if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH)
+               return -EINVAL;
+
+       urb = malloc(sizeof(struct usbfs_urb));
+       if (!urb)
+               return -ENOMEM;
+       memset(urb, 0, sizeof(struct usbfs_urb));
+       tpriv->urbs = urb;
+       tpriv->reap_action = NORMAL;
 
-       for (i = 0; i < transfer->num_iso_packets; i++) {
-               struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i];
-               struct libusb_iso_packet_descriptor *lib_desc =
-                       &transfer->iso_packet_desc[i];
-               urb_desc->length = lib_desc->length;
+       urb->usercontext = itransfer;
+       urb->type = USBFS_URB_TYPE_CONTROL;
+       urb->endpoint = transfer->endpoint;
+       urb->buffer = transfer->buffer;
+       urb->buffer_length = transfer->length;
+
+       r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
+       if (r < 0) {
+               usbi_err("submiturb failed error %d errno=%d", r, errno);
+               free(urb);
        }
+       return r;
 }
 
 static int op_submit_transfer(struct usbi_transfer *itransfer)
 {
-       struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
        struct libusb_transfer *transfer =
                __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
-       memset(urb, 0, sizeof(*urb));
-       urb->usercontext = itransfer;
        switch (transfer->endpoint_type) {
        case LIBUSB_ENDPOINT_TYPE_CONTROL:
-               urb->type = USBFS_URB_TYPE_CONTROL;
-               break;
+               return submit_control_transfer(itransfer);
        case LIBUSB_ENDPOINT_TYPE_BULK:
-               urb->type = USBFS_URB_TYPE_BULK;
-               break;
+               return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_BULK);
        case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
-               urb->type = USBFS_URB_TYPE_INTERRUPT;
-               break;
+               return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_INTERRUPT);
        case LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS:
-               urb->type = USBFS_URB_TYPE_ISO;
-               /* FIXME: interface for non-ASAP data? */
-               urb->flags = USBFS_URB_ISO_ASAP;
-               fill_iso_packet_descriptors(urb, itransfer);
-               urb->number_of_packets = transfer->num_iso_packets;
-               break;
+               return submit_iso_transfer(itransfer);
        default:
                usbi_err("unknown endpoint type %d", transfer->endpoint_type);
                return -EINVAL;
        }
+}
 
-       urb->endpoint = transfer->endpoint;
-       return submit_transfer(itransfer);
+static int cancel_control_transfer(struct usbi_transfer *itransfer)
+{
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       struct libusb_transfer *transfer =
+               __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct linux_device_handle_priv *dpriv =
+               __device_handle_priv(transfer->dev_handle);
+       int r;
+
+       tpriv->reap_action = CANCELLED;
+       r = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, tpriv->urbs);
+       if (r == -ENOENT) {
+               usbi_dbg("URB not found --> assuming ready to be reaped");
+               return 0;
+       } else if (r != 0) {
+               usbi_err("unrecognised DISCARD code %d", r);
+       }
+
+       return r;
 }
 
-static int op_cancel_transfer(struct usbi_transfer *itransfer)
+static void cancel_bulk_transfer(struct usbi_transfer *itransfer)
 {
-       struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
        struct libusb_transfer *transfer =
                __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
        struct linux_device_handle_priv *dpriv =
                __device_handle_priv(transfer->dev_handle);
+       int i;
 
-       return ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urb);
+       tpriv->reap_action = CANCELLED;
+       for (i = 0; i < tpriv->num_urbs; i++) {
+               int tmp = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, &tpriv->urbs[i]);
+               if (tmp == 0)
+                       tpriv->awaiting_discard++;
+               else if (tmp == -EINVAL)
+                       tpriv->awaiting_reap++;
+               else
+                       usbi_warn("unrecognised discard return %d", tmp);
+       }
 }
 
-static int reap_for_handle(struct libusb_device_handle *handle)
+static void cancel_iso_transfer(struct usbi_transfer *itransfer)
 {
-       struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
-       int r;
-       struct usbfs_urb *urb;
-       struct usbi_transfer *itransfer;
-       struct libusb_transfer *transfer;
-       int trf_requested;
-       int length;
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       struct libusb_transfer *transfer =
+               __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct linux_device_handle_priv *dpriv =
+               __device_handle_priv(transfer->dev_handle);
+       int i;
 
-       r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb);
-       if (r == -1 && errno == EAGAIN)
-               return r;
-       if (r < 0) {
-               usbi_err("reap failed error %d errno=%d", r, errno);
-               return r;
+       tpriv->reap_action = CANCELLED;
+       for (i = 0; i < tpriv->num_urbs; i++) {
+               int tmp = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, tpriv->iso_urbs[i]);
+               if (tmp == 0)
+                       tpriv->awaiting_discard++;
+               else if (tmp == -EINVAL)
+                       tpriv->awaiting_reap++;
+               else
+                       usbi_warn("unrecognised discard return %d", tmp);
        }
+}
 
-       itransfer = urb->usercontext;
-       transfer = __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+static int op_cancel_transfer(struct usbi_transfer *itransfer)
+{
+       struct libusb_transfer *transfer =
+               __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
-       usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status,
-               urb->actual_length);
-       list_del(&itransfer->list);
+       switch (transfer->endpoint_type) {
+       case LIBUSB_ENDPOINT_TYPE_CONTROL:
+               return cancel_control_transfer(itransfer);
+       case LIBUSB_ENDPOINT_TYPE_BULK:
+       case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
+               cancel_bulk_transfer(itransfer);
+               return 0;
+       case LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS:
+               cancel_iso_transfer(itransfer);
+               return 0;
+       default:
+               usbi_err("unknown endpoint type %d", transfer->endpoint_type);
+               return -EINVAL;
+       }
+}
 
-       if (urb->status == -2) {
-               usbi_handle_transfer_cancellation(itransfer);
+static int handle_bulk_completion(struct usbi_transfer *itransfer,
+       struct usbfs_urb *urb)
+{
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       int num_urbs = tpriv->num_urbs;
+       int urb_idx = urb - tpriv->urbs;
+
+       usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status,
+               urb_idx + 1, num_urbs);
+
+       if (urb->status == 0)
+               itransfer->transferred += urb->actual_length;
+
+       if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */
+               if (urb->status == -ENOENT) {
+                       usbi_dbg("CANCEL: detected a cancelled URB");
+                       if (tpriv->awaiting_discard == 0)
+                               usbi_err("CANCEL: cancelled URB but not awaiting discards?");
+                       else
+                               tpriv->awaiting_discard--;
+               } else if (urb->status == 0) {
+                       usbi_dbg("CANCEL: detected a completed URB");
+                       if (tpriv->awaiting_reap == 0)
+                               usbi_err("CANCEL: completed URB not awaiting reap?");
+                       else
+                               tpriv->awaiting_reap--;
+               } else {
+                       usbi_warn("unhandled CANCEL urb status %d", urb->status);
+               }
+
+               if (tpriv->awaiting_reap == 0 && tpriv->awaiting_discard == 0) {
+                       usbi_dbg("CANCEL: last URB handled, reporting");
+                       free(tpriv->urbs);
+                       if (tpriv->reap_action == CANCELLED)
+                               usbi_handle_transfer_cancellation(itransfer);
+                       else
+                               usbi_handle_transfer_completion(itransfer,
+                                       LIBUSB_TRANSFER_ERROR);
+               }
                return 0;
        }
 
@@ -544,46 +875,162 @@ static int reap_for_handle(struct libusb_device_handle *handle)
        if (urb->status != 0)
                usbi_warn("unrecognised urb status %d", urb->status);
 
-       /* copy isochronous packet results back */
-       if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS) {
-               int i;
+       /* if we're the last urb or we got less data than requested then we're
+        * done */
+       if (urb_idx == num_urbs - 1)
+               usbi_dbg("last URB in transfer --> complete!");
+       else if (urb->actual_length < urb->buffer_length)
+               usbi_dbg("short transfer %d/%d --> complete!",
+                       urb->actual_length, urb->buffer_length);
+       else
+               return 0;
+
+       free(tpriv->urbs);
+       usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
+       return 0;
+}
+
+static int handle_iso_completion(struct usbi_transfer *itransfer,
+       struct usbfs_urb *urb)
+{
+       struct libusb_transfer *transfer =
+               __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       int num_urbs = tpriv->num_urbs;
+       int urb_idx = 0;
+       int i;
+
+       for (i = 0; i < num_urbs; i++) {
+               if (urb == tpriv->iso_urbs[i]) {
+                       urb_idx = i + 1;
+                       break;
+               }
+       }
+       if (urb_idx == 0) {
+               usbi_err("could not locate urb!");
+               return -EIO;
+       }
+
+       usbi_dbg("handling completion status %d of iso urb %d/%d", urb->status,
+               urb_idx, num_urbs);
+
+       if (urb->status == 0) {
+               /* copy isochronous results back in */
+
                for (i = 0; i < urb->number_of_packets; i++) {
                        struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i];
                        struct libusb_iso_packet_descriptor *lib_desc =
-                               &transfer->iso_packet_desc[i];
+                               &transfer->iso_packet_desc[tpriv->iso_packet_offset++];
                        lib_desc->status = urb_desc->status;
                        lib_desc->actual_length = urb_desc->actual_length;
                }
        }
 
-       /* determine how much data was asked for */
-       length = transfer->length;
-       if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_CONTROL)
-               length -= LIBUSB_CONTROL_SETUP_SIZE;
-       trf_requested = MIN(length - itransfer->transferred,
-               MAX_URB_BUFFER_LENGTH);
+       if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */
+               if (urb->status == -ENOENT) {
+                       usbi_dbg("CANCEL: detected a cancelled URB");
+                       if (tpriv->awaiting_discard == 0)
+                               usbi_err("CANCEL: cancelled URB but not awaiting discards?");
+                       else
+                               tpriv->awaiting_discard--;
+               } else if (urb->status == 0) {
+                       usbi_dbg("CANCEL: detected a completed URB");
+                       if (tpriv->awaiting_reap == 0)
+                               usbi_err("CANCEL: completed URB not awaiting reap?");
+                       else
+                               tpriv->awaiting_reap--;
+               } else {
+                       usbi_warn("unhandled CANCEL urb status %d", urb->status);
+               }
+
+               if (tpriv->awaiting_reap == 0 && tpriv->awaiting_discard == 0) {
+                       usbi_dbg("CANCEL: last URB handled, reporting");
+                       free_iso_urbs(tpriv);
+                       if (tpriv->reap_action == CANCELLED)
+                               usbi_handle_transfer_cancellation(itransfer);
+                       else
+                               usbi_handle_transfer_completion(itransfer,
+                                       LIBUSB_TRANSFER_ERROR);
+               }
+               return 0;
+       }
 
-       itransfer->transferred += urb->actual_length;
+       /* FIXME: research what other status codes may exist */
+       if (urb->status != 0)
+               usbi_warn("unrecognised urb status %d", urb->status);
 
-       /* if we were provided less data than requested, then our transfer is
+       /* if we're the last urb or we got less data than requested then we're
         * done */
-       if (urb->actual_length < trf_requested) {
-               usbi_dbg("less data than requested (%d/%d) --> all done",
-                       urb->actual_length, trf_requested);
+       if (urb_idx == num_urbs) {
+               usbi_dbg("last URB in transfer --> complete!");
+               free_iso_urbs(tpriv);
                usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
-               return 0;
        }
 
-       /* if we've transferred all data, we're done */
-       if (itransfer->transferred == length) {
-               usbi_dbg("transfer complete --> all done");
-               usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
+       return 0;
+}
+
+static int handle_control_completion(struct usbi_transfer *itransfer,
+       struct usbfs_urb *urb)
+{
+       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       usbi_dbg("handling completion status %d", urb->status);
+
+       if (urb->status == 0)
+               itransfer->transferred += urb->actual_length;
+
+       if (tpriv->reap_action == CANCELLED) {
+               if (urb->status != 0 && urb->status != -ENOENT)
+                       usbi_warn("cancel: unrecognised urb status %d", urb->status);
+               free(tpriv->urbs);
+               usbi_handle_transfer_cancellation(itransfer);
                return 0;
        }
 
-       /* otherwise, we have more data to transfer */
-       usbi_dbg("more data to transfer...");
-       return submit_transfer(itransfer);
+       /* FIXME: research what other status codes may exist */
+       if (urb->status != 0)
+               usbi_warn("unrecognised urb status %d", urb->status);
+
+       itransfer->transferred = urb->actual_length;
+       free(tpriv->urbs);
+       usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
+       return 0;
+}
+
+static int reap_for_handle(struct libusb_device_handle *handle)
+{
+       struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
+       int r;
+       struct usbfs_urb *urb;
+       struct usbi_transfer *itransfer;
+       struct libusb_transfer *transfer;
+
+       r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb);
+       if (r == -1 && errno == EAGAIN)
+               return r;
+       if (r < 0) {
+               usbi_err("reap failed error %d errno=%d", r, errno);
+               return r;
+       }
+
+       itransfer = urb->usercontext;
+       transfer = __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+
+       usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status,
+               urb->actual_length);
+
+       switch (transfer->endpoint_type) {
+       case LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS:
+               return handle_iso_completion(itransfer, urb);
+       case LIBUSB_ENDPOINT_TYPE_BULK:
+       case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
+               return handle_bulk_completion(itransfer, urb);
+       case LIBUSB_ENDPOINT_TYPE_CONTROL:
+               return handle_control_completion(itransfer, urb);
+       default:
+               usbi_err("unrecognised endpoint type %x", transfer->endpoint_type);
+               return -EINVAL;
+       }
 }
 
 static int op_handle_events(fd_set *readfds, fd_set *writefds)
@@ -626,7 +1073,7 @@ const struct usbi_os_backend linux_usbfs_backend = {
 
        .device_priv_size = sizeof(struct linux_device_priv),
        .device_handle_priv_size = sizeof(struct linux_device_handle_priv),
-       .transfer_priv_size = sizeof(struct usbfs_urb),
-       .add_iso_packet_size = sizeof(struct usbfs_iso_packet_desc),
+       .transfer_priv_size = sizeof(struct linux_transfer_priv),
+       .add_iso_packet_size = 0,
 };
 
index e96a16b..f7278ee 100644 (file)
@@ -75,7 +75,9 @@ struct usbfs_iso_packet_desc {
        unsigned int status;
 };
 
-#define MAX_URB_BUFFER_LENGTH          16384
+#define MAX_ISO_BUFFER_LENGTH          32768
+#define MAX_BULK_BUFFER_LENGTH         16384
+#define MAX_CTRL_BUFFER_LENGTH         4096
 
 struct usbfs_urb {
        unsigned char type;