freebsd-skq/lib/libusb/libusb10.c
Hans Petter Selasky 31f7072c3b Fix LibUSB v1.0 compliancy.
1) We need to allow the USB callback to free the USB transfer itself.
2) The USB transfer buffer should only be automatically freed when
freeing the USB transfer.

Fixed by:	hselasky
Submitted by:	Gustau Perez i Querol
Approved by:	thompsa (mentor)
2010-11-13 19:25:11 +00:00

1444 lines
33 KiB
C

/* $FreeBSD$ */
/*-
* Copyright (c) 2009 Sylvestre Gallon. All rights reserved.
* Copyright (c) 2009 Hans Petter Selasky. 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/fcntl.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.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"
static pthread_mutex_t default_context_lock = PTHREAD_MUTEX_INITIALIZER;
struct libusb_context *usbi_default_context = NULL;
/* Prototypes */
static struct libusb20_transfer *libusb10_get_transfer(struct libusb20_device *, uint8_t, uint8_t);
static int libusb10_get_maxframe(struct libusb20_device *, libusb_transfer *);
static int libusb10_get_buffsize(struct libusb20_device *, libusb_transfer *);
static int libusb10_convert_error(uint8_t status);
static void libusb10_complete_transfer(struct libusb20_transfer *, struct libusb_super_transfer *, int);
static void libusb10_isoc_proxy(struct libusb20_transfer *);
static void libusb10_bulk_intr_proxy(struct libusb20_transfer *);
static void libusb10_ctrl_proxy(struct libusb20_transfer *);
static void libusb10_submit_transfer_sub(struct libusb20_device *, uint8_t);
/* Library initialisation / deinitialisation */
void
libusb_set_debug(libusb_context *ctx, int level)
{
ctx = GET_CONTEXT(ctx);
if (ctx)
ctx->debug = level;
}
static void
libusb_set_nonblocking(int f)
{
int flags;
/*
* We ignore any failures in this function, hence the
* non-blocking flag is not critical to the operation of
* libUSB. We use F_GETFL and F_SETFL to be compatible with
* Linux.
*/
flags = fcntl(f, F_GETFL, NULL);
if (flags == -1)
return;
flags |= O_NONBLOCK;
fcntl(f, F_SETFL, flags);
}
int
libusb_init(libusb_context **context)
{
struct libusb_context *ctx;
char *debug;
int ret;
ctx = malloc(sizeof(*ctx));
if (!ctx)
return (LIBUSB_ERROR_INVALID_PARAM);
memset(ctx, 0, sizeof(*ctx));
debug = getenv("LIBUSB_DEBUG");
if (debug != NULL) {
ctx->debug = atoi(debug);
if (ctx->debug != 0)
ctx->debug_fixed = 1;
}
TAILQ_INIT(&ctx->pollfds);
TAILQ_INIT(&ctx->tr_done);
pthread_mutex_init(&ctx->ctx_lock, NULL);
pthread_cond_init(&ctx->ctx_cond, NULL);
ctx->ctx_handler = NO_THREAD;
ret = pipe(ctx->ctrl_pipe);
if (ret < 0) {
pthread_mutex_destroy(&ctx->ctx_lock);
pthread_cond_destroy(&ctx->ctx_cond);
free(ctx);
return (LIBUSB_ERROR_OTHER);
}
/* set non-blocking mode on the control pipe to avoid deadlock */
libusb_set_nonblocking(ctx->ctrl_pipe[0]);
libusb_set_nonblocking(ctx->ctrl_pipe[1]);
libusb10_add_pollfd(ctx, &ctx->ctx_poll, NULL, ctx->ctrl_pipe[0], POLLIN);
pthread_mutex_lock(&default_context_lock);
if (usbi_default_context == NULL) {
usbi_default_context = ctx;
}
pthread_mutex_unlock(&default_context_lock);
if (context)
*context = ctx;
DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_init complete");
return (0);
}
void
libusb_exit(libusb_context *ctx)
{
ctx = GET_CONTEXT(ctx);
if (ctx == NULL)
return;
/* XXX cleanup devices */
libusb10_remove_pollfd(ctx, &ctx->ctx_poll);
close(ctx->ctrl_pipe[0]);
close(ctx->ctrl_pipe[1]);
pthread_mutex_destroy(&ctx->ctx_lock);
pthread_cond_destroy(&ctx->ctx_cond);
pthread_mutex_lock(&default_context_lock);
if (ctx == usbi_default_context) {
usbi_default_context = NULL;
}
pthread_mutex_unlock(&default_context_lock);
free(ctx);
}
/* Device handling and initialisation. */
ssize_t
libusb_get_device_list(libusb_context *ctx, libusb_device ***list)
{
struct libusb20_backend *usb_backend;
struct libusb20_device *pdev;
struct libusb_device *dev;
int i;
ctx = GET_CONTEXT(ctx);
if (ctx == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (list == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
usb_backend = libusb20_be_alloc_default();
if (usb_backend == NULL)
return (LIBUSB_ERROR_NO_MEM);
/* figure out how many USB devices are present */
pdev = NULL;
i = 0;
while ((pdev = libusb20_be_device_foreach(usb_backend, pdev)))
i++;
/* allocate device pointer list */
*list = malloc((i + 1) * sizeof(void *));
if (*list == NULL) {
libusb20_be_free(usb_backend);
return (LIBUSB_ERROR_NO_MEM);
}
/* create libusb v1.0 compliant devices */
i = 0;
while ((pdev = libusb20_be_device_foreach(usb_backend, NULL))) {
dev = malloc(sizeof(*dev));
if (dev == NULL) {
while (i != 0) {
libusb_unref_device((*list)[i - 1]);
i--;
}
free(*list);
*list = NULL;
libusb20_be_free(usb_backend);
return (LIBUSB_ERROR_NO_MEM);
}
/* get device into libUSB v1.0 list */
libusb20_be_dequeue_device(usb_backend, pdev);
memset(dev, 0, sizeof(*dev));
/* init transfer queues */
TAILQ_INIT(&dev->tr_head);
/* set context we belong to */
dev->ctx = ctx;
/* link together the two structures */
dev->os_priv = pdev;
pdev->privLuData = dev;
(*list)[i] = libusb_ref_device(dev);
i++;
}
(*list)[i] = NULL;
libusb20_be_free(usb_backend);
return (i);
}
void
libusb_free_device_list(libusb_device **list, int unref_devices)
{
int i;
if (list == NULL)
return; /* be NULL safe */
if (unref_devices) {
for (i = 0; list[i] != NULL; i++)
libusb_unref_device(list[i]);
}
free(list);
}
uint8_t
libusb_get_bus_number(libusb_device *dev)
{
if (dev == NULL)
return (0); /* should not happen */
return (libusb20_dev_get_bus_number(dev->os_priv));
}
uint8_t
libusb_get_device_address(libusb_device *dev)
{
if (dev == NULL)
return (0); /* should not happen */
return (libusb20_dev_get_address(dev->os_priv));
}
int
libusb_get_max_packet_size(libusb_device *dev, uint8_t endpoint)
{
struct libusb_config_descriptor *pdconf;
struct libusb_interface *pinf;
struct libusb_interface_descriptor *pdinf;
struct libusb_endpoint_descriptor *pdend;
int i;
int j;
int k;
int ret;
if (dev == NULL)
return (LIBUSB_ERROR_NO_DEVICE);
ret = libusb_get_active_config_descriptor(dev, &pdconf);
if (ret < 0)
return (ret);
ret = LIBUSB_ERROR_NOT_FOUND;
for (i = 0; i < pdconf->bNumInterfaces; i++) {
pinf = &pdconf->interface[i];
for (j = 0; j < pinf->num_altsetting; j++) {
pdinf = &pinf->altsetting[j];
for (k = 0; k < pdinf->bNumEndpoints; k++) {
pdend = &pdinf->endpoint[k];
if (pdend->bEndpointAddress == endpoint) {
ret = pdend->wMaxPacketSize;
goto out;
}
}
}
}
out:
libusb_free_config_descriptor(pdconf);
return (ret);
}
libusb_device *
libusb_ref_device(libusb_device *dev)
{
if (dev == NULL)
return (NULL); /* be NULL safe */
CTX_LOCK(dev->ctx);
dev->refcnt++;
CTX_UNLOCK(dev->ctx);
return (dev);
}
void
libusb_unref_device(libusb_device *dev)
{
if (dev == NULL)
return; /* be NULL safe */
CTX_LOCK(dev->ctx);
dev->refcnt--;
CTX_UNLOCK(dev->ctx);
if (dev->refcnt == 0) {
libusb20_dev_free(dev->os_priv);
free(dev);
}
}
int
libusb_open(libusb_device *dev, libusb_device_handle **devh)
{
libusb_context *ctx = dev->ctx;
struct libusb20_device *pdev = dev->os_priv;
uint8_t dummy;
int err;
if (devh == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
/* set default device handle value */
*devh = NULL;
dev = libusb_ref_device(dev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
err = libusb20_dev_open(pdev, 16 * 4 /* number of endpoints */ );
if (err) {
libusb_unref_device(dev);
return (LIBUSB_ERROR_NO_MEM);
}
libusb10_add_pollfd(ctx, &dev->dev_poll, pdev, libusb20_dev_get_fd(pdev), POLLIN |
POLLOUT | POLLRDNORM | POLLWRNORM);
/* make sure our event loop detects the new device */
dummy = 0;
err = write(ctx->ctrl_pipe[1], &dummy, sizeof(dummy));
if (err < (int)sizeof(dummy)) {
/* ignore error, if any */
DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_open write failed!");
}
*devh = pdev;
return (0);
}
libusb_device_handle *
libusb_open_device_with_vid_pid(libusb_context *ctx, uint16_t vendor_id,
uint16_t product_id)
{
struct libusb_device **devs;
struct libusb20_device *pdev;
struct LIBUSB20_DEVICE_DESC_DECODED *pdesc;
int i;
int j;
ctx = GET_CONTEXT(ctx);
if (ctx == NULL)
return (NULL); /* be NULL safe */
DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_open_device_width_vid_pid enter");
if ((i = libusb_get_device_list(ctx, &devs)) < 0)
return (NULL);
for (j = 0; j < i; j++) {
pdev = devs[j]->os_priv;
pdesc = libusb20_dev_get_device_desc(pdev);
/*
* NOTE: The USB library will automatically swap the
* fields in the device descriptor to be of host
* endian type!
*/
if (pdesc->idVendor == vendor_id &&
pdesc->idProduct == product_id) {
if (libusb_open(devs[j], &pdev) < 0)
pdev = NULL;
break;
}
}
if (j == i)
pdev = NULL;
libusb_free_device_list(devs, 1);
DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_open_device_width_vid_pid leave");
return (pdev);
}
void
libusb_close(struct libusb20_device *pdev)
{
libusb_context *ctx;
struct libusb_device *dev;
uint8_t dummy;
int err;
if (pdev == NULL)
return; /* be NULL safe */
dev = libusb_get_device(pdev);
ctx = dev->ctx;
libusb10_remove_pollfd(ctx, &dev->dev_poll);
libusb20_dev_close(pdev);
/* unref will free the "pdev" when the refcount reaches zero */
libusb_unref_device(dev);
/* make sure our event loop detects the closed device */
dummy = 0;
err = write(ctx->ctrl_pipe[1], &dummy, sizeof(dummy));
if (err < (int)sizeof(dummy)) {
/* ignore error, if any */
DPRINTF(ctx, LIBUSB_DEBUG_FUNCTION, "libusb_close write failed!");
}
}
libusb_device *
libusb_get_device(struct libusb20_device *pdev)
{
if (pdev == NULL)
return (NULL);
return ((libusb_device *)pdev->privLuData);
}
int
libusb_get_configuration(struct libusb20_device *pdev, int *config)
{
struct libusb20_config *pconf;
if (pdev == NULL || config == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
pconf = libusb20_dev_alloc_config(pdev, libusb20_dev_get_config_index(pdev));
if (pconf == NULL)
return (LIBUSB_ERROR_NO_MEM);
*config = pconf->desc.bConfigurationValue;
free(pconf);
return (0);
}
int
libusb_set_configuration(struct libusb20_device *pdev, int configuration)
{
struct libusb20_config *pconf;
struct libusb_device *dev;
int err;
uint8_t i;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (configuration < 1) {
/* unconfigure */
i = 255;
} else {
for (i = 0; i != 255; i++) {
uint8_t found;
pconf = libusb20_dev_alloc_config(pdev, i);
if (pconf == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
found = (pconf->desc.bConfigurationValue
== configuration);
free(pconf);
if (found)
goto set_config;
}
return (LIBUSB_ERROR_INVALID_PARAM);
}
set_config:
libusb10_cancel_all_transfer(dev);
libusb10_remove_pollfd(dev->ctx, &dev->dev_poll);
err = libusb20_dev_set_config_index(pdev, i);
libusb10_add_pollfd(dev->ctx, &dev->dev_poll, pdev, libusb20_dev_get_fd(pdev), POLLIN |
POLLOUT | POLLRDNORM | POLLWRNORM);
return (err ? LIBUSB_ERROR_INVALID_PARAM : 0);
}
int
libusb_claim_interface(struct libusb20_device *pdev, int interface_number)
{
libusb_device *dev;
int err = 0;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (interface_number < 0 || interface_number > 31)
return (LIBUSB_ERROR_INVALID_PARAM);
CTX_LOCK(dev->ctx);
if (dev->claimed_interfaces & (1 << interface_number))
err = LIBUSB_ERROR_BUSY;
if (!err)
dev->claimed_interfaces |= (1 << interface_number);
CTX_UNLOCK(dev->ctx);
return (err);
}
int
libusb_release_interface(struct libusb20_device *pdev, int interface_number)
{
libusb_device *dev;
int err = 0;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (interface_number < 0 || interface_number > 31)
return (LIBUSB_ERROR_INVALID_PARAM);
CTX_LOCK(dev->ctx);
if (!(dev->claimed_interfaces & (1 << interface_number)))
err = LIBUSB_ERROR_NOT_FOUND;
if (!err)
dev->claimed_interfaces &= ~(1 << interface_number);
CTX_UNLOCK(dev->ctx);
return (err);
}
int
libusb_set_interface_alt_setting(struct libusb20_device *pdev,
int interface_number, int alternate_setting)
{
libusb_device *dev;
int err = 0;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (interface_number < 0 || interface_number > 31)
return (LIBUSB_ERROR_INVALID_PARAM);
CTX_LOCK(dev->ctx);
if (!(dev->claimed_interfaces & (1 << interface_number)))
err = LIBUSB_ERROR_NOT_FOUND;
CTX_UNLOCK(dev->ctx);
if (err)
return (err);
libusb10_cancel_all_transfer(dev);
libusb10_remove_pollfd(dev->ctx, &dev->dev_poll);
err = libusb20_dev_set_alt_index(pdev,
interface_number, alternate_setting);
libusb10_add_pollfd(dev->ctx, &dev->dev_poll,
pdev, libusb20_dev_get_fd(pdev),
POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM);
return (err ? LIBUSB_ERROR_OTHER : 0);
}
static struct libusb20_transfer *
libusb10_get_transfer(struct libusb20_device *pdev,
uint8_t endpoint, uint8_t index)
{
index &= 1; /* double buffering */
index |= (endpoint & LIBUSB20_ENDPOINT_ADDRESS_MASK) * 4;
if (endpoint & LIBUSB20_ENDPOINT_DIR_MASK) {
/* this is an IN endpoint */
index |= 2;
}
return (libusb20_tr_get_pointer(pdev, index));
}
int
libusb_clear_halt(struct libusb20_device *pdev, uint8_t endpoint)
{
struct libusb20_transfer *xfer;
struct libusb_device *dev;
int err;
xfer = libusb10_get_transfer(pdev, endpoint, 0);
if (xfer == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
CTX_LOCK(dev->ctx);
err = libusb20_tr_open(xfer, 0, 0, endpoint);
CTX_UNLOCK(dev->ctx);
if (err != 0 && err != LIBUSB20_ERROR_BUSY)
return (LIBUSB_ERROR_OTHER);
libusb20_tr_clear_stall_sync(xfer);
/* check if we opened the transfer */
if (err == 0) {
CTX_LOCK(dev->ctx);
libusb20_tr_close(xfer);
CTX_UNLOCK(dev->ctx);
}
return (0); /* success */
}
int
libusb_reset_device(struct libusb20_device *pdev)
{
libusb_device *dev;
int err;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
libusb10_cancel_all_transfer(dev);
libusb10_remove_pollfd(dev->ctx, &dev->dev_poll);
err = libusb20_dev_reset(pdev);
libusb10_add_pollfd(dev->ctx, &dev->dev_poll,
pdev, libusb20_dev_get_fd(pdev),
POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM);
return (err ? LIBUSB_ERROR_OTHER : 0);
}
int
libusb_check_connected(struct libusb20_device *pdev)
{
libusb_device *dev;
int err;
dev = libusb_get_device(pdev);
if (dev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
err = libusb20_dev_check_connected(pdev);
return (err ? LIBUSB_ERROR_NO_DEVICE : 0);
}
int
libusb_kernel_driver_active(struct libusb20_device *pdev, int interface)
{
if (pdev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
return (libusb20_dev_kernel_driver_active(
pdev, interface));
}
int
libusb_get_driver_np(struct libusb20_device *pdev, int interface,
char *name, int namelen)
{
return (libusb_get_driver(pdev, interface, name, namelen));
}
int
libusb_get_driver(struct libusb20_device *pdev, int interface,
char *name, int namelen)
{
char *ptr;
int err;
if (pdev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (namelen < 1)
return (LIBUSB_ERROR_INVALID_PARAM);
err = libusb20_dev_get_iface_desc(
pdev, interface, name, namelen);
if (err != 0)
return (LIBUSB_ERROR_OTHER);
/* we only want the driver name */
ptr = strstr(name, ":");
if (ptr != NULL)
*ptr = 0;
return (0);
}
int
libusb_detach_kernel_driver_np(struct libusb20_device *pdev, int interface)
{
return (libusb_detach_kernel_driver(pdev, interface));
}
int
libusb_detach_kernel_driver(struct libusb20_device *pdev, int interface)
{
int err;
if (pdev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
err = libusb20_dev_detach_kernel_driver(
pdev, interface);
return (err ? LIBUSB_ERROR_OTHER : 0);
}
int
libusb_attach_kernel_driver(struct libusb20_device *pdev, int interface)
{
if (pdev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
/* stub - currently not supported by libusb20 */
return (0);
}
/* Asynchronous device I/O */
struct libusb_transfer *
libusb_alloc_transfer(int iso_packets)
{
struct libusb_transfer *uxfer;
struct libusb_super_transfer *sxfer;
int len;
len = sizeof(struct libusb_transfer) +
sizeof(struct libusb_super_transfer) +
(iso_packets * sizeof(libusb_iso_packet_descriptor));
sxfer = malloc(len);
if (sxfer == NULL)
return (NULL);
memset(sxfer, 0, len);
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
/* set default value */
uxfer->num_iso_packets = iso_packets;
return (uxfer);
}
void
libusb_free_transfer(struct libusb_transfer *uxfer)
{
struct libusb_super_transfer *sxfer;
if (uxfer == NULL)
return; /* be NULL safe */
/* check if we should free the transfer buffer */
if (uxfer->flags & LIBUSB_TRANSFER_FREE_BUFFER)
free(uxfer->buffer);
sxfer = (struct libusb_super_transfer *)(
(uint8_t *)uxfer - sizeof(*sxfer));
free(sxfer);
}
static int
libusb10_get_maxframe(struct libusb20_device *pdev, libusb_transfer *xfer)
{
int ret;
int usb_speed;
usb_speed = libusb20_dev_get_speed(pdev);
switch (xfer->type) {
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
switch (usb_speed) {
case LIBUSB20_SPEED_LOW:
case LIBUSB20_SPEED_FULL:
ret = 60 * 1;
break;
default:
ret = 60 * 8;
break;
}
break;
case LIBUSB_TRANSFER_TYPE_CONTROL:
ret = 2;
break;
default:
ret = 1;
break;
}
return (ret);
}
static int
libusb10_get_buffsize(struct libusb20_device *pdev, libusb_transfer *xfer)
{
int ret;
int usb_speed;
usb_speed = libusb20_dev_get_speed(pdev);
switch (xfer->type) {
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
ret = 0; /* kernel will auto-select */
break;
case LIBUSB_TRANSFER_TYPE_CONTROL:
ret = 1024;
break;
default:
switch (usb_speed) {
case LIBUSB20_SPEED_LOW:
ret = 256;
break;
case LIBUSB20_SPEED_FULL:
ret = 4096;
break;
default:
ret = 16384;
break;
}
break;
}
return (ret);
}
static int
libusb10_convert_error(uint8_t status)
{
; /* indent fix */
switch (status) {
case LIBUSB20_TRANSFER_START:
case LIBUSB20_TRANSFER_COMPLETED:
return (LIBUSB_TRANSFER_COMPLETED);
case LIBUSB20_TRANSFER_OVERFLOW:
return (LIBUSB_TRANSFER_OVERFLOW);
case LIBUSB20_TRANSFER_NO_DEVICE:
return (LIBUSB_TRANSFER_NO_DEVICE);
case LIBUSB20_TRANSFER_STALL:
return (LIBUSB_TRANSFER_STALL);
case LIBUSB20_TRANSFER_CANCELLED:
return (LIBUSB_TRANSFER_CANCELLED);
case LIBUSB20_TRANSFER_TIMED_OUT:
return (LIBUSB_TRANSFER_TIMED_OUT);
default:
return (LIBUSB_TRANSFER_ERROR);
}
}
/* This function must be called locked */
static void
libusb10_complete_transfer(struct libusb20_transfer *pxfer,
struct libusb_super_transfer *sxfer, int status)
{
struct libusb_transfer *uxfer;
struct libusb_device *dev;
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
if (pxfer != NULL)
libusb20_tr_set_priv_sc1(pxfer, NULL);
/* set transfer status */
uxfer->status = status;
/* update super transfer state */
sxfer->state = LIBUSB_SUPER_XFER_ST_NONE;
dev = libusb_get_device(uxfer->dev_handle);
TAILQ_INSERT_TAIL(&dev->ctx->tr_done, sxfer, entry);
}
/* This function must be called locked */
static void
libusb10_isoc_proxy(struct libusb20_transfer *pxfer)
{
struct libusb_super_transfer *sxfer;
struct libusb_transfer *uxfer;
uint32_t actlen;
uint16_t iso_packets;
uint16_t i;
uint8_t status;
uint8_t flags;
status = libusb20_tr_get_status(pxfer);
sxfer = libusb20_tr_get_priv_sc1(pxfer);
actlen = libusb20_tr_get_actual_length(pxfer);
iso_packets = libusb20_tr_get_max_frames(pxfer);
if (sxfer == NULL)
return; /* cancelled - nothing to do */
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
if (iso_packets > uxfer->num_iso_packets)
iso_packets = uxfer->num_iso_packets;
if (iso_packets == 0)
return; /* nothing to do */
/* make sure that the number of ISOCHRONOUS packets is valid */
uxfer->num_iso_packets = iso_packets;
flags = uxfer->flags;
switch (status) {
case LIBUSB20_TRANSFER_COMPLETED:
/* update actual length */
uxfer->actual_length = actlen;
for (i = 0; i != iso_packets; i++) {
uxfer->iso_packet_desc[i].actual_length =
libusb20_tr_get_length(pxfer, i);
}
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_COMPLETED);
break;
case LIBUSB20_TRANSFER_START:
/* setup length(s) */
actlen = 0;
for (i = 0; i != iso_packets; i++) {
libusb20_tr_setup_isoc(pxfer,
&uxfer->buffer[actlen],
uxfer->iso_packet_desc[i].length, i);
actlen += uxfer->iso_packet_desc[i].length;
}
/* no remainder */
sxfer->rem_len = 0;
libusb20_tr_set_total_frames(pxfer, iso_packets);
libusb20_tr_submit(pxfer);
/* fork another USB transfer, if any */
libusb10_submit_transfer_sub(libusb20_tr_get_priv_sc0(pxfer), uxfer->endpoint);
break;
default:
libusb10_complete_transfer(pxfer, sxfer, libusb10_convert_error(status));
break;
}
}
/* This function must be called locked */
static void
libusb10_bulk_intr_proxy(struct libusb20_transfer *pxfer)
{
struct libusb_super_transfer *sxfer;
struct libusb_transfer *uxfer;
uint32_t max_bulk;
uint32_t actlen;
uint8_t status;
uint8_t flags;
status = libusb20_tr_get_status(pxfer);
sxfer = libusb20_tr_get_priv_sc1(pxfer);
max_bulk = libusb20_tr_get_max_total_length(pxfer);
actlen = libusb20_tr_get_actual_length(pxfer);
if (sxfer == NULL)
return; /* cancelled - nothing to do */
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
flags = uxfer->flags;
switch (status) {
case LIBUSB20_TRANSFER_COMPLETED:
uxfer->actual_length += actlen;
/* check for short packet */
if (sxfer->last_len != actlen) {
if (flags & LIBUSB_TRANSFER_SHORT_NOT_OK) {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_ERROR);
} else {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_COMPLETED);
}
break;
}
/* check for end of data */
if (sxfer->rem_len == 0) {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_COMPLETED);
break;
}
/* FALLTHROUGH */
case LIBUSB20_TRANSFER_START:
if (max_bulk > sxfer->rem_len) {
max_bulk = sxfer->rem_len;
}
/* setup new BULK or INTERRUPT transaction */
libusb20_tr_setup_bulk(pxfer,
sxfer->curr_data, max_bulk, uxfer->timeout);
/* update counters */
sxfer->last_len = max_bulk;
sxfer->curr_data += max_bulk;
sxfer->rem_len -= max_bulk;
libusb20_tr_submit(pxfer);
/* check if we can fork another USB transfer */
if (sxfer->rem_len == 0)
libusb10_submit_transfer_sub(libusb20_tr_get_priv_sc0(pxfer), uxfer->endpoint);
break;
default:
libusb10_complete_transfer(pxfer, sxfer, libusb10_convert_error(status));
break;
}
}
/* This function must be called locked */
static void
libusb10_ctrl_proxy(struct libusb20_transfer *pxfer)
{
struct libusb_super_transfer *sxfer;
struct libusb_transfer *uxfer;
uint32_t max_bulk;
uint32_t actlen;
uint8_t status;
uint8_t flags;
status = libusb20_tr_get_status(pxfer);
sxfer = libusb20_tr_get_priv_sc1(pxfer);
max_bulk = libusb20_tr_get_max_total_length(pxfer);
actlen = libusb20_tr_get_actual_length(pxfer);
if (sxfer == NULL)
return; /* cancelled - nothing to do */
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
flags = uxfer->flags;
switch (status) {
case LIBUSB20_TRANSFER_COMPLETED:
uxfer->actual_length += actlen;
/* subtract length of SETUP packet, if any */
actlen -= libusb20_tr_get_length(pxfer, 0);
/* check for short packet */
if (sxfer->last_len != actlen) {
if (flags & LIBUSB_TRANSFER_SHORT_NOT_OK) {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_ERROR);
} else {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_COMPLETED);
}
break;
}
/* check for end of data */
if (sxfer->rem_len == 0) {
libusb10_complete_transfer(pxfer, sxfer, LIBUSB_TRANSFER_COMPLETED);
break;
}
/* FALLTHROUGH */
case LIBUSB20_TRANSFER_START:
if (max_bulk > sxfer->rem_len) {
max_bulk = sxfer->rem_len;
}
/* setup new CONTROL transaction */
if (status == LIBUSB20_TRANSFER_COMPLETED) {
/* next fragment - don't send SETUP packet */
libusb20_tr_set_length(pxfer, 0, 0);
} else {
/* first fragment - send SETUP packet */
libusb20_tr_set_length(pxfer, 8, 0);
libusb20_tr_set_buffer(pxfer, uxfer->buffer, 0);
}
if (max_bulk != 0) {
libusb20_tr_set_length(pxfer, max_bulk, 1);
libusb20_tr_set_buffer(pxfer, sxfer->curr_data, 1);
libusb20_tr_set_total_frames(pxfer, 2);
} else {
libusb20_tr_set_total_frames(pxfer, 1);
}
/* update counters */
sxfer->last_len = max_bulk;
sxfer->curr_data += max_bulk;
sxfer->rem_len -= max_bulk;
libusb20_tr_submit(pxfer);
/* check if we can fork another USB transfer */
if (sxfer->rem_len == 0)
libusb10_submit_transfer_sub(libusb20_tr_get_priv_sc0(pxfer), uxfer->endpoint);
break;
default:
libusb10_complete_transfer(pxfer, sxfer, libusb10_convert_error(status));
break;
}
}
/* The following function must be called locked */
static void
libusb10_submit_transfer_sub(struct libusb20_device *pdev, uint8_t endpoint)
{
struct libusb20_transfer *pxfer0;
struct libusb20_transfer *pxfer1;
struct libusb_super_transfer *sxfer;
struct libusb_transfer *uxfer;
struct libusb_device *dev;
int err;
int buffsize;
int maxframe;
int temp;
uint8_t dummy;
dev = libusb_get_device(pdev);
pxfer0 = libusb10_get_transfer(pdev, endpoint, 0);
pxfer1 = libusb10_get_transfer(pdev, endpoint, 1);
if (pxfer0 == NULL || pxfer1 == NULL)
return; /* shouldn't happen */
temp = 0;
if (libusb20_tr_pending(pxfer0))
temp |= 1;
if (libusb20_tr_pending(pxfer1))
temp |= 2;
switch (temp) {
case 3:
/* wait till one of the transfers complete */
return;
case 2:
sxfer = libusb20_tr_get_priv_sc1(pxfer1);
if (sxfer == NULL)
return; /* cancelling */
if (sxfer->rem_len)
return; /* cannot queue another one */
/* swap transfers */
pxfer1 = pxfer0;
break;
case 1:
sxfer = libusb20_tr_get_priv_sc1(pxfer0);
if (sxfer == NULL)
return; /* cancelling */
if (sxfer->rem_len)
return; /* cannot queue another one */
/* swap transfers */
pxfer0 = pxfer1;
break;
default:
break;
}
/* find next transfer on same endpoint */
TAILQ_FOREACH(sxfer, &dev->tr_head, entry) {
uxfer = (struct libusb_transfer *)(
((uint8_t *)sxfer) + sizeof(*sxfer));
if (uxfer->endpoint == endpoint) {
TAILQ_REMOVE(&dev->tr_head, sxfer, entry);
sxfer->entry.tqe_prev = NULL;
goto found;
}
}
return; /* success */
found:
libusb20_tr_set_priv_sc0(pxfer0, pdev);
libusb20_tr_set_priv_sc1(pxfer0, sxfer);
/* reset super transfer state */
sxfer->rem_len = uxfer->length;
sxfer->curr_data = uxfer->buffer;
uxfer->actual_length = 0;
switch (uxfer->type) {
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
libusb20_tr_set_callback(pxfer0, libusb10_isoc_proxy);
break;
case LIBUSB_TRANSFER_TYPE_BULK:
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
libusb20_tr_set_callback(pxfer0, libusb10_bulk_intr_proxy);
break;
case LIBUSB_TRANSFER_TYPE_CONTROL:
libusb20_tr_set_callback(pxfer0, libusb10_ctrl_proxy);
if (sxfer->rem_len < 8)
goto failure;
/* remove SETUP packet from data */
sxfer->rem_len -= 8;
sxfer->curr_data += 8;
break;
default:
goto failure;
}
buffsize = libusb10_get_buffsize(pdev, uxfer);
maxframe = libusb10_get_maxframe(pdev, uxfer);
/* make sure the transfer is opened */
err = libusb20_tr_open(pxfer0, buffsize, maxframe, endpoint);
if (err && (err != LIBUSB20_ERROR_BUSY)) {
goto failure;
}
libusb20_tr_start(pxfer0);
return;
failure:
libusb10_complete_transfer(pxfer0, sxfer, LIBUSB_TRANSFER_ERROR);
/* make sure our event loop spins the done handler */
dummy = 0;
write(dev->ctx->ctrl_pipe[1], &dummy, sizeof(dummy));
}
/* The following function must be called unlocked */
int
libusb_submit_transfer(struct libusb_transfer *uxfer)
{
struct libusb20_transfer *pxfer0;
struct libusb20_transfer *pxfer1;
struct libusb_super_transfer *sxfer;
struct libusb_device *dev;
uint32_t endpoint;
int err;
if (uxfer == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
if (uxfer->dev_handle == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
endpoint = uxfer->endpoint;
if (endpoint > 255)
return (LIBUSB_ERROR_INVALID_PARAM);
dev = libusb_get_device(uxfer->dev_handle);
DPRINTF(dev->ctx, LIBUSB_DEBUG_FUNCTION, "libusb_submit_transfer enter");
sxfer = (struct libusb_super_transfer *)(
(uint8_t *)uxfer - sizeof(*sxfer));
CTX_LOCK(dev->ctx);
pxfer0 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 0);
pxfer1 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 1);
if (pxfer0 == NULL || pxfer1 == NULL) {
err = LIBUSB_ERROR_OTHER;
} else if ((sxfer->entry.tqe_prev != NULL) ||
(libusb20_tr_get_priv_sc1(pxfer0) == sxfer) ||
(libusb20_tr_get_priv_sc1(pxfer1) == sxfer)) {
err = LIBUSB_ERROR_BUSY;
} else {
/* set pending state */
sxfer->state = LIBUSB_SUPER_XFER_ST_PEND;
/* insert transfer into transfer head list */
TAILQ_INSERT_TAIL(&dev->tr_head, sxfer, entry);
/* start work transfers */
libusb10_submit_transfer_sub(
uxfer->dev_handle, endpoint);
err = 0; /* success */
}
CTX_UNLOCK(dev->ctx);
DPRINTF(dev->ctx, LIBUSB_DEBUG_FUNCTION, "libusb_submit_transfer leave %d", err);
return (err);
}
/* Asynchronous transfer cancel */
int
libusb_cancel_transfer(struct libusb_transfer *uxfer)
{
struct libusb20_transfer *pxfer0;
struct libusb20_transfer *pxfer1;
struct libusb_super_transfer *sxfer;
struct libusb_device *dev;
uint32_t endpoint;
int retval;
if (uxfer == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
/* check if not initialised */
if (uxfer->dev_handle == NULL)
return (LIBUSB_ERROR_NOT_FOUND);
endpoint = uxfer->endpoint;
if (endpoint > 255)
return (LIBUSB_ERROR_INVALID_PARAM);
dev = libusb_get_device(uxfer->dev_handle);
DPRINTF(dev->ctx, LIBUSB_DEBUG_FUNCTION, "libusb_cancel_transfer enter");
sxfer = (struct libusb_super_transfer *)(
(uint8_t *)uxfer - sizeof(*sxfer));
retval = 0;
CTX_LOCK(dev->ctx);
pxfer0 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 0);
pxfer1 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 1);
if (sxfer->state != LIBUSB_SUPER_XFER_ST_PEND) {
/* only update the transfer status */
uxfer->status = LIBUSB_TRANSFER_CANCELLED;
retval = LIBUSB_ERROR_NOT_FOUND;
} else if (sxfer->entry.tqe_prev != NULL) {
/* we are lucky - transfer is on a queue */
TAILQ_REMOVE(&dev->tr_head, sxfer, entry);
sxfer->entry.tqe_prev = NULL;
libusb10_complete_transfer(NULL,
sxfer, LIBUSB_TRANSFER_CANCELLED);
} else if (pxfer0 == NULL || pxfer1 == NULL) {
/* not started */
retval = LIBUSB_ERROR_NOT_FOUND;
} else if (libusb20_tr_get_priv_sc1(pxfer0) == sxfer) {
libusb10_complete_transfer(pxfer0,
sxfer, LIBUSB_TRANSFER_CANCELLED);
libusb20_tr_stop(pxfer0);
/* make sure the queue doesn't stall */
libusb10_submit_transfer_sub(
uxfer->dev_handle, endpoint);
} else if (libusb20_tr_get_priv_sc1(pxfer1) == sxfer) {
libusb10_complete_transfer(pxfer1,
sxfer, LIBUSB_TRANSFER_CANCELLED);
libusb20_tr_stop(pxfer1);
/* make sure the queue doesn't stall */
libusb10_submit_transfer_sub(
uxfer->dev_handle, endpoint);
} else {
/* not started */
retval = LIBUSB_ERROR_NOT_FOUND;
}
CTX_UNLOCK(dev->ctx);
DPRINTF(dev->ctx, LIBUSB_DEBUG_FUNCTION, "libusb_cancel_transfer leave");
return (retval);
}
UNEXPORTED void
libusb10_cancel_all_transfer(libusb_device *dev)
{
/* TODO */
}
uint16_t
libusb_cpu_to_le16(uint16_t x)
{
return (htole16(x));
}
uint16_t
libusb_le16_to_cpu(uint16_t x)
{
return (le16toh(x));
}
const char *
libusb_strerror(int code)
{
/* TODO */
return ("Unknown error");
}