diff options
Diffstat (limited to 'lib/libusb/libusb10_io.c')
-rw-r--r-- | lib/libusb/libusb10_io.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/lib/libusb/libusb10_io.c b/lib/libusb/libusb10_io.c new file mode 100644 index 0000000..302fdb8 --- /dev/null +++ b/lib/libusb/libusb10_io.c @@ -0,0 +1,742 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2009 Sylvestre Gallon. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/queue.h> + +#include <errno.h> +#include <poll.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#define libusb_device_handle libusb20_device + +#include "libusb20.h" +#include "libusb20_desc.h" +#include "libusb20_int.h" +#include "libusb.h" +#include "libusb10.h" + +UNEXPORTED void +libusb10_add_pollfd(libusb_context *ctx, struct libusb_super_pollfd *pollfd, + struct libusb20_device *pdev, int fd, short events) +{ + if (ctx == NULL) + return; /* invalid */ + + if (pollfd->entry.tqe_prev != NULL) + return; /* already queued */ + + if (fd < 0) + return; /* invalid */ + + pollfd->pdev = pdev; + pollfd->pollfd.fd = fd; + pollfd->pollfd.events = events; + + CTX_LOCK(ctx); + TAILQ_INSERT_TAIL(&ctx->pollfds, pollfd, entry); + CTX_UNLOCK(ctx); + + if (ctx->fd_added_cb) + ctx->fd_added_cb(fd, events, ctx->fd_cb_user_data); +} + +UNEXPORTED void +libusb10_remove_pollfd(libusb_context *ctx, struct libusb_super_pollfd *pollfd) +{ + if (ctx == NULL) + return; /* invalid */ + + if (pollfd->entry.tqe_prev == NULL) + return; /* already dequeued */ + + CTX_LOCK(ctx); + TAILQ_REMOVE(&ctx->pollfds, pollfd, entry); + pollfd->entry.tqe_prev = NULL; + CTX_UNLOCK(ctx); + + if (ctx->fd_removed_cb) + ctx->fd_removed_cb(pollfd->pollfd.fd, ctx->fd_cb_user_data); +} + +/* This function must be called locked */ + +static int +libusb10_handle_events_sub(struct libusb_context *ctx, struct timeval *tv) +{ + struct libusb_device *dev; + struct libusb20_device **ppdev; + struct libusb_super_pollfd *pfd; + struct pollfd *fds; + struct libusb_super_transfer *sxfer; + struct libusb_transfer *uxfer; + nfds_t nfds; + int timeout; + int i; + int err; + + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb10_handle_events_sub enter"); + + nfds = 0; + i = 0; + TAILQ_FOREACH(pfd, &ctx->pollfds, entry) + nfds++; + + fds = alloca(sizeof(*fds) * nfds); + if (fds == NULL) + return (LIBUSB_ERROR_NO_MEM); + + ppdev = alloca(sizeof(*ppdev) * nfds); + if (ppdev == NULL) + return (LIBUSB_ERROR_NO_MEM); + + TAILQ_FOREACH(pfd, &ctx->pollfds, entry) { + fds[i].fd = pfd->pollfd.fd; + fds[i].events = pfd->pollfd.events; + fds[i].revents = 0; + ppdev[i] = pfd->pdev; + if (pfd->pdev != NULL) + libusb_get_device(pfd->pdev)->refcnt++; + i++; + } + + if (tv == NULL) + timeout = -1; + else + timeout = (tv->tv_sec * 1000) + ((tv->tv_usec + 999) / 1000); + + CTX_UNLOCK(ctx); + err = poll(fds, nfds, timeout); + CTX_LOCK(ctx); + + if ((err == -1) && (errno == EINTR)) + err = LIBUSB_ERROR_INTERRUPTED; + else if (err < 0) + err = LIBUSB_ERROR_IO; + + if (err < 1) { + for (i = 0; i != (int)nfds; i++) { + if (ppdev[i] != NULL) { + CTX_UNLOCK(ctx); + libusb_unref_device(libusb_get_device(ppdev[i])); + CTX_LOCK(ctx); + } + } + goto do_done; + } + for (i = 0; i != (int)nfds; i++) { + if (ppdev[i] != NULL) { + dev = libusb_get_device(ppdev[i]); + + if (fds[i].revents == 0) + err = 0; /* nothing to do */ + else + err = libusb20_dev_process(ppdev[i]); + + if (err) { + /* cancel all transfers - device is gone */ + libusb10_cancel_all_transfer(dev); + + /* remove USB device from polling loop */ + libusb10_remove_pollfd(dev->ctx, &dev->dev_poll); + } + CTX_UNLOCK(ctx); + libusb_unref_device(dev); + CTX_LOCK(ctx); + + } else { + uint8_t dummy; + + while (1) { + if (read(fds[i].fd, &dummy, 1) != 1) + break; + } + } + } + + err = 0; + +do_done: + + /* Do all done callbacks */ + + while ((sxfer = TAILQ_FIRST(&ctx->tr_done))) { + uint8_t flags; + + TAILQ_REMOVE(&ctx->tr_done, sxfer, entry); + sxfer->entry.tqe_prev = NULL; + + ctx->tr_done_ref++; + + CTX_UNLOCK(ctx); + + uxfer = (struct libusb_transfer *)( + ((uint8_t *)sxfer) + sizeof(*sxfer)); + + /* Allow the callback to free the transfer itself. */ + flags = uxfer->flags; + + if (uxfer->callback != NULL) + (uxfer->callback) (uxfer); + + /* Check if the USB transfer should be automatically freed. */ + if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) + libusb_free_transfer(uxfer); + + CTX_LOCK(ctx); + + ctx->tr_done_ref--; + ctx->tr_done_gen++; + } + + /* Wakeup other waiters */ + pthread_cond_broadcast(&ctx->ctx_cond); + + return (err); +} + +/* Polling and timing */ + +int +libusb_try_lock_events(libusb_context *ctx) +{ + int err; + + ctx = GET_CONTEXT(ctx); + if (ctx == NULL) + return (1); + + err = CTX_TRYLOCK(ctx); + if (err) + return (1); + + err = (ctx->ctx_handler != NO_THREAD); + if (err) + CTX_UNLOCK(ctx); + else + ctx->ctx_handler = pthread_self(); + + return (err); +} + +void +libusb_lock_events(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + CTX_LOCK(ctx); + if (ctx->ctx_handler == NO_THREAD) + ctx->ctx_handler = pthread_self(); +} + +void +libusb_unlock_events(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + if (ctx->ctx_handler == pthread_self()) { + ctx->ctx_handler = NO_THREAD; + pthread_cond_broadcast(&ctx->ctx_cond); + } + CTX_UNLOCK(ctx); +} + +int +libusb_event_handling_ok(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + return (ctx->ctx_handler == pthread_self()); +} + +int +libusb_event_handler_active(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + return (ctx->ctx_handler != NO_THREAD); +} + +void +libusb_lock_event_waiters(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + CTX_LOCK(ctx); +} + +void +libusb_unlock_event_waiters(libusb_context *ctx) +{ + ctx = GET_CONTEXT(ctx); + CTX_UNLOCK(ctx); +} + +int +libusb_wait_for_event(libusb_context *ctx, struct timeval *tv) +{ + struct timespec ts; + int err; + + ctx = GET_CONTEXT(ctx); + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_wait_for_event enter"); + + if (tv == NULL) { + pthread_cond_wait(&ctx->ctx_cond, + &ctx->ctx_lock); + return (0); + } + err = clock_gettime(CLOCK_MONOTONIC, &ts); + if (err < 0) + return (LIBUSB_ERROR_OTHER); + + /* + * The "tv" arguments points to a relative time structure and + * not an absolute time structure. + */ + ts.tv_sec += tv->tv_sec; + ts.tv_nsec += tv->tv_usec * 1000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + err = pthread_cond_timedwait(&ctx->ctx_cond, + &ctx->ctx_lock, &ts); + + if (err == ETIMEDOUT) + return (1); + + return (0); +} + +int +libusb_handle_events_timeout(libusb_context *ctx, struct timeval *tv) +{ + int err; + + ctx = GET_CONTEXT(ctx); + + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_handle_events_timeout enter"); + + libusb_lock_events(ctx); + + err = libusb_handle_events_locked(ctx, tv); + + libusb_unlock_events(ctx); + + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_handle_events_timeout leave"); + + return (err); +} + +int +libusb_handle_events(libusb_context *ctx) +{ + return (libusb_handle_events_timeout(ctx, NULL)); +} + +int +libusb_handle_events_locked(libusb_context *ctx, struct timeval *tv) +{ + int err; + + ctx = GET_CONTEXT(ctx); + + if (libusb_event_handling_ok(ctx)) { + err = libusb10_handle_events_sub(ctx, tv); + } else { + libusb_wait_for_event(ctx, tv); + err = 0; + } + return (err); +} + +int +libusb_get_next_timeout(libusb_context *ctx, struct timeval *tv) +{ + /* all timeouts are currently being done by the kernel */ + timerclear(tv); + return (0); +} + +void +libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data) +{ + ctx = GET_CONTEXT(ctx); + + ctx->fd_added_cb = added_cb; + ctx->fd_removed_cb = removed_cb; + ctx->fd_cb_user_data = user_data; +} + +struct libusb_pollfd ** +libusb_get_pollfds(libusb_context *ctx) +{ + struct libusb_super_pollfd *pollfd; + libusb_pollfd **ret; + int i; + + ctx = GET_CONTEXT(ctx); + + CTX_LOCK(ctx); + + i = 0; + TAILQ_FOREACH(pollfd, &ctx->pollfds, entry) + i++; + + ret = calloc(i + 1, sizeof(struct libusb_pollfd *)); + if (ret == NULL) + goto done; + + i = 0; + TAILQ_FOREACH(pollfd, &ctx->pollfds, entry) + ret[i++] = &pollfd->pollfd; + ret[i] = NULL; + +done: + CTX_UNLOCK(ctx); + return (ret); +} + + +/* Synchronous device I/O */ + +int +libusb_control_transfer(libusb_device_handle *devh, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint8_t *data, uint16_t wLength, unsigned int timeout) +{ + struct LIBUSB20_CONTROL_SETUP_DECODED req; + int err; + uint16_t actlen; + + if (devh == NULL) + return (LIBUSB_ERROR_INVALID_PARAM); + + if ((wLength != 0) && (data == NULL)) + return (LIBUSB_ERROR_INVALID_PARAM); + + LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req); + + req.bmRequestType = bmRequestType; + req.bRequest = bRequest; + req.wValue = wValue; + req.wIndex = wIndex; + req.wLength = wLength; + + err = libusb20_dev_request_sync(devh, &req, data, + &actlen, timeout, 0); + + if (err == LIBUSB20_ERROR_PIPE) + return (LIBUSB_ERROR_PIPE); + else if (err == LIBUSB20_ERROR_TIMEOUT) + return (LIBUSB_ERROR_TIMEOUT); + else if (err) + return (LIBUSB_ERROR_NO_DEVICE); + + return (actlen); +} + +static void +libusb10_do_transfer_cb(struct libusb_transfer *transfer) +{ + libusb_context *ctx; + int *pdone; + + ctx = GET_CONTEXT(NULL); + + DPRINTF(ctx, LIBUSB_DEBUG_TRANSFER, "sync I/O done"); + + pdone = transfer->user_data; + *pdone = 1; +} + +/* + * TODO: Replace the following function. Allocating and freeing on a + * per-transfer basis is slow. --HPS + */ +static int +libusb10_do_transfer(libusb_device_handle *devh, + uint8_t endpoint, uint8_t *data, int length, + int *transferred, unsigned int timeout, int type) +{ + libusb_context *ctx; + struct libusb_transfer *xfer; + int done; + int ret; + + if (devh == NULL) + return (LIBUSB_ERROR_INVALID_PARAM); + + if ((length != 0) && (data == NULL)) + return (LIBUSB_ERROR_INVALID_PARAM); + + xfer = libusb_alloc_transfer(0); + if (xfer == NULL) + return (LIBUSB_ERROR_NO_MEM); + + ctx = libusb_get_device(devh)->ctx; + + xfer->dev_handle = devh; + xfer->endpoint = endpoint; + xfer->type = type; + xfer->timeout = timeout; + xfer->buffer = data; + xfer->length = length; + xfer->user_data = (void *)&done; + xfer->callback = libusb10_do_transfer_cb; + done = 0; + + if ((ret = libusb_submit_transfer(xfer)) < 0) { + libusb_free_transfer(xfer); + return (ret); + } + while (done == 0) { + if ((ret = libusb_handle_events(ctx)) < 0) { + libusb_cancel_transfer(xfer); + usleep(1000); /* nice it */ + } + } + + *transferred = xfer->actual_length; + + switch (xfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + ret = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + ret = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_OVERFLOW: + ret = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_STALL: + ret = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ret = LIBUSB_ERROR_NO_DEVICE; + break; + default: + ret = LIBUSB_ERROR_OTHER; + break; + } + + libusb_free_transfer(xfer); + return (ret); +} + +int +libusb_bulk_transfer(libusb_device_handle *devh, + uint8_t endpoint, uint8_t *data, int length, + int *transferred, unsigned int timeout) +{ + libusb_context *ctx; + int ret; + + ctx = GET_CONTEXT(NULL); + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_bulk_transfer enter"); + + ret = libusb10_do_transfer(devh, endpoint, data, length, transferred, + timeout, LIBUSB_TRANSFER_TYPE_BULK); + + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_bulk_transfer leave"); + return (ret); +} + +int +libusb_interrupt_transfer(libusb_device_handle *devh, + uint8_t endpoint, uint8_t *data, int length, + int *transferred, unsigned int timeout) +{ + libusb_context *ctx; + int ret; + + ctx = GET_CONTEXT(NULL); + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_interrupt_transfer enter"); + + ret = libusb10_do_transfer(devh, endpoint, data, length, transferred, + timeout, LIBUSB_TRANSFER_TYPE_INTERRUPT); + + DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_interrupt_transfer leave"); + return (ret); +} + +uint8_t * +libusb_get_iso_packet_buffer(struct libusb_transfer *transfer, uint32_t off) +{ + uint8_t *ptr; + uint32_t n; + + if (transfer->num_iso_packets < 0) + return (NULL); + + if (off >= (uint32_t)transfer->num_iso_packets) + return (NULL); + + ptr = transfer->buffer; + if (ptr == NULL) + return (NULL); + + for (n = 0; n != off; n++) { + ptr += transfer->iso_packet_desc[n].length; + } + return (ptr); +} + +uint8_t * +libusb_get_iso_packet_buffer_simple(struct libusb_transfer *transfer, uint32_t off) +{ + uint8_t *ptr; + + if (transfer->num_iso_packets < 0) + return (NULL); + + if (off >= (uint32_t)transfer->num_iso_packets) + return (NULL); + + ptr = transfer->buffer; + if (ptr == NULL) + return (NULL); + + ptr += transfer->iso_packet_desc[0].length * off; + + return (ptr); +} + +void +libusb_set_iso_packet_lengths(struct libusb_transfer *transfer, uint32_t length) +{ + int n; + + if (transfer->num_iso_packets < 0) + return; + + for (n = 0; n != transfer->num_iso_packets; n++) + transfer->iso_packet_desc[n].length = length; +} + +uint8_t * +libusb_control_transfer_get_data(struct libusb_transfer *transfer) +{ + if (transfer->buffer == NULL) + return (NULL); + + return (transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE); +} + +struct libusb_control_setup * +libusb_control_transfer_get_setup(struct libusb_transfer *transfer) +{ + return ((struct libusb_control_setup *)transfer->buffer); +} + +void +libusb_fill_control_setup(uint8_t *buf, uint8_t bmRequestType, + uint8_t bRequest, uint16_t wValue, + uint16_t wIndex, uint16_t wLength) +{ + struct libusb_control_setup *req = (struct libusb_control_setup *)buf; + + /* The alignment is OK for all fields below. */ + req->bmRequestType = bmRequestType; + req->bRequest = bRequest; + req->wValue = htole16(wValue); + req->wIndex = htole16(wIndex); + req->wLength = htole16(wLength); +} + +void +libusb_fill_control_transfer(struct libusb_transfer *transfer, + libusb_device_handle *devh, uint8_t *buf, + libusb_transfer_cb_fn callback, void *user_data, + uint32_t timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)buf; + + transfer->dev_handle = devh; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buf; + if (setup != NULL) + transfer->length = LIBUSB_CONTROL_SETUP_SIZE + + le16toh(setup->wLength); + else + transfer->length = 0; + transfer->user_data = user_data; + transfer->callback = callback; + +} + +void +libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *devh, uint8_t endpoint, uint8_t *buf, + int length, libusb_transfer_cb_fn callback, void *user_data, + uint32_t timeout) +{ + transfer->dev_handle = devh; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buf; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +void +libusb_fill_interrupt_transfer(struct libusb_transfer *transfer, + libusb_device_handle *devh, uint8_t endpoint, uint8_t *buf, + int length, libusb_transfer_cb_fn callback, void *user_data, + uint32_t timeout) +{ + transfer->dev_handle = devh; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buf; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +void +libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *devh, uint8_t endpoint, uint8_t *buf, + int length, int npacket, libusb_transfer_cb_fn callback, + void *user_data, uint32_t timeout) +{ + transfer->dev_handle = devh; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buf; + transfer->length = length; + transfer->num_iso_packets = npacket; + transfer->user_data = user_data; + transfer->callback = callback; +} + |