Handle hot-unplugging
authorDaniel Drake <dsd@gentoo.org>
Sun, 11 May 2008 19:31:58 +0000 (20:31 +0100)
committerDaniel Drake <dsd@gentoo.org>
Sun, 11 May 2008 19:47:27 +0000 (20:47 +0100)
This involved moving from select() to poll() because there is no way to
distinguish usbfs's POLLERR condition with select().

TODO
examples/dpfp.c
libusb/core.c
libusb/io.c
libusb/libusb.h
libusb/libusbi.h
libusb/os/linux_usbfs.c
libusb/sync.c

diff --git a/TODO b/TODO
index 887976f..ba66de7 100644 (file)
--- a/TODO
+++ b/TODO
@@ -3,7 +3,6 @@ for 1.0
 review functionality missing over 0.1
 serialization of handle_events
 internal docs for OS porters
-hot-unplugging
 
 1.0 API style/naming points to reconsider
 =========================================
@@ -13,6 +12,5 @@ for 1.1 or future
 ==================
 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 a64abdc..7808184 100644 (file)
@@ -283,6 +283,7 @@ static void cb_irq(struct libusb_transfer *transfer)
        if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
                fprintf(stderr, "irq transfer status %d?\n", transfer->status);
                do_exit = 2;
+               libusb_free_transfer(transfer);
                irq_transfer = NULL;
                return;
        }
@@ -319,6 +320,7 @@ static void cb_img(struct libusb_transfer *transfer)
        if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
                fprintf(stderr, "img transfer status %d?\n", transfer->status);
                do_exit = 2;
+               libusb_free_transfer(transfer);
                img_transfer = NULL;
                return;
        }
@@ -431,7 +433,7 @@ int main(void)
 
        r = libusb_claim_interface(devh, 0);
        if (r < 0) {
-               fprintf(stderr, "usb_claim_interface error %d %s\n", r, strerror(-r));
+               fprintf(stderr, "usb_claim_interface error %d\n", r);
                goto out;
        }
        printf("claimed interface\n");
@@ -468,14 +470,18 @@ int main(void)
        }
 
        printf("shutting down...\n");
+       
+       if (irq_transfer) {
+               r = libusb_cancel_transfer(irq_transfer);
+               if (r < 0)
+                       goto out_deinit;
+       }
 
-       r = libusb_cancel_transfer(irq_transfer);
-       if (r < 0)
-               goto out_deinit;
-
-       r = libusb_cancel_transfer(img_transfer);
-       if (r < 0)
-               goto out_deinit;
+       if (img_transfer) {
+               r = libusb_cancel_transfer(img_transfer);
+               if (r < 0)
+                       goto out_deinit;
+       }
        
        while (irq_transfer || img_transfer)
                if (libusb_handle_events() < 0)
index e36989f..83f0cdb 100644 (file)
@@ -140,6 +140,13 @@ pthread_mutex_t usbi_open_devs_lock = PTHREAD_MUTEX_INITIALIZER;
  * libusb-1.0 lacks functionality for providing notifications of when devices
  * are added or removed. This functionality is planned to be implemented
  * for libusb-1.1.
+ *
+ * That said, there is basic disconnection handling for open device handles:
+ *  - If there are ongoing transfers, libusb's handle_events loop will detect
+ *    disconnections and complete ongoing transfers with the
+ *    LIBUSB_TRANSFER_NO_DEVICE status code.
+ *  - Many functions such as libusb_set_configuration() return the special
+ *    LIBUSB_ERROR_NO_DEVICE error code when the device has been disconnected.
  */
 
 /**
@@ -734,6 +741,7 @@ API_EXPORTED libusb_device *libusb_get_device(libusb_device_handle *dev_handle)
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist
  * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_set_configuration(libusb_device_handle *dev,
@@ -764,6 +772,7 @@ API_EXPORTED int libusb_set_configuration(libusb_device_handle *dev,
  * \returns LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist
  * \returns LIBUSB_ERROR_BUSY if another program or driver has claimed the
  * interface
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns a LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_claim_interface(libusb_device_handle *dev,
@@ -798,8 +807,10 @@ out:
  * \param dev a device handle
  * \param interface_number the <tt>bInterfaceNumber</tt> of the
  * previously-claimed interface
- * \returns 0 on success, or a LIBUSB_ERROR code on failure.
- * LIBUSB_ERROR_NOT_FOUND indicates that the interface was not claimed.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_release_interface(libusb_device_handle *dev,
        int interface_number)
@@ -843,6 +854,7 @@ out:
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed, or the
  * requested alternate setting does not exist
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_set_interface_alt_setting(libusb_device_handle *dev,
@@ -876,6 +888,7 @@ API_EXPORTED int libusb_set_interface_alt_setting(libusb_device_handle *dev,
  * \param endpoint the endpoint to clear halt status
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_clear_halt(libusb_device_handle *dev,
@@ -900,7 +913,8 @@ API_EXPORTED int libusb_clear_halt(libusb_device_handle *dev,
  *
  * \param dev a handle of the device to reset
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if re-enumeration is required
+ * \returns LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the
+ * device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_reset_device(libusb_device_handle *dev)
@@ -918,7 +932,8 @@ API_EXPORTED int libusb_reset_device(libusb_device_handle *dev)
  * \param interface the interface to check
  * \returns 0 if no kernel driver is active
  * \returns 1 if a kernel driver is active
- * \returns LIBUSB_ERROR code on failure
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_detach_kernel_driver()
  */
 API_EXPORTED int libusb_kernel_driver_active(libusb_device_handle *dev,
@@ -940,6 +955,7 @@ API_EXPORTED int libusb_kernel_driver_active(libusb_device_handle *dev,
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active
  * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_kernel_driver_active()
  */
index 133f849..f91b14c 100644 (file)
@@ -26,7 +26,6 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/select.h>
 #include <sys/time.h>
 #include <time.h>
 #include <unistd.h>
@@ -715,7 +714,9 @@ API_EXPORTED void libusb_free_transfer(struct libusb_transfer *transfer)
  * submitted but has not yet completed.
  *
  * \param transfer the transfer to submit
- * \returns 0 on success, or a LIBUSB_ERROR code on failure
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer)
 {
@@ -751,7 +752,7 @@ API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer)
  *
  * \param transfer the transfer to cancel
  * \returns 0 on success
- * \returns non-zero on error
+ * \returns a LIBUSB_ERROR code on failure
  */
 API_EXPORTED int libusb_cancel_transfer(struct libusb_transfer *transfer)
 {
@@ -873,15 +874,13 @@ out:
 static int handle_events(struct timeval *tv)
 {
        int r;
-       int maxfd = 0;
-       fd_set readfds, writefds;
-       fd_set *_readfds = NULL;
-       fd_set *_writefds = NULL;
        struct usbi_pollfd *ipollfd;
-       int have_readfds = 0;
-       int have_writefds = 0;
-       struct timeval select_timeout;
        struct timeval timeout;
+       struct timeval poll_timeout;
+       nfds_t nfds = 0;
+       struct pollfd *fds;
+       int i = -1;
+       int timeout_ms;
 
        r = libusb_get_next_timeout(&timeout);
        if (r) {
@@ -891,53 +890,46 @@ static int handle_events(struct timeval *tv)
 
                /* choose the smallest of next URB timeout or user specified timeout */
                if (timercmp(&timeout, tv, <))
-                       select_timeout = timeout;
+                       poll_timeout = timeout;
                else
-                       select_timeout = *tv;
+                       poll_timeout = *tv;
        } else {
-               select_timeout = *tv;
+               poll_timeout = *tv;
        }
 
-       FD_ZERO(&readfds);
-       FD_ZERO(&writefds);
        pthread_mutex_lock(&pollfds_lock);
+       list_for_each_entry(ipollfd, &pollfds, list)
+               nfds++;
+
+       /* TODO: malloc when number of fd's changes, not on every poll */
+       fds = malloc(sizeof(*fds) * nfds);
+       if (!fds)
+               return LIBUSB_ERROR_NO_MEM;
+
        list_for_each_entry(ipollfd, &pollfds, list) {
                struct libusb_pollfd *pollfd = &ipollfd->pollfd;
                int fd = pollfd->fd;
-               if (pollfd->events & POLLIN) {
-                       have_readfds = 1;
-                       FD_SET(fd, &readfds);
-               }
-               if (pollfd->events & POLLOUT) {
-                       have_writefds = 1;
-                       FD_SET(fd, &writefds);
-               }
-               if (fd > maxfd)
-                       maxfd = fd;
+               i++;
+               fds[i].fd = fd;
+               fds[i].events = pollfd->events;
+               fds[i].revents = 0;
        }
        pthread_mutex_unlock(&pollfds_lock);
 
-       if (have_readfds)
-               _readfds = &readfds;
-       if (have_writefds)
-               _writefds = &writefds;
-
-       usbi_dbg("select() with timeout in %d.%06ds", select_timeout.tv_sec,
-               select_timeout.tv_usec);
-       r = select(maxfd + 1, _readfds, _writefds, NULL, &select_timeout);
-       usbi_dbg("select() returned %d with %d.%06ds remaining",
-               r, select_timeout.tv_sec, select_timeout.tv_usec);
+       timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
+       usbi_dbg("poll() %d fds with timeout in %dms", nfds, timeout_ms);
+       r = poll(fds, nfds, timeout_ms);
+       usbi_dbg("poll() returned %d", r);
        if (r == 0) {
-               *tv = select_timeout;
                return handle_timeouts();
        } else if (r == -1 && errno == EINTR) {
-               return 0;
+               return LIBUSB_ERROR_INTERRUPTED;
        } else if (r < 0) {
-               usbi_err("select failed %d err=%d\n", r, errno);
+               usbi_err("poll failed %d err=%d\n", r, errno);
                return LIBUSB_ERROR_IO;
        }
 
-       r = usbi_backend->handle_events(_readfds, _writefds);
+       r = usbi_backend->handle_events(fds, nfds, r);
        if (r)
                usbi_err("backend handle_events failed with error %d", r);
 
@@ -1113,7 +1105,7 @@ void usbi_remove_pollfd(int fd)
                }
 
        if (!found) {
-               usbi_err("couldn't find fd %d to remove", fd);
+               usbi_dbg("couldn't find fd %d to remove", fd);
                pthread_mutex_unlock(&pollfds_lock);
                return;
        }
@@ -1159,3 +1151,43 @@ out:
        return (const struct libusb_pollfd **) ret;
 }
 
+void usbi_handle_disconnect(struct libusb_device_handle *handle)
+{
+       struct usbi_transfer *cur;
+       struct usbi_transfer *to_cancel;
+
+       usbi_dbg("device %d.%d",
+               handle->dev->bus_number, handle->dev->device_address);
+
+       /* terminate all pending transfers with the LIBUSB_TRANSFER_NO_DEVICE
+        * status code.
+        * 
+        * this is a bit tricky because:
+        * 1. we can't do transfer completion while holding flying_transfers_lock
+        * 2. the transfers list can change underneath us - if we were to build a
+        *    list of transfers to complete (while holding look), the situation
+        *    might be different by the time we come to free them
+        *
+        * so we resort to a loop-based approach as below
+        * FIXME: is this still potentially racy?
+        */
+
+       while (1) {
+               pthread_mutex_lock(&flying_transfers_lock);
+               to_cancel = NULL;
+               list_for_each_entry(cur, &flying_transfers, list)
+                       if (__USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == handle) {
+                               to_cancel = cur;
+                               break;
+                       }
+               pthread_mutex_unlock(&flying_transfers_lock);
+
+               if (!to_cancel)
+                       break;
+
+               usbi_backend->clear_transfer_priv(to_cancel);
+               usbi_handle_transfer_completion(to_cancel, LIBUSB_TRANSFER_NO_DEVICE);
+       }
+
+}
+
index 19f7d81..2cd6633 100644 (file)
@@ -581,26 +581,32 @@ enum libusb_error {
        /** Access denied (insufficient permissions) */
        LIBUSB_ERROR_ACCESS = -3,
 
+       /** No such device (it may have been disconnected) */
+       LIBUSB_ERROR_NO_DEVICE = -4,
+
        /** Entity not found */
-       LIBUSB_ERROR_NOT_FOUND = -4,
+       LIBUSB_ERROR_NOT_FOUND = -5,
 
        /** Resource busy */
-       LIBUSB_ERROR_BUSY = -5,
+       LIBUSB_ERROR_BUSY = -6,
 
        /** Operation timed out */
-       LIBUSB_ERROR_TIMEOUT = -6,
+       LIBUSB_ERROR_TIMEOUT = -7,
 
        /** Pipe error */
-       LIBUSB_ERROR_PIPE = -7,
+       LIBUSB_ERROR_PIPE = -8,
+
+       /** System call interrupted (perhaps due to signal) */
+       LIBUSB_ERROR_INTERRUPTED = -9,
 
        /** Insufficient memory */
-       LIBUSB_ERROR_NO_MEM = -8,
+       LIBUSB_ERROR_NO_MEM = -10,
 
        /** Operation not supported or unimplemented on this platform */
-       LIBUSB_ERROR_NOT_SUPPORTED = -9,
+       LIBUSB_ERROR_NOT_SUPPORTED = -11,
 
        /** Other error */
-       LIBUSB_ERROR_OTHER = -10,
+       LIBUSB_ERROR_OTHER = -12,
 };
 
 /** \ingroup asyncio
@@ -622,6 +628,9 @@ enum libusb_transfer_status {
        /** For bulk/interrupt endpoints: halt condition detected (endpoint
         * stalled). For control endpoints: control request not supported. */
        LIBUSB_TRANSFER_STALL,
+
+       /** Device was disconnected */
+       LIBUSB_TRANSFER_NO_DEVICE,
 };
 
 /** \ingroup asyncio
index 414da25..87b2953 100644 (file)
@@ -23,9 +23,9 @@
 
 #include <config.h>
 
+#include <poll.h>
 #include <pthread.h>
 #include <stddef.h>
-#include <sys/select.h>
 #include <time.h>
 
 #include <libusb.h>
@@ -211,6 +211,7 @@ void usbi_io_init(void);
 struct libusb_device *usbi_alloc_device(unsigned long session_id);
 struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id);
 int usbi_sanitize_device(struct libusb_device *dev);
+void usbi_handle_disconnect(struct libusb_device_handle *handle);
 
 void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
        enum libusb_transfer_status status);
@@ -288,8 +289,9 @@ struct usbi_os_backend {
 
        int (*submit_transfer)(struct usbi_transfer *itransfer);
        int (*cancel_transfer)(struct usbi_transfer *itransfer);
+       void (*clear_transfer_priv)(struct usbi_transfer *itransfer);
 
-       int (*handle_events)(fd_set *readfds, fd_set *writefds);
+       int (*handle_events)(struct pollfd *fds, nfds_t nfds, int num_ready);
 
        /* number of bytes to reserve for libusb_device.os_priv */
        size_t device_priv_size;
index e69578c..e3aa7a9 100644 (file)
@@ -29,7 +29,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
-#include <sys/select.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -482,6 +481,9 @@ static int usbfs_get_active_config(struct libusb_device *dev, int fd)
 
        r = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl);
        if (r < 0) {
+               if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
+
                usbi_err("get_configuration failed ret=%d errno=%d", r, errno);
                return LIBUSB_ERROR_IO;
        }
@@ -869,6 +871,8 @@ static int op_open(struct libusb_device_handle *handle)
                                "libusb requires write access to USB device nodes.\n",
                                filename);
                        return LIBUSB_ERROR_ACCESS;
+               } else if (errno == ENOENT) {
+                       return LIBUSB_ERROR_NO_DEVICE;
                } else {
                        usbi_err("open failed, code %d errno %d", hpriv->fd, errno);
                        return LIBUSB_ERROR_IO;
@@ -895,6 +899,8 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config)
                        return LIBUSB_ERROR_NOT_FOUND;
                else if (errno == EBUSY)
                        return LIBUSB_ERROR_BUSY;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("failed, error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -927,6 +933,8 @@ static int op_claim_interface(struct libusb_device_handle *handle, int iface)
                        return LIBUSB_ERROR_NOT_FOUND;
                else if (errno == EBUSY)
                        return LIBUSB_ERROR_BUSY;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("claim interface failed, error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -939,6 +947,9 @@ static int op_release_interface(struct libusb_device_handle *handle, int iface)
        int fd = __device_handle_priv(handle)->fd;
        int r = ioctl(fd, IOCTL_USBFS_RELEASEINTF, &iface);
        if (r) {
+               if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
+
                usbi_err("release interface failed, error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
        }
@@ -958,6 +969,8 @@ static int op_set_interface(struct libusb_device_handle *handle, int iface,
        if (r) {
                if (errno == EINVAL)
                        return LIBUSB_ERROR_NOT_FOUND;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("setintf failed error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -975,6 +988,8 @@ static int op_clear_halt(struct libusb_device_handle *handle,
        if (r) {
                if (errno == ENOENT)
                        return LIBUSB_ERROR_NOT_FOUND;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("clear_halt failed error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -1010,6 +1025,8 @@ static int op_kernel_driver_active(struct libusb_device_handle *handle,
        if (r) {
                if (errno == ENODATA)
                        return 0;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("get driver failed error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -1035,6 +1052,8 @@ static int op_detach_kernel_driver(struct libusb_device_handle *handle,
                        return LIBUSB_ERROR_NOT_FOUND;
                else if (errno == EINVAL)
                        return LIBUSB_ERROR_INVALID_PARAM;
+               else if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
 
                usbi_err("detach failed error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
@@ -1118,14 +1137,20 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
                if (r < 0) {
                        int j;
-                       usbi_err("submiturb failed error %d errno=%d", r, errno);
+
+                       if (errno == ENODEV) {
+                               r = LIBUSB_ERROR_NO_DEVICE;
+                       } else {
+                               usbi_err("submiturb failed error %d errno=%d", r, errno);
+                               r = LIBUSB_ERROR_IO;
+                       }
        
                        /* 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 LIBUSB_ERROR_IO;
+                               return r;
                        }
 
                        /* if it's not the first URB that failed, the situation is a bit
@@ -1266,14 +1291,20 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer)
                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 (errno == ENODEV) {
+                               r = LIBUSB_ERROR_NO_DEVICE;
+                       } else {
+                               usbi_err("submiturb failed error %d errno=%d", r, errno);
+                               r = LIBUSB_ERROR_IO;
+                       }
 
                        /* 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 LIBUSB_ERROR_IO;
+                               return r;
                        }
 
                        /* if it's not the first URB that failed, the situation is a bit
@@ -1338,8 +1369,11 @@ static int submit_control_transfer(struct usbi_transfer *itransfer)
 
        r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
        if (r < 0) {
-               usbi_err("submiturb failed error %d errno=%d", r, errno);
                free(urb);
+               if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
+
+               usbi_err("submiturb failed error %d errno=%d", r, errno);
                return LIBUSB_ERROR_IO;
        }
        return 0;
@@ -1450,12 +1484,33 @@ static int op_cancel_transfer(struct usbi_transfer *itransfer)
        }
 }
 
+static void op_clear_transfer_priv(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);
+
+       switch (transfer->type) {
+       case LIBUSB_TRANSFER_TYPE_CONTROL:
+       case LIBUSB_TRANSFER_TYPE_BULK:
+       case LIBUSB_TRANSFER_TYPE_INTERRUPT:
+               free(tpriv->urbs);
+               break;
+       case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
+               free_iso_urbs(tpriv);
+               break;
+       default:
+               usbi_err("unknown endpoint type %d", transfer->type);
+       }
+}
+
 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;
+       enum libusb_transfer_status status;
 
        usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status,
                urb_idx + 1, num_urbs);
@@ -1483,19 +1538,20 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
                if (tpriv->awaiting_reap == 0 && tpriv->awaiting_discard == 0) {
                        usbi_dbg("CANCEL: last URB handled, reporting");
                        free(tpriv->urbs);
-                       if (tpriv->reap_action == CANCELLED)
+                       if (tpriv->reap_action == CANCELLED) {
                                usbi_handle_transfer_cancellation(itransfer);
-                       else
-                               usbi_handle_transfer_completion(itransfer,
-                                       LIBUSB_TRANSFER_ERROR);
+                       } else {
+                               status = LIBUSB_TRANSFER_ERROR;
+                               goto out;
+                       }
                }
                return 0;
        }
 
        if (urb->status == -EPIPE) {
                usbi_dbg("detected endpoint stall");
-               usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_STALL);
-               return 0;
+               status = LIBUSB_TRANSFER_STALL;
+               goto out;
        } else if (urb->status != 0) {
                usbi_warn("unrecognised urb status %d", urb->status);
        }
@@ -1510,6 +1566,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
        else
                return 0;
 
+out:
        free(tpriv->urbs);
        usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
        return 0;
@@ -1641,10 +1698,13 @@ static int reap_for_handle(struct libusb_device_handle *handle)
 
        r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb);
        if (r == -1 && errno == EAGAIN)
-               return r;
+               return 1;
        if (r < 0) {
+               if (errno == ENODEV)
+                       return LIBUSB_ERROR_NO_DEVICE;
+
                usbi_err("reap failed error %d errno=%d", r, errno);
-               return r;
+               return LIBUSB_ERROR_IO;
        }
 
        itransfer = urb->usercontext;
@@ -1667,30 +1727,44 @@ static int reap_for_handle(struct libusb_device_handle *handle)
        }
 }
 
-static int op_handle_events(fd_set *readfds, fd_set *writefds)
+static int op_handle_events(struct pollfd *fds, nfds_t nfds, int num_ready)
 {
-       struct libusb_device_handle *handle;
-       int ret = 0;
+       int r;
+       int i = 0;
 
        pthread_mutex_lock(&usbi_open_devs_lock);
-       list_for_each_entry(handle, &usbi_open_devs, list) {
-               struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
-               int r;
+       for (i = 0; i < nfds && num_ready > 0; i++) {
+               struct pollfd *pollfd = &fds[i];
+               struct libusb_device_handle *handle;
+               struct linux_device_handle_priv *hpriv = NULL;
 
-               if (!FD_ISSET(hpriv->fd, writefds))
+               if (!pollfd->revents)
                        continue;
+
+               num_ready--;
+               list_for_each_entry(handle, &usbi_open_devs, list) {
+                       hpriv =  __device_handle_priv(handle);
+                       if (hpriv->fd == pollfd->fd)
+                               break;
+               }
+
+               if (pollfd->revents & POLLERR) {
+                       usbi_remove_pollfd(hpriv->fd);
+                       usbi_handle_disconnect(handle);
+                       continue;
+               }
+
                r = reap_for_handle(handle);
-               if (r == -1 && errno == EAGAIN)
+               if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE)
                        continue;
-               if (r < 0) {
-                       ret = LIBUSB_ERROR_IO;
+               else if (r < 0)
                        goto out;
-               }
        }
 
+       r = 0;
 out:
        pthread_mutex_unlock(&usbi_open_devs_lock);
-       return ret;
+       return r;
 }
 
 const struct usbi_os_backend linux_usbfs_backend = {
@@ -1719,6 +1793,7 @@ const struct usbi_os_backend linux_usbfs_backend = {
 
        .submit_transfer = op_submit_transfer,
        .cancel_transfer = op_cancel_transfer,
+       .clear_transfer_priv = op_clear_transfer_priv,
 
        .handle_events = op_handle_events,
 
index 1d0f7c4..d158922 100644 (file)
@@ -66,6 +66,7 @@ static void ctrl_transfer_cb(struct libusb_transfer *transfer)
  * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out
  * \returns LIBUSB_ERROR_PIPE if the control request was not supported by the
  * device
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failures
  */
 API_EXPORTED int libusb_control_transfer(libusb_device_handle *dev_handle,
@@ -125,6 +126,9 @@ API_EXPORTED int libusb_control_transfer(libusb_device_handle *dev_handle,
        case LIBUSB_TRANSFER_STALL:
                r = LIBUSB_ERROR_PIPE;
                break;
+       case LIBUSB_TRANSFER_NO_DEVICE:
+               r = LIBUSB_ERROR_NO_DEVICE;
+               break;
        default:
                usbi_warn("unrecognised status code %d", transfer->status);
                r = LIBUSB_ERROR_OTHER;
@@ -186,6 +190,9 @@ static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
        case LIBUSB_TRANSFER_STALL:
                r = LIBUSB_ERROR_PIPE;
                break;
+       case LIBUSB_TRANSFER_NO_DEVICE:
+               r = LIBUSB_ERROR_NO_DEVICE;
+               break;
        default:
                usbi_warn("unrecognised status code %d", transfer->status);
                r = LIBUSB_ERROR_OTHER;
@@ -230,6 +237,7 @@ static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
  * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates
  * <tt>transferred</tt>)
  * \returns LIBUSB_ERROR_PIPE if the endpoint halted
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failures
  */
 API_EXPORTED int libusb_bulk_transfer(struct libusb_device_handle *dev_handle,
@@ -276,6 +284,7 @@ API_EXPORTED int libusb_bulk_transfer(struct libusb_device_handle *dev_handle,
  * \returns 0 on success (and populates <tt>transferred</tt>)
  * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out
  * \returns LIBUSB_ERROR_PIPE if the endpoint halted
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other error
  */
 API_EXPORTED int libusb_interrupt_transfer(