diff options
author | Daniel Drake <dsd@gentoo.org> | 2008-03-13 12:36:56 +0000 |
---|---|---|
committer | Daniel Drake <dsd@gentoo.org> | 2008-03-13 12:46:08 +0000 |
commit | c0c9432d38b22784070dce3a7874c62c31786a27 (patch) | |
tree | 825ca9817d3609a0645dbe3f1a1e46aa40dc1ff9 | |
parent | de4c5341d168697baa4c0901c406deb47e78aae7 (diff) | |
download | libusb-c0c9432d38b22784070dce3a7874c62c31786a27.tar.gz |
Beginnings of cross-platform abstraction
This also includes a libusb_get_pollfds API change
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | configure.ac | 14 | ||||
-rw-r--r-- | examples/dpfp.c | 2 | ||||
-rw-r--r-- | libusb/Makefile.am | 10 | ||||
-rw-r--r-- | libusb/core.c | 397 | ||||
-rw-r--r-- | libusb/io.c | 221 | ||||
-rw-r--r-- | libusb/libusb.h | 2 | ||||
-rw-r--r-- | libusb/libusbi.h | 85 | ||||
-rw-r--r-- | libusb/os/linux_usbfs.c | 584 | ||||
-rw-r--r-- | libusb/os/linux_usbfs.h (renamed from libusb/usbfs.h) | 78 |
10 files changed, 874 insertions, 520 deletions
@@ -4,7 +4,6 @@ cancellation race concerns - better tracking of kernel feedback? API docs isochronous endpoint I/O thread safety -abstraction for cross-platform-ness error codes fixme review review functionality missing over 0.1 diff --git a/configure.ac b/configure.ac index 58b4aa2..e8ae187 100644 --- a/configure.ac +++ b/configure.ac @@ -10,6 +10,20 @@ AC_C_INLINE AM_PROG_CC_C_O AC_DEFINE([_GNU_SOURCE], [], [Use GNU extensions]) +AC_MSG_CHECKING([operating system]) +case $host in +*-linux*) + AC_DEFINE(OS_LINUX, [], [Linux backend]) + AC_SUBST(OS_LINUX) + AC_MSG_RESULT([Linux]) + backend="linux" + ;; +*) + AC_MSG_ERROR([unsupported operating system]) +esac + +AM_CONDITIONAL([OS_LINUX], [test "x$backend" == "xlinux"]) + # Library versioning lt_major="0" lt_revision="0" diff --git a/examples/dpfp.c b/examples/dpfp.c index 70944f8..47d9fc6 100644 --- a/examples/dpfp.c +++ b/examples/dpfp.c @@ -270,7 +270,7 @@ static int next_state(void) printf("unrecognised state %d\n", state); } if (r < 0) { - fprintf(stderr, "error detected changing state"); + fprintf(stderr, "error detected changing state\n"); return r; } diff --git a/libusb/Makefile.am b/libusb/Makefile.am index 462bd2f..fb5d033 100644 --- a/libusb/Makefile.am +++ b/libusb/Makefile.am @@ -1,7 +1,15 @@ lib_LTLIBRARIES = libusb-1.0.la +LINUX_USBFS_SRC = os/linux_usbfs.h os/linux_usbfs.c + +EXTRA_DIST = $(LINUX_USBFS_SRC) + +if OS_LINUX +OS_SRC = $(LINUX_USBFS_SRC) +endif + libusb_1_0_la_CFLAGS = -fvisibility=hidden $(AM_CFLAGS) -libusb_1_0_la_SOURCES = libusbi.h usbfs.h core.c descriptor.c io.c sync.c +libusb_1_0_la_SOURCES = libusbi.h core.c descriptor.c io.c sync.c $(OS_SRC) libusb_1_0_la_LIBADD = -lrt hdrdir = $(includedir)/libusb-1.0 diff --git a/libusb/core.c b/libusb/core.c index 8d7abcf..68d32ce 100644 --- a/libusb/core.c +++ b/libusb/core.c @@ -20,66 +20,24 @@ #include <config.h> -#include <dirent.h> #include <errno.h> -#include <fcntl.h> -#include <features.h> #include <poll.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> #include "libusb.h" #include "libusbi.h" -struct list_head usb_devs; -struct list_head open_devs; -static const char *usbfs_path = NULL; +#ifdef OS_LINUX +const struct usbi_os_backend * const usbi_backend = &linux_usbfs_backend; +#else +#error "Unsupported OS" +#endif -static int check_usb_vfs(const char *dirname) -{ - DIR *dir; - struct dirent *entry; - int found = 0; - - dir = opendir(dirname); - if (!dir) - return 0; - - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') - continue; - - /* We assume if we find any files that it must be the right place */ - found = 1; - break; - } - - closedir(dir); - return found; -} - -static const char *find_usbfs_path(void) -{ - const char *path = "/dev/bus/usb"; - const char *ret = NULL; - - if (check_usb_vfs(path)) { - ret = path; - } else { - path = "/proc/bus/usb"; - if (check_usb_vfs(path)) - ret = path; - } - - usbi_dbg("found usbfs at %s", ret); - return ret; -} +static struct list_head usb_devs; +struct list_head usbi_open_devs; /* we traverse usbfs without knowing how many devices we are going to find. * so we create this discovered_devs model which is similar to a linked-list @@ -87,11 +45,6 @@ static const char *find_usbfs_path(void) * eliminating the need for a list node in the libusb_device structure * itself. */ #define DISCOVERED_DEVICES_SIZE_STEP 8 -struct discovered_devs { - size_t len; - size_t capacity; - struct libusb_device *devices[0]; -}; static struct discovered_devs *discovered_devs_alloc(void) { @@ -107,7 +60,7 @@ static struct discovered_devs *discovered_devs_alloc(void) /* append a device to the discovered devices collection. may realloc itself, * returning new discdevs. returns NULL on realloc failure. */ -static struct discovered_devs *discovered_devs_append( +struct discovered_devs *discovered_devs_append( struct discovered_devs *discdevs, struct libusb_device *dev) { size_t len = discdevs->len; @@ -144,115 +97,21 @@ static void discovered_devs_free(struct discovered_devs *discdevs) free(discdevs); } -static struct libusb_device *device_new(uint8_t busnum, uint8_t devaddr) +struct libusb_device *usbi_alloc_device(unsigned long session_id) { - char path[PATH_MAX + 1]; - unsigned char raw_desc[DEVICE_DESC_LENGTH]; - struct libusb_device *dev = malloc(sizeof(*dev)); - int fd = 0; - int i; - int r; - int tmp; - + size_t priv_size = usbi_backend->device_priv_size; + struct libusb_device *dev = malloc(sizeof(*dev) + priv_size); if (!dev) return NULL; dev->refcnt = 1; - dev->nodepath = NULL; - dev->config = NULL; - - snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr); - usbi_dbg("%s", path); - fd = open(path, O_RDWR); - if (!fd) { - usbi_dbg("open '%s' failed, ret=%d errno=%d", path, fd, errno); - /* FIXME this might not be an error if the file has gone away due - * to unplugging */ - goto err; - } - - r = read(fd, raw_desc, DEVICE_DESC_LENGTH); - if (r < 0) { - usbi_err("read failed ret=%d errno=%d", r, errno); - goto err; - } - /* FIXME: short read handling? */ - - usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc); - - /* Now try to fetch the rest of the descriptors */ - if (dev->desc.bNumConfigurations > USB_MAXCONFIG) { - usbi_err("too many configurations"); - goto err; - } - - if (dev->desc.bNumConfigurations < 1) { - usbi_dbg("no configurations?"); - goto err; - } - - tmp = dev->desc.bNumConfigurations * sizeof(struct libusb_config_descriptor); - dev->config = malloc(tmp); - if (!dev->config) - goto err; - - memset(dev->config, 0, tmp); - for (i = 0; i < dev->desc.bNumConfigurations; i++) { - unsigned char buffer[8], *bigbuffer; - struct libusb_config_descriptor config; - - /* Get the first 8 bytes to figure out what the total length is */ - r = read(fd, buffer, sizeof(buffer)); - if (r < sizeof(buffer)) { - usbi_err("short descriptor read (%d/%d)", r, sizeof(buffer)); - goto err; - } - - usbi_parse_descriptor(buffer, "bbw", &config); - - bigbuffer = malloc(config.wTotalLength); - if (!bigbuffer) - goto err; - - /* Read the rest of the config descriptor */ - memcpy(bigbuffer, buffer, sizeof(buffer)); - - tmp = config.wTotalLength - 8; - r = read(fd, bigbuffer + 8, tmp); - if (r < tmp) { - usbi_err("short descriptor read (%d/%d)", r, tmp); - free(bigbuffer); - goto err; - } - - r = usbi_parse_configuration(&dev->config[i], bigbuffer); - if (r > 0) - usbi_warn("descriptor data still left\n"); - free(bigbuffer); - } - - dev->nodepath = strdup(path); - if (!dev->nodepath) - goto err; - - dev->session_data = busnum << 8 | devaddr; + dev->session_data = session_id; list_add(&dev->list, &usb_devs); - close(fd); + memset(&dev->os_priv, 0, priv_size); return dev; - -err: - if (fd) - close(fd); - if (dev->config) - free(dev->config); - if (dev->nodepath) - free(dev->nodepath); - if (dev) - free(dev); - return NULL; } -static struct libusb_device *get_device_by_session_id(unsigned long session_id) +struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id) { struct libusb_device *dev; @@ -263,103 +122,9 @@ static struct libusb_device *get_device_by_session_id(unsigned long session_id) return NULL; } -/* open a device file, set up the libusb_device structure for it, and add it to - * discdevs. on failure (non-zero return) the pre-existing discdevs should - * be destroyed (and devices freed). on success, the new discdevs pointer - * should be used it may have been moved. */ -static int scan_device(struct discovered_devs **_discdevs, uint8_t busnum, - uint8_t devaddr) -{ - struct discovered_devs *discdevs; - unsigned long session_id; - struct libusb_device *dev; - int need_unref = 0; - int r = 0; - - /* FIXME: session ID is not guaranteed unique as addresses can wrap and - * will be reused. instead we should add a simple sysfs attribute with - * a session ID. */ - session_id = busnum << 8 | devaddr; - usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr, - session_id); - - dev = get_device_by_session_id(session_id); - if (dev) { - usbi_dbg("using existing device for %d/%d (session %ld)", - busnum, devaddr, session_id); - } else { - usbi_dbg("allocating new device for %d/%d (session %ld)", - busnum, devaddr, session_id); - dev = device_new(busnum, devaddr); - if (!dev) { - r = -EIO; - goto out; - } - need_unref = 1; - } - - discdevs = discovered_devs_append(*_discdevs, dev); - if (!discdevs) - r = -ENOMEM; - else - *_discdevs = discdevs; - -out: - if (need_unref) - libusb_device_unref(dev); - return r; -} - -/* open a bus directory and adds all discovered devices to discdevs. on - * failure (non-zero return) the pre-existing discdevs should be destroyed - * (and devices freed). on success, the new discdevs pointer should be used - * as it may have been moved. */ -static int scan_busdir(struct discovered_devs **_discdevs, uint8_t busnum) -{ - DIR *dir; - char dirpath[PATH_MAX + 1]; - struct dirent *entry; - struct discovered_devs *discdevs = *_discdevs; - int r = 0; - - snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum); - usbi_dbg("%s", dirpath); - dir = opendir(dirpath); - if (!dir) { - usbi_err("opendir '%s' failed, errno=%d", dirpath, errno); - /* FIXME: should handle valid race conditions like hub unplugged - * during directory iteration - this is not an error */ - return -1; - } - - while ((entry = readdir(dir))) { - int devaddr; - - if (entry->d_name[0] == '.') - continue; - - devaddr = atoi(entry->d_name); - if (devaddr == 0) { - usbi_dbg("unknown dir entry %s", entry->d_name); - continue; - } - - r = scan_device(&discdevs, busnum, (uint8_t) devaddr); - if (r < 0) - goto out; - } - - *_discdevs = discdevs; -out: - closedir(dir); - return r; -} - API_EXPORTED int libusb_get_device_list(struct libusb_device ***list) { - DIR *buses; struct discovered_devs *discdevs = discovered_devs_alloc(); - struct dirent *entry; struct libusb_device **ret; int r = 0; size_t i; @@ -369,30 +134,9 @@ API_EXPORTED int libusb_get_device_list(struct libusb_device ***list) if (!discdevs) return -ENOMEM; - buses = opendir(usbfs_path); - if (!buses) { - usbi_err("opendir buses failed errno=%d", errno); - return -1; - } - - while ((entry = readdir(buses))) { - struct discovered_devs *discdevs_new = discdevs; - int busnum; - - if (entry->d_name[0] == '.') - continue; - - busnum = atoi(entry->d_name); - if (busnum == 0) { - usbi_dbg("unknown dir entry %s", entry->d_name); - continue; - } - - r = scan_busdir(&discdevs_new, busnum); - if (r < 0) - goto out; - discdevs = discdevs_new; - } + r = usbi_backend->get_device_list(discdevs); + if (r < 0) + goto out; /* convert discovered_devs into a list */ len = discdevs->len; @@ -411,7 +155,6 @@ API_EXPORTED int libusb_get_device_list(struct libusb_device ***list) out: discovered_devs_free(discdevs); - closedir(buses); return r; } @@ -445,9 +188,13 @@ API_EXPORTED void libusb_device_unref(struct libusb_device *dev) if (--dev->refcnt == 0) { usbi_dbg("destroy device %04x:%04x", dev->desc.idVendor, dev->desc.idProduct); + + if (usbi_backend->destroy_device) + usbi_backend->destroy_device(dev); + list_del(&dev->list); - free(dev->config); - free(dev->nodepath); + if (dev->config) + free(dev->config); free(dev); } } @@ -466,27 +213,26 @@ API_EXPORTED struct libusb_config_descriptor *libusb_get_config_descriptor( API_EXPORTED struct libusb_device_handle *libusb_open(struct libusb_device *dev) { - struct libusb_device_handle *devh; - int fd; + struct libusb_device_handle *handle; + size_t priv_size = usbi_backend->device_handle_priv_size; + int r; usbi_dbg("open %04x:%04x", dev->desc.idVendor, dev->desc.idProduct); - fd = open(dev->nodepath, O_RDWR); - if (!fd) { - usbi_err("open failed, code %d errno %d", fd, errno); + handle = malloc(sizeof(*handle) + priv_size); + if (!handle) return NULL; - } - devh = malloc(sizeof(*devh)); - if (!devh) { - close(fd); + handle->dev = libusb_device_ref(dev); + memset(&handle->os_priv, 0, priv_size); + r = usbi_backend->open(handle); + if (r < 0) { + libusb_device_unref(dev); + free(handle); return NULL; } - devh->fd = fd; - devh->dev = libusb_device_ref(dev); - list_add(&devh->list, &open_devs); - usbi_add_pollfd(fd, POLLOUT); - return devh; + list_add(&handle->list, &usbi_open_devs); + return handle; } /* convenience function for finding a device with a particular vendor/product @@ -499,7 +245,7 @@ API_EXPORTED struct libusb_device_handle *libusb_open_device_with_vid_pid( struct libusb_device **devs; struct libusb_device *found = NULL; struct libusb_device *dev; - struct libusb_device_handle *devh; + struct libusb_device_handle *handle = NULL; size_t i = 0; if (libusb_get_device_list(&devs) < 0) @@ -515,17 +261,16 @@ API_EXPORTED struct libusb_device_handle *libusb_open_device_with_vid_pid( } if (found) - devh = libusb_open(found); + handle = libusb_open(found); libusb_free_device_list(devs, 1); - return devh; + return handle; } -static void do_close(struct libusb_device_handle *devh) +static void do_close(struct libusb_device_handle *dev_handle) { - usbi_remove_pollfd(devh->fd); - close(devh->fd); - libusb_device_unref(devh->dev); + usbi_backend->close(dev_handle); + libusb_device_unref(dev_handle->dev); } API_EXPORTED void libusb_close(struct libusb_device_handle *dev_handle) @@ -548,38 +293,29 @@ API_EXPORTED struct libusb_device *libusb_get_device( API_EXPORTED int libusb_claim_interface(struct libusb_device_handle *dev, int iface) { - int r; usbi_dbg("interface %d", iface); - - r = ioctl(dev->fd, IOCTL_USB_CLAIMINTF, &iface); - if (r < 0) - usbi_err("claim interface failed, error %d", r); - return r; + return usbi_backend->claim_interface(dev, iface); } API_EXPORTED int libusb_release_interface(struct libusb_device_handle *dev, int iface) { - int r; usbi_dbg("interface %d", iface); - - r = ioctl(dev->fd, IOCTL_USB_RELEASEINTF, &iface); - if (r < 0) - usbi_err("release interface failed, error %d", r); - return r; + return usbi_backend->release_interface(dev, iface); } API_EXPORTED int libusb_init(void) { usbi_dbg(""); - usbfs_path = find_usbfs_path(); - if (!usbfs_path) { - usbi_err("could not find usbfs"); - return -ENODEV; + + if (usbi_backend->init) { + int r = usbi_backend->init(); + if (r < 0) + return r; } list_init(&usb_devs); - list_init(&open_devs); + list_init(&usbi_open_devs); usbi_io_init(); return 0; } @@ -588,37 +324,14 @@ API_EXPORTED void libusb_exit(void) { struct libusb_device_handle *devh; usbi_dbg(""); - if (!list_empty(&open_devs)) { + if (!list_empty(&usbi_open_devs)) { usbi_dbg("naughty app left some devices open!\n"); - list_for_each_entry(devh, &open_devs, list) + list_for_each_entry(devh, &usbi_open_devs, list) do_close(devh); + /* FIXME where do the open handles get freed? */ } -} - -API_EXPORTED size_t libusb_get_pollfds(struct libusb_pollfd **pollfds) -{ - struct libusb_device_handle *devh; - struct libusb_pollfd *ret; - size_t cnt = 0; - size_t i = 0; - - /* count number of open devices */ - list_for_each_entry(devh, &open_devs, list) - cnt++; - - /* create array */ - ret = calloc(cnt, sizeof(struct libusb_pollfd)); - if (!ret) - return -ENOMEM; - - /* add fds */ - list_for_each_entry(devh, &open_devs, list) { - ret[i++].fd = devh->fd; - ret[i].events = POLLOUT; - } - - *pollfds = ret; - return cnt; + if (usbi_backend->exit) + usbi_backend->exit(); } void usbi_log(enum usbi_log_level level, const char *function, diff --git a/libusb/io.c b/libusb/io.c index 118c8b0..36f5ceb 100644 --- a/libusb/io.c +++ b/libusb/io.c @@ -20,6 +20,7 @@ #include <config.h> #include <errno.h> +#include <poll.h> #include <signal.h> #include <stdint.h> #include <stdlib.h> @@ -39,6 +40,9 @@ * are always placed at the very end. */ static struct list_head flying_transfers; +/* list of poll fd's */ +static struct list_head pollfds; + /* user callbacks for pollfd changes */ static libusb_pollfd_added_cb fd_added_cb = NULL; static libusb_pollfd_removed_cb fd_removed_cb = NULL; @@ -46,6 +50,7 @@ static libusb_pollfd_removed_cb fd_removed_cb = NULL; void usbi_io_init() { list_init(&flying_transfers); + list_init(&pollfds); fd_added_cb = NULL; fd_removed_cb = NULL; } @@ -113,43 +118,9 @@ static void add_to_flying_list(struct usbi_transfer *transfer) static int submit_transfer(struct usbi_transfer *itransfer) { - int r; - struct usb_urb *urb = &itransfer->urb; - struct libusb_transfer *transfer = &itransfer->pub; - int to_be_transferred = transfer->length - itransfer->transferred; - - switch (transfer->endpoint_type) { - case LIBUSB_ENDPOINT_TYPE_CONTROL: - urb->type = USB_URB_TYPE_CONTROL; - break; - case LIBUSB_ENDPOINT_TYPE_BULK: - urb->type = USB_URB_TYPE_BULK; - break; - case LIBUSB_ENDPOINT_TYPE_INTERRUPT: - urb->type = USB_URB_TYPE_INTERRUPT; - break; - default: - usbi_err("unknown endpoint type %d", transfer->endpoint_type); - return -EINVAL; - } - - urb->endpoint = transfer->endpoint; - urb->buffer = transfer->buffer + itransfer->transferred; - urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH); - - /* 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); - - r = ioctl(transfer->dev_handle->fd, IOCTL_USB_SUBMITURB, urb); - if (r < 0) { - usbi_err("submiturb failed error %d errno=%d", r, errno); + int r = usbi_backend->submit_transfer(itransfer); + if (r < 0) return r; - } add_to_flying_list(itransfer); return 0; @@ -157,7 +128,7 @@ static int submit_transfer(struct usbi_transfer *itransfer) API_EXPORTED size_t libusb_get_transfer_alloc_size(void) { - return sizeof(struct usbi_transfer); + return sizeof(struct usbi_transfer) + usbi_backend->transfer_priv_size; } void __init_transfer(struct usbi_transfer *transfer) @@ -172,7 +143,8 @@ API_EXPORTED void libusb_init_transfer(struct libusb_transfer *transfer) API_EXPORTED struct libusb_transfer *libusb_alloc_transfer(void) { - struct usbi_transfer *transfer = malloc(sizeof(*transfer)); + struct usbi_transfer *transfer = + malloc(sizeof(*transfer) + usbi_backend->transfer_priv_size); if (!transfer) return NULL; @@ -212,7 +184,7 @@ API_EXPORTED int libusb_cancel_transfer(struct libusb_transfer *transfer) int r; usbi_dbg(""); - r = ioctl(transfer->dev_handle->fd, IOCTL_USB_DISCARDURB, &itransfer->urb); + r = usbi_backend->cancel_transfer(itransfer); if (r < 0) usbi_err("cancel transfer failed error %d", r); return r; @@ -224,7 +196,7 @@ API_EXPORTED int libusb_cancel_transfer_sync(struct libusb_transfer *transfer) int r; usbi_dbg(""); - r = ioctl(transfer->dev_handle->fd, IOCTL_USB_DISCARDURB, &itransfer->urb); + r = usbi_backend->cancel_transfer(itransfer); if (r < 0) { usbi_err("cancel transfer failed error %d", r); return r; @@ -240,7 +212,7 @@ API_EXPORTED int libusb_cancel_transfer_sync(struct libusb_transfer *transfer) return 0; } -static void handle_transfer_completion(struct usbi_transfer *itransfer, +void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, enum libusb_transfer_status status) { struct libusb_transfer *transfer = &itransfer->pub; @@ -267,7 +239,7 @@ static void handle_transfer_completion(struct usbi_transfer *itransfer, libusb_free_transfer(transfer); } -static void handle_transfer_cancellation(struct usbi_transfer *transfer) +void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer) { /* if the URB is being cancelled synchronously, raise cancellation * completion event by unsetting flag, and ensure that user callback does @@ -276,83 +248,20 @@ static void handle_transfer_cancellation(struct usbi_transfer *transfer) if (transfer->flags & USBI_TRANSFER_SYNC_CANCELLED) { transfer->flags &= ~USBI_TRANSFER_SYNC_CANCELLED; usbi_dbg("detected sync. cancel"); - handle_transfer_completion(transfer, LIBUSB_TRANSFER_SILENT_COMPLETION); + usbi_handle_transfer_completion(transfer, + LIBUSB_TRANSFER_SILENT_COMPLETION); return; } /* if the URB was cancelled due to timeout, report timeout to the user */ if (transfer->flags & USBI_TRANSFER_TIMED_OUT) { usbi_dbg("detected timeout cancellation"); - handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT); + usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT); return; } /* otherwise its a normal async cancel */ - handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED); -} - -static int reap_for_devh(struct libusb_device_handle *devh) -{ - int r; - struct usb_urb *urb; - struct usbi_transfer *itransfer; - struct libusb_transfer *transfer; - int trf_requested; - int length; - - r = ioctl(devh->fd, IOCTL_USB_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 = container_of(urb, struct usbi_transfer, urb); - transfer = &itransfer->pub; - - usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status, - urb->actual_length); - list_del(&itransfer->list); - - if (urb->status == -2) { - handle_transfer_cancellation(itransfer); - return 0; - } - - /* FIXME: research what other status codes may exist */ - if (urb->status != 0) - usbi_warn("unrecognised urb status %d", urb->status); - - /* 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); - - itransfer->transferred += urb->actual_length; - - /* if we were provided less data than requested, then our transfer is - * done */ - if (urb->actual_length < trf_requested) { - usbi_dbg("less data than requested (%d/%d) --> all done", - urb->actual_length, trf_requested); - 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"); - handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); - return 0; - } - - /* otherwise, we have more data to transfer */ - usbi_dbg("more data to transfer..."); - memset(urb, 0, sizeof(*urb)); - return submit_transfer(itransfer); + usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED); } static void handle_timeout(struct usbi_transfer *itransfer) @@ -416,10 +325,14 @@ static int handle_timeouts(void) static int poll_io(struct timeval *tv) { - struct libusb_device_handle *devh; int r; int maxfd = 0; - fd_set writefds; + 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; @@ -438,19 +351,33 @@ static int poll_io(struct timeval *tv) select_timeout = *tv; } + FD_ZERO(&readfds); FD_ZERO(&writefds); - list_for_each_entry(devh, &open_devs, list) { - int fd = devh->fd; - FD_SET(fd, &writefds); + 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; } + 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, NULL, &writefds, NULL, &select_timeout); - usbi_dbg("select() returned %d with %d.%06ds remaining", r, 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); if (r == 0) { *tv = select_timeout; return handle_timeouts(); @@ -461,15 +388,9 @@ static int poll_io(struct timeval *tv) return r; } - list_for_each_entry(devh, &open_devs, list) { - if (!FD_ISSET(devh->fd, &writefds)) - continue; - r = reap_for_devh(devh); - if (r == -1 && errno == EAGAIN) - continue; - if (r < 0) - return r; - } + r = usbi_backend->handle_events(_readfds, _writefds); + if (r < 0) + return r; /* FIXME check return value? */ return handle_timeouts(); @@ -561,17 +482,63 @@ API_EXPORTED void libusb_set_pollfd_notifiers(libusb_pollfd_added_cb added_cb, fd_removed_cb = removed_cb; } -void usbi_add_pollfd(int fd, short events) +int usbi_add_pollfd(int fd, short events) { + struct usbi_pollfd *ipollfd = malloc(sizeof(*ipollfd)); + if (!ipollfd) + return -ENOMEM; + usbi_dbg("add fd %d events %d", fd, events); + ipollfd->pollfd.fd = fd; + ipollfd->pollfd.events = events; + list_add(&ipollfd->list, &pollfds); + if (fd_added_cb) fd_added_cb(fd, events); + return 0; } void usbi_remove_pollfd(int fd) { + struct usbi_pollfd *ipollfd; + int found = 0; + usbi_dbg("remove fd %d", fd); + list_for_each_entry(ipollfd, &pollfds, list) + if (ipollfd->pollfd.fd == fd) { + found = 1; + break; + } + + if (!found) { + usbi_err("couldn't find fd %d to remove", fd); + return; + } + + list_del(&ipollfd->list); + free(ipollfd); if (fd_removed_cb) fd_removed_cb(fd); } +API_EXPORTED struct libusb_pollfd **libusb_get_pollfds(void) +{ + struct libusb_pollfd **ret; + struct usbi_pollfd *ipollfd; + size_t i = 0; + size_t cnt = 0; + + list_for_each_entry(ipollfd, &pollfds, list) + cnt++; + + ret = calloc(cnt + 1, sizeof(struct libusb_pollfd *)); + if (!ret) + return NULL; + + list_for_each_entry(ipollfd, &pollfds, list) + ret[i++] = (struct libusb_pollfd *) ipollfd; + ret[cnt] = NULL; + + return ret; +} + diff --git a/libusb/libusb.h b/libusb/libusb.h index 4f38ddf..414803c 100644 --- a/libusb/libusb.h +++ b/libusb/libusb.h @@ -351,7 +351,7 @@ struct libusb_pollfd { int libusb_poll_timeout(struct timeval *tv); int libusb_poll(void); int libusb_get_next_timeout(struct timeval *tv); -size_t libusb_get_pollfds(struct libusb_pollfd **pollfds); +struct libusb_pollfd **libusb_get_pollfds(void); typedef void (*libusb_pollfd_added_cb)(int fd, short events); typedef void (*libusb_pollfd_removed_cb)(int fd); diff --git a/libusb/libusbi.h b/libusb/libusbi.h index 2ef2573..2d56d64 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -25,11 +25,10 @@ #include <endian.h> #include <stddef.h> -#include <sys/ioctl.h> +#include <sys/select.h> #include <time.h> #include <libusb.h> -#include <usbfs.h> #define DEVICE_DESC_LENGTH 18 @@ -146,15 +145,15 @@ struct libusb_device { struct list_head list; int refcnt; unsigned long session_data; - char *nodepath; struct libusb_device_descriptor desc; struct libusb_config_descriptor *config; + unsigned char os_priv[0]; }; struct libusb_device_handle { struct list_head list; struct libusb_device *dev; - int fd; + unsigned char os_priv[0]; }; #define USBI_TRANSFER_SYNC_CANCELLED (1<<0) @@ -164,11 +163,12 @@ struct usbi_transfer { /* must come first */ struct libusb_transfer pub; - struct usb_urb urb; struct list_head list; struct timeval timeout; int transferred; uint8_t flags; + + unsigned char os_priv[0]; }; /* bus structures */ @@ -181,15 +181,84 @@ struct usb_descriptor_header { /* shared data and functions */ -extern struct list_head open_devs; +extern struct list_head usbi_open_devs; void usbi_io_init(void); -void usbi_add_pollfd(int fd, short events); -void usbi_remove_pollfd(int fd); + +struct libusb_device *usbi_alloc_device(unsigned long session_id); +struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id); + +void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, + enum libusb_transfer_status status); +void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer); int usbi_parse_descriptor(unsigned char *source, char *descriptor, void *dest); int usbi_parse_configuration(struct libusb_config_descriptor *config, unsigned char *buffer); +/* polling */ + +struct usbi_pollfd { + /* must come first */ + struct libusb_pollfd pollfd; + + struct list_head list; +}; + +int usbi_add_pollfd(int fd, short events); +void usbi_remove_pollfd(int fd); + +/* device discovery */ + +/* we traverse usbfs without knowing how many devices we are going to find. + * so we create this discovered_devs model which is similar to a linked-list + * which grows when required. it can be freed once discovery has completed, + * eliminating the need for a list node in the libusb_device structure + * itself. */ +struct discovered_devs { + size_t len; + size_t capacity; + struct libusb_device *devices[0]; +}; + +struct discovered_devs *discovered_devs_append( + struct discovered_devs *discdevs, struct libusb_device *dev); + +/* OS abstraction */ + +struct usbi_os_backend { + const char *name; + int (*init)(void); + void (*exit)(void); + + int (*get_device_list)(struct discovered_devs *discdevs); + + int (*open)(struct libusb_device_handle *handle); + void (*close)(struct libusb_device_handle *handle); + + int (*claim_interface)(struct libusb_device_handle *handle, int iface); + int (*release_interface)(struct libusb_device_handle *handle, int iface); + + void (*destroy_device)(struct libusb_device *dev); + + int (*submit_transfer)(struct usbi_transfer *itransfer); + int (*cancel_transfer)(struct usbi_transfer *itransfer); + + int (*handle_events)(fd_set *readfds, fd_set *writefds); + + /* number of bytes to reserve for libusb_device.os_priv */ + size_t device_priv_size; + + /* number of bytes to reserve for libusb_device_handle.os_priv */ + size_t device_handle_priv_size; + + /* number of bytes to reserve for usbi_transfer.os_priv */ + size_t transfer_priv_size; +}; + +extern const struct usbi_os_backend * const usbi_backend; + +extern const struct usbi_os_backend linux_usbfs_backend; + #endif diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c new file mode 100644 index 0000000..bfd526b --- /dev/null +++ b/libusb/os/linux_usbfs.c @@ -0,0 +1,584 @@ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <config.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#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> + +#include "libusb.h" +#include "libusbi.h" +#include "linux_usbfs.h" + +static const char *usbfs_path = NULL; + +struct linux_device_priv { + char *nodepath; +}; + +struct linux_device_handle_priv { + int fd; +}; + +struct linux_transfer_priv { + struct usbfs_urb urb; +}; + +static struct linux_device_priv *__device_priv(struct libusb_device *dev) +{ + return (struct linux_device_priv *) dev->os_priv; +} + +static struct linux_device_handle_priv *__device_handle_priv( + struct libusb_device_handle *handle) +{ + return (struct linux_device_handle_priv *) handle->os_priv; +} + +static struct linux_transfer_priv *__transfer_priv( + struct usbi_transfer *transfer) +{ + return (struct linux_transfer_priv *) transfer->os_priv; +} + +#define TRANSFER_PRIV_GET_ITRANSFER(tpriv) \ + ((struct usbi_transfer *) \ + container_of((tpriv), struct usbi_transfer, os_priv)) + +static int check_usb_vfs(const char *dirname) +{ + DIR *dir; + struct dirent *entry; + int found = 0; + + dir = opendir(dirname); + if (!dir) + return 0; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') + continue; + + /* We assume if we find any files that it must be the right place */ + found = 1; + break; + } + + closedir(dir); + return found; +} + +static const char *find_usbfs_path(void) +{ + const char *path = "/dev/bus/usb"; + const char *ret = NULL; + + if (check_usb_vfs(path)) { + ret = path; + } else { + path = "/proc/bus/usb"; + if (check_usb_vfs(path)) + ret = path; + } + + usbi_dbg("found usbfs at %s", ret); + return ret; +} + +static int op_init(void) +{ + usbfs_path = find_usbfs_path(); + if (!usbfs_path) { + usbi_err("could not find usbfs"); + return -ENODEV; + } + return 0; +} + +static int initialize_device(struct libusb_device *dev, uint8_t busnum, + uint8_t devaddr) +{ + struct linux_device_priv *priv = __device_priv(dev); + char path[PATH_MAX + 1]; + unsigned char raw_desc[DEVICE_DESC_LENGTH]; + int fd = 0; + int i; + int r; + int tmp; + + priv->nodepath = NULL; + dev->config = NULL; + + snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr); + usbi_dbg("%s", path); + fd = open(path, O_RDWR); + if (!fd) { + usbi_dbg("open '%s' failed, ret=%d errno=%d", path, fd, errno); + /* FIXME this might not be an error if the file has gone away due + * to unplugging */ + r = -EIO; + goto err; + } + + /* FIXME: move config parsing into main lib */ + r = read(fd, raw_desc, DEVICE_DESC_LENGTH); + if (r < 0) { + usbi_err("read failed ret=%d errno=%d", r, errno); + goto err; + } + /* FIXME: short read handling? */ + + usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc); + + /* Now try to fetch the rest of the descriptors */ + if (dev->desc.bNumConfigurations > USB_MAXCONFIG) { + usbi_err("too many configurations"); + r = -EINVAL; + goto err; + } + + if (dev->desc.bNumConfigurations < 1) { + usbi_dbg("no configurations?"); + r = -EINVAL; + goto err; + } + + tmp = dev->desc.bNumConfigurations * sizeof(struct libusb_config_descriptor); + dev->config = malloc(tmp); + if (!dev->config) { + r = -ENOMEM; + goto err; + } + + memset(dev->config, 0, tmp); + for (i = 0; i < dev->desc.bNumConfigurations; i++) { + unsigned char buffer[8], *bigbuffer; + struct libusb_config_descriptor config; + + /* Get the first 8 bytes to figure out what the total length is */ + r = read(fd, buffer, sizeof(buffer)); + if (r < sizeof(buffer)) { + usbi_err("short descriptor read (%d/%d)", r, sizeof(buffer)); + r = -EIO; + goto err; + } + + usbi_parse_descriptor(buffer, "bbw", &config); + + bigbuffer = malloc(config.wTotalLength); + if (!bigbuffer) { + r = -ENOMEM; + goto err; + } + + /* Read the rest of the config descriptor */ + memcpy(bigbuffer, buffer, sizeof(buffer)); + + tmp = config.wTotalLength - 8; + r = read(fd, bigbuffer + 8, tmp); + if (r < tmp) { + usbi_err("short descriptor read (%d/%d)", r, tmp); + free(bigbuffer); + r = -EIO; + goto err; + } + + r = usbi_parse_configuration(&dev->config[i], bigbuffer); + if (r > 0) + usbi_warn("descriptor data still left\n"); + free(bigbuffer); + } + + priv->nodepath = strdup(path); + if (!priv->nodepath) { + r = -ENOMEM; + goto err; + } + + close(fd); + return 0; + +err: + if (fd) + close(fd); + if (dev->config) { + free(dev->config); + dev->config = NULL; + } + if (priv->nodepath) { + free(priv->nodepath); + priv->nodepath = NULL; + } + return r; +} + +/* open a device file, set up the libusb_device structure for it, and add it to + * discdevs. on failure (non-zero return) the pre-existing discdevs should + * be destroyed (and devices freed). on success, the new discdevs pointer + * should be used it may have been moved. */ +static int scan_device(struct discovered_devs **_discdevs, uint8_t busnum, + uint8_t devaddr) +{ + struct discovered_devs *discdevs; + unsigned long session_id; + struct libusb_device *dev; + int need_unref = 0; + int r = 0; + + /* FIXME: session ID is not guaranteed unique as addresses can wrap and + * will be reused. instead we should add a simple sysfs attribute with + * a session ID. */ + session_id = busnum << 8 | devaddr; + usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr, + session_id); + + dev = usbi_get_device_by_session_id(session_id); + if (dev) { + usbi_dbg("using existing device for %d/%d (session %ld)", + busnum, devaddr, session_id); + } else { + usbi_dbg("allocating new device for %d/%d (session %ld)", + busnum, devaddr, session_id); + dev = usbi_alloc_device(session_id); + if (!dev) { + r = -ENOMEM; + goto out; + } + need_unref = 1; + r = initialize_device(dev, busnum, devaddr); + if (r < 0) + goto out; + } + + discdevs = discovered_devs_append(*_discdevs, dev); + if (!discdevs) + r = -ENOMEM; + else + *_discdevs = discdevs; + +out: + if (need_unref) + libusb_device_unref(dev); + return r; +} + +/* open a bus directory and adds all discovered devices to discdevs. on + * failure (non-zero return) the pre-existing discdevs should be destroyed + * (and devices freed). on success, the new discdevs pointer should be used + * as it may have been moved. */ +static int scan_busdir(struct discovered_devs **_discdevs, uint8_t busnum) +{ + DIR *dir; + char dirpath[PATH_MAX + 1]; + struct dirent *entry; + struct discovered_devs *discdevs = *_discdevs; + int r = 0; + + snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum); + usbi_dbg("%s", dirpath); + dir = opendir(dirpath); + if (!dir) { + usbi_err("opendir '%s' failed, errno=%d", dirpath, errno); + /* FIXME: should handle valid race conditions like hub unplugged + * during directory iteration - this is not an error */ + return -1; + } + + while ((entry = readdir(dir))) { + int devaddr; + + if (entry->d_name[0] == '.') + continue; + + devaddr = atoi(entry->d_name); + if (devaddr == 0) { + usbi_dbg("unknown dir entry %s", entry->d_name); + continue; + } + + r = scan_device(&discdevs, busnum, (uint8_t) devaddr); + if (r < 0) + goto out; + } + + *_discdevs = discdevs; +out: + closedir(dir); + return r; +} + +static int op_get_device_list(struct discovered_devs *discdevs) +{ + struct dirent *entry; + int r = 0; + DIR *buses = opendir(usbfs_path); + if (!buses) { + usbi_err("opendir buses failed errno=%d", errno); + return -1; + } + + while ((entry = readdir(buses))) { + struct discovered_devs *discdevs_new = discdevs; + int busnum; + + if (entry->d_name[0] == '.') + continue; + + busnum = atoi(entry->d_name); + if (busnum == 0) { + usbi_dbg("unknown dir entry %s", entry->d_name); + continue; + } + + r = scan_busdir(&discdevs_new, busnum); + if (r < 0) + goto out; + discdevs = discdevs_new; + } + +out: + closedir(buses); + return r; +} + +static int op_open(struct libusb_device_handle *handle) +{ + struct linux_device_priv *dpriv = __device_priv(handle->dev); + struct linux_device_handle_priv *hpriv = __device_handle_priv(handle); + + hpriv->fd = open(dpriv->nodepath, O_RDWR); + if (hpriv->fd < 0) { + usbi_err("open failed, code %d errno %d", hpriv->fd, errno); + return -EIO; + } + + return usbi_add_pollfd(hpriv->fd, POLLOUT); +} + +static void op_close(struct libusb_device_handle *dev_handle) +{ + int fd = __device_handle_priv(dev_handle)->fd; + usbi_remove_pollfd(fd); + close(fd); +} + +static int op_claim_interface(struct libusb_device_handle *handle, int iface) +{ + int fd = __device_handle_priv(handle)->fd; + int r = ioctl(fd, IOCTL_USBFS_CLAIMINTF, &iface); + if (r < 0) + usbi_err("claim interface failed, error %d", r); + return r; +} + +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 < 0) + usbi_err("release interface failed, error %d", r); + return r; +} + +static void op_destroy_device(struct libusb_device *dev) +{ + unsigned char *nodepath = __device_priv(dev)->nodepath; + if (nodepath) + free(nodepath); +} + +static int submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = &itransfer->pub; + struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb; + struct linux_device_handle_priv *dpriv = + __device_handle_priv(transfer->dev_handle); + int to_be_transferred = transfer->length - itransfer->transferred; + int r; + + urb->buffer = transfer->buffer + itransfer->transferred; + urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH); + + /* 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); + + r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) + usbi_err("submiturb failed error %d errno=%d", r, errno); + + return r; +} + +static int op_submit_transfer(struct usbi_transfer *itransfer) +{ + struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb; + struct libusb_transfer *transfer = &itransfer->pub; + + memset(urb, 0, sizeof(*urb)); + switch (transfer->endpoint_type) { + case LIBUSB_ENDPOINT_TYPE_CONTROL: + urb->type = USBFS_URB_TYPE_CONTROL; + break; + case LIBUSB_ENDPOINT_TYPE_BULK: + urb->type = USBFS_URB_TYPE_BULK; + break; + case LIBUSB_ENDPOINT_TYPE_INTERRUPT: + urb->type = USBFS_URB_TYPE_INTERRUPT; + break; + default: + usbi_err("unknown endpoint type %d", transfer->endpoint_type); + return -EINVAL; + } + + urb->endpoint = transfer->endpoint; + return submit_transfer(itransfer); +} + +static int op_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb; + struct libusb_transfer *transfer = &itransfer->pub; + struct linux_device_handle_priv *dpriv = + __device_handle_priv(transfer->dev_handle); + + return ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urb); +} + +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 linux_transfer_priv *tpriv; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + int trf_requested; + int length; + + 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 = container_of(urb, struct linux_transfer_priv, urb); + itransfer = TRANSFER_PRIV_GET_ITRANSFER(tpriv); + transfer = &itransfer->pub; + + usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status, + urb->actual_length); + list_del(&itransfer->list); + + if (urb->status == -2) { + usbi_handle_transfer_cancellation(itransfer); + return 0; + } + + /* FIXME: research what other status codes may exist */ + if (urb->status != 0) + usbi_warn("unrecognised urb status %d", urb->status); + + /* 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); + + itransfer->transferred += urb->actual_length; + + /* if we were provided less data than requested, then our transfer is + * done */ + if (urb->actual_length < trf_requested) { + usbi_dbg("less data than requested (%d/%d) --> all done", + urb->actual_length, trf_requested); + 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; + } + + /* otherwise, we have more data to transfer */ + usbi_dbg("more data to transfer..."); + return submit_transfer(itransfer); +} + +static int op_handle_events(fd_set *readfds, fd_set *writefds) +{ + struct libusb_device_handle *handle; + int r; + + list_for_each_entry(handle, &usbi_open_devs, list) { + struct linux_device_handle_priv *hpriv = __device_handle_priv(handle); + if (!FD_ISSET(hpriv->fd, writefds)) + continue; + r = reap_for_handle(handle); + if (r == -1 && errno == EAGAIN) + continue; + if (r < 0) + return r; + } + + return 0; +} + +const struct usbi_os_backend linux_usbfs_backend = { + .name = "Linux usbfs", + .init = op_init, + .exit = NULL, + .get_device_list = op_get_device_list, + .open = op_open, + .close = op_close, + .claim_interface = op_claim_interface, + .release_interface = op_release_interface, + + .destroy_device = op_destroy_device, + + .submit_transfer = op_submit_transfer, + .cancel_transfer = op_cancel_transfer, + + .handle_events = op_handle_events, + + .device_priv_size = sizeof(struct linux_device_priv), + .device_handle_priv_size = sizeof(struct linux_device_handle_priv), + .transfer_priv_size = sizeof(struct linux_transfer_priv), +}; + diff --git a/libusb/usbfs.h b/libusb/os/linux_usbfs.h index 9daf0cc..1642959 100644 --- a/libusb/usbfs.h +++ b/libusb/os/linux_usbfs.h @@ -21,7 +21,7 @@ #ifndef __LIBUSB_USBFS_H__ #define __LIBUSB_USBFS_H__ -struct usb_ctrltransfer { +struct usbfs_ctrltransfer { /* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */ uint8_t bRequestType; uint8_t bRequest; @@ -35,7 +35,7 @@ struct usb_ctrltransfer { void *data; }; -struct usb_bulktransfer { +struct usbfs_bulktransfer { /* keep in sync with usbdevice_fs.h:usbdevfs_bulktransfer */ unsigned int ep; unsigned int len; @@ -45,31 +45,31 @@ struct usb_bulktransfer { void *data; }; -struct usb_setinterface { +struct usbfs_setinterface { /* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */ unsigned int interface; unsigned int altsetting; }; -#define USB_MAXDRIVERNAME 255 +#define USBFS_MAXDRIVERNAME 255 -struct usb_getdriver { +struct usbfs_getdriver { unsigned int interface; - char driver[USB_MAXDRIVERNAME + 1]; + char driver[USBFS_MAXDRIVERNAME + 1]; }; -#define USB_URB_DISABLE_SPD 1 -#define USB_URB_ISO_ASAP 2 -#define USB_URB_QUEUE_BULK 0x10 +#define USBFS_URB_DISABLE_SPD 1 +#define USBFS_URB_ISO_ASAP 2 +#define USBFS_URB_QUEUE_BULK 0x10 -enum usb_urb_type { - USB_URB_TYPE_ISO = 0, - USB_URB_TYPE_INTERRUPT = 1, - USB_URB_TYPE_CONTROL = 2, - USB_URB_TYPE_BULK = 3, +enum usbfs_urb_type { + USBFS_URB_TYPE_ISO = 0, + USBFS_URB_TYPE_INTERRUPT = 1, + USBFS_URB_TYPE_CONTROL = 2, + USBFS_URB_TYPE_BULK = 3, }; -struct usb_iso_packet_desc { +struct usbfs_iso_packet_desc { unsigned int length; unsigned int actual_length; unsigned int status; @@ -77,7 +77,7 @@ struct usb_iso_packet_desc { #define MAX_URB_BUFFER_LENGTH 16384 -struct usb_urb { +struct usbfs_urb { unsigned char type; unsigned char endpoint; int status; @@ -90,44 +90,44 @@ struct usb_urb { int error_count; unsigned int signr; void *usercontext; - struct usb_iso_packet_desc iso_frame_desc[0]; + struct usbfs_iso_packet_desc iso_frame_desc[0]; }; -struct usb_connectinfo { +struct usbfs_connectinfo { unsigned int devnum; unsigned char slow; }; -struct usb_ioctl { +struct usbfs_ioctl { int ifno; /* interface 0..N ; negative numbers reserved */ int ioctl_code; /* MUST encode size + direction of data so the * macros in <asm/ioctl.h> give correct values */ void *data; /* param buffer (in, or out) */ }; -struct usb_hub_portinfo { +struct usbfs_hub_portinfo { unsigned char numports; unsigned char port[127]; /* port to device num mapping */ }; -#define IOCTL_USB_CONTROL _IOWR('U', 0, struct usb_ctrltransfer) -#define IOCTL_USB_BULK _IOWR('U', 2, struct usb_bulktransfer) -#define IOCTL_USB_RESETEP _IOR('U', 3, unsigned int) -#define IOCTL_USB_SETINTF _IOR('U', 4, struct usb_setinterface) -#define IOCTL_USB_SETCONFIG _IOR('U', 5, unsigned int) -#define IOCTL_USB_GETDRIVER _IOW('U', 8, struct usb_getdriver) -#define IOCTL_USB_SUBMITURB _IOR('U', 10, struct usb_urb) -#define IOCTL_USB_DISCARDURB _IO('U', 11) -#define IOCTL_USB_REAPURB _IOW('U', 12, void *) -#define IOCTL_USB_REAPURBNDELAY _IOW('U', 13, void *) -#define IOCTL_USB_CLAIMINTF _IOR('U', 15, unsigned int) -#define IOCTL_USB_RELEASEINTF _IOR('U', 16, unsigned int) -#define IOCTL_USB_CONNECTINFO _IOW('U', 17, struct usb_connectinfo) -#define IOCTL_USB_IOCTL _IOWR('U', 18, struct usb_ioctl) -#define IOCTL_USB_HUB_PORTINFO _IOR('U', 19, struct usb_hub_portinfo) -#define IOCTL_USB_RESET _IO('U', 20) -#define IOCTL_USB_CLEAR_HALT _IOR('U', 21, unsigned int) -#define IOCTL_USB_DISCONNECT _IO('U', 22) -#define IOCTL_USB_CONNECT _IO('U', 23) +#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) +#define IOCTL_USBFS_SETINTF _IOR('U', 4, struct usbfs_setinterface) +#define IOCTL_USBFS_SETCONFIG _IOR('U', 5, unsigned int) +#define IOCTL_USBFS_GETDRIVER _IOW('U', 8, struct usbfs_getdriver) +#define IOCTL_USBFS_SUBMITURB _IOR('U', 10, struct usbfs_urb) +#define IOCTL_USBFS_DISCARDURB _IO('U', 11) +#define IOCTL_USBFS_REAPURB _IOW('U', 12, void *) +#define IOCTL_USBFS_REAPURBNDELAY _IOW('U', 13, void *) +#define IOCTL_USBFS_CLAIMINTF _IOR('U', 15, unsigned int) +#define IOCTL_USBFS_RELEASEINTF _IOR('U', 16, unsigned int) +#define IOCTL_USBFS_CONNECTINFO _IOW('U', 17, struct usbfs_connectinfo) +#define IOCTL_USBFS_IOCTL _IOWR('U', 18, struct usbfs_ioctl) +#define IOCTL_USBFS_HUB_PORTINFO _IOR('U', 19, struct usbfs_hub_portinfo) +#define IOCTL_USBFS_RESET _IO('U', 20) +#define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) #endif |