From hdegoede at redhat.com Fri Jun 29 12:10:25 2012 From: hdegoede at redhat.com (Hans de Goede) Date: Fri, 29 Jun 2012 18:10:25 +0200 Subject: [RFC] linux_usbfs: Support kernel capabilities and scatter-gather APIs Message-ID: <1340986227-29209-1-git-send-email-hdegoede@redhat.com> I've been working on some kernel usbfs changes to make live easier for libusbx, consisting of 2 API additions: 1) An API to query the kernel if it supports things like the bulk-continuation flag and other features for which we currently abuse the kernel version to find out if they are supported 2) Support for using scatter-gather lists for large bulk transfers, this does not really change the userspace API, but we can take advantage of this by simply submitting large transfers in one go, rather then splitting it up, with all difficulties that creates for us The patches in this patch-set add support for these 2 new features. Note this is strictly a RFC, do not add these patches to the git tree yet, please! The reason for this is that although the upstream kernel reception of the patches has been positive. the patches are not in the upstream kernel yet, so the API may still change!! Regards, Hans From hdegoede at redhat.com Fri Jun 29 12:10:26 2012 From: hdegoede at redhat.com (Hans de Goede) Date: Fri, 29 Jun 2012 18:10:26 +0200 Subject: [PATCH 1/2] linux_usbfs: Add support for the new get_capabilities ioctl In-Reply-To: <1340986227-29209-1-git-send-email-hdegoede@redhat.com> References: <1340986227-29209-1-git-send-email-hdegoede@redhat.com> Message-ID: <1340986227-29209-2-git-send-email-hdegoede@redhat.com> There were a few (new) usbdevfs capabilities which libusb could not discover in any other way then checking the kernel version. There are 3 problems with this: 1) It is just not very pretty 2) Given the tendency of enterprise distros to backport stuff it is not reliable 3) Some of these features turn out to not work with certain host controllers, making depending on them based on the kernel version not a good idea Therefor a new USBDEVFS_GET_CAPABILITIES ioctl has been added to the kernel to offer a better way to find out a device's capabilities (technically the capabilities of the host controller to which the device is attached, but that does not matter). Signed-off-by: Hans de Goede --- libusb/os/linux_usbfs.c | 24 ++++++++++++++++++++---- libusb/os/linux_usbfs.h | 10 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index a01fff8..239c68e 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -116,6 +116,7 @@ struct linux_device_priv { struct linux_device_handle_priv { int fd; + __u32 caps; }; enum reap_action { @@ -1284,6 +1285,7 @@ static int op_open(struct libusb_device_handle *handle) { struct linux_device_handle_priv *hpriv = _device_handle_priv(handle); char filename[PATH_MAX]; + int r; _get_usbfs_path(handle->dev, filename); usbi_dbg("opening %s", filename); @@ -1306,6 +1308,20 @@ static int op_open(struct libusb_device_handle *handle) } } + r = ioctl(hpriv->fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); + if (r < 0) { + if (errno == ENOTTY) + usbi_dbg("%s: getcap not available", filename); + else + usbi_err(HANDLE_CTX(handle), + "%s: getcap failed (%d)", filename, errno); + hpriv->caps = 0; + if (supports_flag_zero_packet) + hpriv->caps |= USBFS_CAP_ZERO_PACKET; + if (supports_flag_bulk_continuation) + hpriv->caps |= USBFS_CAP_BULK_CONTINUATION; + } + return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT); } @@ -1659,8 +1675,8 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, if (tpriv->urbs) return LIBUSB_ERROR_BUSY; - if (is_out && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET && - !supports_flag_zero_packet) + if (is_out && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) && + !(dpriv->caps & USBFS_CAP_ZERO_PACKET)) return LIBUSB_ERROR_NOT_SUPPORTED; /* usbfs places a 16kb limit on bulk URBs. we divide up larger requests @@ -1695,7 +1711,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, urb->type = urb_type; urb->endpoint = transfer->endpoint; urb->buffer = transfer->buffer + (i * MAX_BULK_BUFFER_LENGTH); - if (supports_flag_bulk_continuation && !is_out) + if ((dpriv->caps & USBFS_CAP_BULK_CONTINUATION) && !is_out) urb->flags = USBFS_URB_SHORT_NOT_OK; if (i == num_urbs - 1 && last_urb_partial) urb->buffer_length = transfer->length % MAX_BULK_BUFFER_LENGTH; @@ -1704,7 +1720,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, else urb->buffer_length = MAX_BULK_BUFFER_LENGTH; - if (i > 0 && supports_flag_bulk_continuation) + if (i > 0 && (dpriv->caps & USBFS_CAP_BULK_CONTINUATION)) urb->flags |= USBFS_URB_BULK_CONTINUATION; /* we have already checked that the flag is supported */ diff --git a/libusb/os/linux_usbfs.h b/libusb/os/linux_usbfs.h index 3a80261..593e736 100644 --- a/libusb/os/linux_usbfs.h +++ b/libusb/os/linux_usbfs.h @@ -21,6 +21,8 @@ #ifndef LIBUSB_USBFS_H #define LIBUSB_USBFS_H +#include + #define SYSFS_DEVICE_PATH "/sys/bus/usb/devices" struct usbfs_ctrltransfer { @@ -116,6 +118,11 @@ struct usbfs_hub_portinfo { unsigned char port[127]; /* port to device num mapping */ }; +#define USBFS_CAP_ZERO_PACKET 0x01 +#define USBFS_CAP_BULK_CONTINUATION 0x02 +#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04 +#define USBFS_CAP_BULK_SCATTER_GATHER 0x08 + #define IOCTL_USBFS_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) #define IOCTL_USBFS_BULK _IOWR('U', 2, struct usbfs_bulktransfer) #define IOCTL_USBFS_RESETEP _IOR('U', 3, unsigned int) @@ -135,5 +142,8 @@ struct usbfs_hub_portinfo { #define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) #define IOCTL_USBFS_DISCONNECT _IO('U', 22) #define IOCTL_USBFS_CONNECT _IO('U', 23) +#define IOCTL_USBFS_CLAIM_PORT _IOR('U', 24, unsigned int) +#define IOCTL_USBFS_RELEASE_PORT _IOR('U', 25, unsigned int) +#define IOCTL_USBFS_GET_CAPABILITIES _IOR('U', 26, __u32) #endif -- 1.7.10.4 From hdegoede at redhat.com Fri Jun 29 12:10:27 2012 From: hdegoede at redhat.com (Hans de Goede) Date: Fri, 29 Jun 2012 18:10:27 +0200 Subject: [PATCH 2/2] linux_usbfs: Avoid unnecessary splitting of bulk transfers In-Reply-To: <1340986227-29209-1-git-send-email-hdegoede@redhat.com> References: <1340986227-29209-1-git-send-email-hdegoede@redhat.com> Message-ID: <1340986227-29209-3-git-send-email-hdegoede@redhat.com> With the latest kernels it is no longer needed to always split large bulk transfers into multiple urbs. This patch takes advantage of this by not splitting when not necessary. Note that the non-split code path is in essence using the old split code path with an urb count which is always 1. This leads to more sane handling of large transfers with recent kernels, although our splitting code is well tested, not splitting at all still is a lot better :) When used with a recent kernel, this also fixes the problems, on XHCI attached devices, when a large bulk-in transfer ends with a short read in an urb other then the last urb. For details on this see the mailinglist thread titled "usbdevfs: BULK_CONTINUATION flag does not work with XHCI controller". Signed-off-by: Hans de Goede --- libusb/os/linux_usbfs.c | 69 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index 239c68e..3894554 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -1668,6 +1668,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, struct usbfs_urb *urbs; int is_out = (transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT; + int bulk_buffer_len, use_bulk_continuation; int r; int i; size_t alloc_size; @@ -1679,16 +1680,54 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, !(dpriv->caps & USBFS_CAP_ZERO_PACKET)) return LIBUSB_ERROR_NOT_SUPPORTED; - /* 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; + /* + * Older versions of usbfs place a 16kb limit on bulk URBs. We work + * around this by splitting large transfers into 16k blocks, and then + * submit all urbs at once. it would be simpler to submit one urb at + * a time, but there is a big performance gain doing it this way. + * + * Newer versions lift the 16k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbritary large transfers can still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + * + * The kernel solves this problem by splitting the transfer into + * blocks itself when the host-controller is scatter-gather capable + * (USBFS_CAP_BULK_SCATTER_GATHER), which most controllers are. + * + * Last, there is the issue of short-transfers when splitting, for + * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION + * is needed, but this is not always available. + */ + if (dpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) { + /* Good! Just submit everything in one go */ + bulk_buffer_len = transfer->length; + use_bulk_continuation = 0; + } else if (dpriv->caps & USBFS_CAP_BULK_CONTINUATION) { + /* Split the transfers and use bulk-continuation to + avoid issues with short-transfers */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 1; + } else if (dpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) { + /* Don't split, assume the kernel can alloc the buffer + (otherwise the submit will fail with -ENOMEM) */ + bulk_buffer_len = transfer->length; + use_bulk_continuation = 0; + } else { + /* Bad, splitting without bulk-continuation, short transfers + which end before the last urb will not work reliable! */ + /* Note we don't warn here as this is "normal" on kernels < + 2.6.32 and not a problem for most applications */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 0; + } + + int num_urbs = transfer->length / bulk_buffer_len; int last_urb_partial = 0; if (transfer->length == 0) { num_urbs = 1; - } else if ((transfer->length % MAX_BULK_BUFFER_LENGTH) > 0) { + } else if ((transfer->length % bulk_buffer_len) > 0) { last_urb_partial = 1; num_urbs++; } @@ -1710,17 +1749,17 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, urb->usercontext = itransfer; urb->type = urb_type; urb->endpoint = transfer->endpoint; - urb->buffer = transfer->buffer + (i * MAX_BULK_BUFFER_LENGTH); - if ((dpriv->caps & USBFS_CAP_BULK_CONTINUATION) && !is_out) + urb->buffer = transfer->buffer + (i * bulk_buffer_len); + if (use_bulk_continuation && !is_out) urb->flags = USBFS_URB_SHORT_NOT_OK; if (i == num_urbs - 1 && last_urb_partial) - urb->buffer_length = transfer->length % MAX_BULK_BUFFER_LENGTH; + urb->buffer_length = transfer->length % bulk_buffer_len; else if (transfer->length == 0) urb->buffer_length = 0; else - urb->buffer_length = MAX_BULK_BUFFER_LENGTH; + urb->buffer_length = bulk_buffer_len; - if (i > 0 && (dpriv->caps & USBFS_CAP_BULK_CONTINUATION)) + if (i > 0 && use_bulk_continuation) urb->flags |= USBFS_URB_BULK_CONTINUATION; /* we have already checked that the flag is supported */ @@ -1807,7 +1846,13 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) /* 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. */ + * but there is a big performance gain through doing it this way. + * + * Newer kernels lift the 32k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbritary large transfers is still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + */ /* calculate how many URBs we need */ for (i = 0; i < num_packets; i++) { -- 1.7.10.4