freebsd-dev/lib/libusb/libusb20.c
Hans Petter Selasky dfd30b26a9 Improve the userspace USB string reading function in LibUSB.
Some USB devices does not allow a partial descriptor readout.

Found by:		bz @
MFC after:		1 week
Sponsored by:		Mellanox Technologies
2018-06-26 16:00:16 +00:00

1379 lines
29 KiB
C

/* $FreeBSD$ */
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2008-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.
*/
#ifdef LIBUSB_GLOBAL_INCLUDE_FILE
#include LIBUSB_GLOBAL_INCLUDE_FILE
#else
#include <ctype.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/queue.h>
#endif
#include "libusb20.h"
#include "libusb20_desc.h"
#include "libusb20_int.h"
static int
dummy_int(void)
{
return (LIBUSB20_ERROR_NOT_SUPPORTED);
}
static void
dummy_void(void)
{
return;
}
static void
dummy_callback(struct libusb20_transfer *xfer)
{
; /* style fix */
switch (libusb20_tr_get_status(xfer)) {
case LIBUSB20_TRANSFER_START:
libusb20_tr_submit(xfer);
break;
default:
/* complete or error */
break;
}
return;
}
#define dummy_get_config_desc_full (void *)dummy_int
#define dummy_get_config_index (void *)dummy_int
#define dummy_set_config_index (void *)dummy_int
#define dummy_set_alt_index (void *)dummy_int
#define dummy_reset_device (void *)dummy_int
#define dummy_check_connected (void *)dummy_int
#define dummy_set_power_mode (void *)dummy_int
#define dummy_get_power_mode (void *)dummy_int
#define dummy_get_power_usage (void *)dummy_int
#define dummy_kernel_driver_active (void *)dummy_int
#define dummy_detach_kernel_driver (void *)dummy_int
#define dummy_do_request_sync (void *)dummy_int
#define dummy_tr_open (void *)dummy_int
#define dummy_tr_close (void *)dummy_int
#define dummy_tr_clear_stall_sync (void *)dummy_int
#define dummy_process (void *)dummy_int
#define dummy_dev_info (void *)dummy_int
#define dummy_dev_get_iface_driver (void *)dummy_int
#define dummy_tr_submit (void *)dummy_void
#define dummy_tr_cancel_async (void *)dummy_void
static const struct libusb20_device_methods libusb20_dummy_methods = {
LIBUSB20_DEVICE(LIBUSB20_DECLARE, dummy)
};
void
libusb20_tr_callback_wrapper(struct libusb20_transfer *xfer)
{
; /* style fix */
repeat:
if (!xfer->is_pending) {
xfer->status = LIBUSB20_TRANSFER_START;
} else {
xfer->is_pending = 0;
}
xfer->callback(xfer);
if (xfer->is_restart) {
xfer->is_restart = 0;
goto repeat;
}
if (xfer->is_draining &&
(!xfer->is_pending)) {
xfer->is_draining = 0;
xfer->status = LIBUSB20_TRANSFER_DRAINED;
xfer->callback(xfer);
}
return;
}
int
libusb20_tr_close(struct libusb20_transfer *xfer)
{
int error;
if (!xfer->is_opened) {
return (LIBUSB20_ERROR_OTHER);
}
error = xfer->pdev->methods->tr_close(xfer);
if (xfer->pLength) {
free(xfer->pLength);
}
if (xfer->ppBuffer) {
free(xfer->ppBuffer);
}
/* reset variable fields in case the transfer is opened again */
xfer->priv_sc0 = NULL;
xfer->priv_sc1 = NULL;
xfer->is_opened = 0;
xfer->is_pending = 0;
xfer->is_cancel = 0;
xfer->is_draining = 0;
xfer->is_restart = 0;
xfer->status = 0;
xfer->flags = 0;
xfer->nFrames = 0;
xfer->aFrames = 0;
xfer->timeout = 0;
xfer->maxFrames = 0;
xfer->maxTotalLength = 0;
xfer->maxPacketLen = 0;
return (error);
}
int
libusb20_tr_open(struct libusb20_transfer *xfer, uint32_t MaxBufSize,
uint32_t MaxFrameCount, uint8_t ep_no)
{
return (libusb20_tr_open_stream(xfer, MaxBufSize, MaxFrameCount, ep_no, 0));
}
int
libusb20_tr_open_stream(struct libusb20_transfer *xfer, uint32_t MaxBufSize,
uint32_t MaxFrameCount, uint8_t ep_no, uint16_t stream_id)
{
uint32_t size;
uint8_t pre_scale;
int error;
if (xfer->is_opened)
return (LIBUSB20_ERROR_BUSY);
if (MaxFrameCount & LIBUSB20_MAX_FRAME_PRE_SCALE) {
MaxFrameCount &= ~LIBUSB20_MAX_FRAME_PRE_SCALE;
/*
* The kernel can setup 8 times more frames when
* pre-scaling ISOCHRONOUS transfers. Make sure the
* length and pointer buffers are big enough:
*/
MaxFrameCount *= 8;
pre_scale = 1;
} else {
pre_scale = 0;
}
if (MaxFrameCount == 0)
return (LIBUSB20_ERROR_INVALID_PARAM);
xfer->maxFrames = MaxFrameCount;
size = MaxFrameCount * sizeof(xfer->pLength[0]);
xfer->pLength = malloc(size);
if (xfer->pLength == NULL) {
return (LIBUSB20_ERROR_NO_MEM);
}
memset(xfer->pLength, 0, size);
size = MaxFrameCount * sizeof(xfer->ppBuffer[0]);
xfer->ppBuffer = malloc(size);
if (xfer->ppBuffer == NULL) {
free(xfer->pLength);
return (LIBUSB20_ERROR_NO_MEM);
}
memset(xfer->ppBuffer, 0, size);
if (pre_scale) {
error = xfer->pdev->methods->tr_open(xfer, MaxBufSize,
MaxFrameCount / 8, ep_no, stream_id, 1);
} else {
error = xfer->pdev->methods->tr_open(xfer, MaxBufSize,
MaxFrameCount, ep_no, stream_id, 0);
}
if (error) {
free(xfer->ppBuffer);
free(xfer->pLength);
} else {
xfer->is_opened = 1;
}
return (error);
}
struct libusb20_transfer *
libusb20_tr_get_pointer(struct libusb20_device *pdev, uint16_t trIndex)
{
if (trIndex >= pdev->nTransfer) {
return (NULL);
}
return (pdev->pTransfer + trIndex);
}
uint32_t
libusb20_tr_get_actual_frames(struct libusb20_transfer *xfer)
{
return (xfer->aFrames);
}
uint16_t
libusb20_tr_get_time_complete(struct libusb20_transfer *xfer)
{
return (xfer->timeComplete);
}
uint32_t
libusb20_tr_get_actual_length(struct libusb20_transfer *xfer)
{
uint32_t x;
uint32_t actlen = 0;
for (x = 0; x != xfer->aFrames; x++) {
actlen += xfer->pLength[x];
}
return (actlen);
}
uint32_t
libusb20_tr_get_max_frames(struct libusb20_transfer *xfer)
{
return (xfer->maxFrames);
}
uint32_t
libusb20_tr_get_max_packet_length(struct libusb20_transfer *xfer)
{
/*
* Special Case NOTE: If the packet multiplier is non-zero for
* High Speed USB, the value returned is equal to
* "wMaxPacketSize * multiplier" !
*/
return (xfer->maxPacketLen);
}
uint32_t
libusb20_tr_get_max_total_length(struct libusb20_transfer *xfer)
{
return (xfer->maxTotalLength);
}
uint8_t
libusb20_tr_get_status(struct libusb20_transfer *xfer)
{
return (xfer->status);
}
uint8_t
libusb20_tr_pending(struct libusb20_transfer *xfer)
{
return (xfer->is_pending);
}
void *
libusb20_tr_get_priv_sc0(struct libusb20_transfer *xfer)
{
return (xfer->priv_sc0);
}
void *
libusb20_tr_get_priv_sc1(struct libusb20_transfer *xfer)
{
return (xfer->priv_sc1);
}
void
libusb20_tr_stop(struct libusb20_transfer *xfer)
{
if (!xfer->is_opened) {
/* transfer is not opened */
return;
}
if (!xfer->is_pending) {
/* transfer not pending */
return;
}
if (xfer->is_cancel) {
/* already cancelling */
return;
}
xfer->is_cancel = 1; /* we are cancelling */
xfer->pdev->methods->tr_cancel_async(xfer);
return;
}
void
libusb20_tr_drain(struct libusb20_transfer *xfer)
{
if (!xfer->is_opened) {
/* transfer is not opened */
return;
}
/* make sure that we are cancelling */
libusb20_tr_stop(xfer);
if (xfer->is_pending) {
xfer->is_draining = 1;
}
return;
}
void
libusb20_tr_clear_stall_sync(struct libusb20_transfer *xfer)
{
xfer->pdev->methods->tr_clear_stall_sync(xfer);
return;
}
void
libusb20_tr_set_buffer(struct libusb20_transfer *xfer, void *buffer, uint16_t frIndex)
{
xfer->ppBuffer[frIndex] = libusb20_pass_ptr(buffer);
return;
}
void
libusb20_tr_set_callback(struct libusb20_transfer *xfer, libusb20_tr_callback_t *cb)
{
xfer->callback = cb;
return;
}
void
libusb20_tr_set_flags(struct libusb20_transfer *xfer, uint8_t flags)
{
xfer->flags = flags;
return;
}
uint32_t
libusb20_tr_get_length(struct libusb20_transfer *xfer, uint16_t frIndex)
{
return (xfer->pLength[frIndex]);
}
void
libusb20_tr_set_length(struct libusb20_transfer *xfer, uint32_t length, uint16_t frIndex)
{
xfer->pLength[frIndex] = length;
return;
}
void
libusb20_tr_set_priv_sc0(struct libusb20_transfer *xfer, void *sc0)
{
xfer->priv_sc0 = sc0;
return;
}
void
libusb20_tr_set_priv_sc1(struct libusb20_transfer *xfer, void *sc1)
{
xfer->priv_sc1 = sc1;
return;
}
void
libusb20_tr_set_timeout(struct libusb20_transfer *xfer, uint32_t timeout)
{
xfer->timeout = timeout;
return;
}
void
libusb20_tr_set_total_frames(struct libusb20_transfer *xfer, uint32_t nFrames)
{
if (nFrames > xfer->maxFrames) {
/* should not happen */
nFrames = xfer->maxFrames;
}
xfer->nFrames = nFrames;
return;
}
void
libusb20_tr_setup_bulk(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint32_t timeout)
{
xfer->ppBuffer[0] = libusb20_pass_ptr(pBuf);
xfer->pLength[0] = length;
xfer->timeout = timeout;
xfer->nFrames = 1;
return;
}
void
libusb20_tr_setup_control(struct libusb20_transfer *xfer, void *psetup, void *pBuf, uint32_t timeout)
{
uint16_t len;
xfer->ppBuffer[0] = libusb20_pass_ptr(psetup);
xfer->pLength[0] = 8; /* fixed */
xfer->timeout = timeout;
len = ((uint8_t *)psetup)[6] | (((uint8_t *)psetup)[7] << 8);
if (len != 0) {
xfer->nFrames = 2;
xfer->ppBuffer[1] = libusb20_pass_ptr(pBuf);
xfer->pLength[1] = len;
} else {
xfer->nFrames = 1;
}
return;
}
void
libusb20_tr_setup_intr(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint32_t timeout)
{
xfer->ppBuffer[0] = libusb20_pass_ptr(pBuf);
xfer->pLength[0] = length;
xfer->timeout = timeout;
xfer->nFrames = 1;
return;
}
void
libusb20_tr_setup_isoc(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint16_t frIndex)
{
if (frIndex >= xfer->maxFrames) {
/* should not happen */
return;
}
xfer->ppBuffer[frIndex] = libusb20_pass_ptr(pBuf);
xfer->pLength[frIndex] = length;
return;
}
uint8_t
libusb20_tr_bulk_intr_sync(struct libusb20_transfer *xfer,
void *pbuf, uint32_t length, uint32_t *pactlen,
uint32_t timeout)
{
struct libusb20_device *pdev = xfer->pdev;
uint32_t transfer_max;
uint32_t transfer_act;
uint8_t retval;
/* set some sensible default value */
if (pactlen != NULL)
*pactlen = 0;
/* check for error condition */
if (libusb20_tr_pending(xfer))
return (LIBUSB20_ERROR_OTHER);
do {
/* compute maximum transfer length */
transfer_max =
libusb20_tr_get_max_total_length(xfer);
if (transfer_max > length)
transfer_max = length;
/* setup bulk or interrupt transfer */
libusb20_tr_setup_bulk(xfer, pbuf,
transfer_max, timeout);
/* start the transfer */
libusb20_tr_start(xfer);
/* wait for transfer completion */
while (libusb20_dev_process(pdev) == 0) {
if (libusb20_tr_pending(xfer) == 0)
break;
libusb20_dev_wait_process(pdev, -1);
}
transfer_act = libusb20_tr_get_actual_length(xfer);
/* update actual length, if any */
if (pactlen != NULL)
pactlen[0] += transfer_act;
/* check transfer status */
retval = libusb20_tr_get_status(xfer);
if (retval)
break;
/* check for short transfer */
if (transfer_act != transfer_max)
break;
/* update buffer pointer and length */
pbuf = ((uint8_t *)pbuf) + transfer_max;
length = length - transfer_max;
} while (length != 0);
return (retval);
}
void
libusb20_tr_submit(struct libusb20_transfer *xfer)
{
if (!xfer->is_opened) {
/* transfer is not opened */
return;
}
if (xfer->is_pending) {
/* should not happen */
return;
}
xfer->is_pending = 1; /* we are pending */
xfer->is_cancel = 0; /* not cancelling */
xfer->is_restart = 0; /* not restarting */
xfer->pdev->methods->tr_submit(xfer);
return;
}
void
libusb20_tr_start(struct libusb20_transfer *xfer)
{
if (!xfer->is_opened) {
/* transfer is not opened */
return;
}
if (xfer->is_pending) {
if (xfer->is_cancel) {
/* cancelling - restart */
xfer->is_restart = 1;
}
/* transfer not pending */
return;
}
/* get into the callback */
libusb20_tr_callback_wrapper(xfer);
return;
}
/* USB device operations */
int
libusb20_dev_close(struct libusb20_device *pdev)
{
struct libusb20_transfer *xfer;
uint16_t x;
int error = 0;
if (!pdev->is_opened) {
return (LIBUSB20_ERROR_OTHER);
}
for (x = 0; x != pdev->nTransfer; x++) {
xfer = pdev->pTransfer + x;
if (!xfer->is_opened) {
/* transfer is not opened */
continue;
}
libusb20_tr_drain(xfer);
libusb20_tr_close(xfer);
}
if (pdev->pTransfer != NULL) {
free(pdev->pTransfer);
pdev->pTransfer = NULL;
}
error = pdev->beMethods->close_device(pdev);
pdev->methods = &libusb20_dummy_methods;
pdev->is_opened = 0;
/*
* The following variable is only used by the libusb v0.1
* compat layer:
*/
pdev->claimed_interface = 0;
/*
* The following variable is only used by the libusb v1.0
* compat layer:
*/
pdev->auto_detach = 0;
return (error);
}
int
libusb20_dev_detach_kernel_driver(struct libusb20_device *pdev, uint8_t ifaceIndex)
{
int error;
error = pdev->methods->detach_kernel_driver(pdev, ifaceIndex);
return (error);
}
struct LIBUSB20_DEVICE_DESC_DECODED *
libusb20_dev_get_device_desc(struct libusb20_device *pdev)
{
return (&(pdev->ddesc));
}
int
libusb20_dev_get_fd(struct libusb20_device *pdev)
{
return (pdev->file);
}
int
libusb20_dev_kernel_driver_active(struct libusb20_device *pdev, uint8_t ifaceIndex)
{
int error;
error = pdev->methods->kernel_driver_active(pdev, ifaceIndex);
return (error);
}
int
libusb20_dev_open(struct libusb20_device *pdev, uint16_t nTransferMax)
{
struct libusb20_transfer *xfer;
uint32_t size;
uint16_t x;
int error;
if (pdev->is_opened) {
return (LIBUSB20_ERROR_BUSY);
}
if (nTransferMax >= 256) {
return (LIBUSB20_ERROR_INVALID_PARAM);
} else if (nTransferMax != 0) {
size = sizeof(pdev->pTransfer[0]) * nTransferMax;
pdev->pTransfer = malloc(size);
if (pdev->pTransfer == NULL) {
return (LIBUSB20_ERROR_NO_MEM);
}
memset(pdev->pTransfer, 0, size);
}
/* initialise all transfers */
for (x = 0; x != nTransferMax; x++) {
xfer = pdev->pTransfer + x;
xfer->pdev = pdev;
xfer->trIndex = x;
xfer->callback = &dummy_callback;
}
/* set "nTransfer" early */
pdev->nTransfer = nTransferMax;
error = pdev->beMethods->open_device(pdev, nTransferMax);
if (error) {
if (pdev->pTransfer != NULL) {
free(pdev->pTransfer);
pdev->pTransfer = NULL;
}
pdev->file = -1;
pdev->file_ctrl = -1;
pdev->nTransfer = 0;
} else {
pdev->is_opened = 1;
}
return (error);
}
int
libusb20_dev_reset(struct libusb20_device *pdev)
{
int error;
error = pdev->methods->reset_device(pdev);
return (error);
}
int
libusb20_dev_check_connected(struct libusb20_device *pdev)
{
int error;
error = pdev->methods->check_connected(pdev);
return (error);
}
int
libusb20_dev_set_power_mode(struct libusb20_device *pdev, uint8_t power_mode)
{
int error;
error = pdev->methods->set_power_mode(pdev, power_mode);
return (error);
}
uint8_t
libusb20_dev_get_power_mode(struct libusb20_device *pdev)
{
int error;
uint8_t power_mode;
error = pdev->methods->get_power_mode(pdev, &power_mode);
if (error)
power_mode = LIBUSB20_POWER_ON; /* fake power mode */
return (power_mode);
}
int
libusb20_dev_get_port_path(struct libusb20_device *pdev, uint8_t *buf, uint8_t bufsize)
{
if (pdev->port_level == 0) {
/*
* Fallback for backends without port path:
*/
if (bufsize < 2)
return (LIBUSB20_ERROR_OVERFLOW);
buf[0] = pdev->parent_address;
buf[1] = pdev->parent_port;
return (2);
}
/* check if client buffer is too small */
if (pdev->port_level > bufsize)
return (LIBUSB20_ERROR_OVERFLOW);
/* copy port number information */
memcpy(buf, pdev->port_path, pdev->port_level);
return (pdev->port_level); /* success */
}
uint16_t
libusb20_dev_get_power_usage(struct libusb20_device *pdev)
{
int error;
uint16_t power_usage;
error = pdev->methods->get_power_usage(pdev, &power_usage);
if (error)
power_usage = 0;
return (power_usage);
}
int
libusb20_dev_set_alt_index(struct libusb20_device *pdev, uint8_t ifaceIndex, uint8_t altIndex)
{
int error;
error = pdev->methods->set_alt_index(pdev, ifaceIndex, altIndex);
return (error);
}
int
libusb20_dev_set_config_index(struct libusb20_device *pdev, uint8_t configIndex)
{
int error;
error = pdev->methods->set_config_index(pdev, configIndex);
return (error);
}
int
libusb20_dev_request_sync(struct libusb20_device *pdev,
struct LIBUSB20_CONTROL_SETUP_DECODED *setup, void *data,
uint16_t *pactlen, uint32_t timeout, uint8_t flags)
{
int error;
error = pdev->methods->do_request_sync(pdev,
setup, data, pactlen, timeout, flags);
return (error);
}
int
libusb20_dev_req_string_sync(struct libusb20_device *pdev,
uint8_t str_index, uint16_t langid, void *ptr, uint16_t len)
{
struct LIBUSB20_CONTROL_SETUP_DECODED req;
int error;
int flags;
/* make sure memory is initialised */
memset(ptr, 0, len);
if (len < 4) {
/* invalid length */
return (LIBUSB20_ERROR_INVALID_PARAM);
}
LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req);
/*
* We need to read the USB string in two steps else some USB
* devices will complain.
*/
req.bmRequestType =
LIBUSB20_REQUEST_TYPE_STANDARD |
LIBUSB20_RECIPIENT_DEVICE |
LIBUSB20_ENDPOINT_IN;
req.bRequest = LIBUSB20_REQUEST_GET_DESCRIPTOR;
req.wValue = (LIBUSB20_DT_STRING << 8) | str_index;
req.wIndex = langid;
req.wLength = 4; /* bytes */
error = libusb20_dev_request_sync(pdev, &req,
ptr, NULL, 1000, LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK);
if (error) {
/* try to request full string */
req.wLength = 255;
flags = 0;
} else {
/* extract length and request full string */
req.wLength = *(uint8_t *)ptr;
flags = LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK;
}
if (req.wLength > len) {
/* partial string read */
req.wLength = len;
}
error = libusb20_dev_request_sync(pdev, &req, ptr, NULL, 1000, flags);
if (error)
return (error);
if (((uint8_t *)ptr)[1] != LIBUSB20_DT_STRING)
return (LIBUSB20_ERROR_OTHER);
return (0); /* success */
}
int
libusb20_dev_req_string_simple_sync(struct libusb20_device *pdev,
uint8_t str_index, void *ptr, uint16_t len)
{
char *buf;
int error;
uint16_t langid;
uint16_t n;
uint16_t i;
uint16_t c;
uint8_t temp[255];
uint8_t swap;
/* the following code derives from the FreeBSD USB kernel */
if ((len < 1) || (ptr == NULL)) {
/* too short buffer */
return (LIBUSB20_ERROR_INVALID_PARAM);
}
error = libusb20_dev_req_string_sync(pdev,
0, 0, temp, sizeof(temp));
if (error < 0) {
*(uint8_t *)ptr = 0; /* zero terminate */
return (error);
}
langid = temp[2] | (temp[3] << 8);
error = libusb20_dev_req_string_sync(pdev, str_index,
langid, temp, sizeof(temp));
if (error < 0) {
*(uint8_t *)ptr = 0; /* zero terminate */
return (error);
}
if (temp[0] < 2) {
/* string length is too short */
*(uint8_t *)ptr = 0; /* zero terminate */
return (LIBUSB20_ERROR_OTHER);
}
/* reserve one byte for terminating zero */
len--;
/* find maximum length */
n = (temp[0] / 2) - 1;
if (n > len) {
n = len;
}
/* reset swap state */
swap = 3;
/* setup output buffer pointer */
buf = ptr;
/* convert and filter */
for (i = 0; (i != n); i++) {
c = temp[(2 * i) + 2] | (temp[(2 * i) + 3] << 8);
/* convert from Unicode, handle buggy strings */
if (((c & 0xff00) == 0) && (swap & 1)) {
/* Little Endian, default */
*buf = c;
swap = 1;
} else if (((c & 0x00ff) == 0) && (swap & 2)) {
/* Big Endian */
*buf = c >> 8;
swap = 2;
} else {
/* skip invalid character */
continue;
}
/*
* Filter by default - we don't allow greater and less than
* signs because they might confuse the dmesg printouts!
*/
if ((*buf == '<') || (*buf == '>') || (!isprint(*buf))) {
/* skip invalid character */
continue;
}
buf++;
}
*buf = 0; /* zero terminate string */
return (0);
}
struct libusb20_config *
libusb20_dev_alloc_config(struct libusb20_device *pdev, uint8_t configIndex)
{
struct libusb20_config *retval = NULL;
uint8_t *ptr;
uint16_t len;
uint8_t do_close;
int error;
if (!pdev->is_opened) {
error = libusb20_dev_open(pdev, 0);
if (error) {
return (NULL);
}
do_close = 1;
} else {
do_close = 0;
}
error = pdev->methods->get_config_desc_full(pdev,
&ptr, &len, configIndex);
if (error) {
goto done;
}
/* parse new config descriptor */
retval = libusb20_parse_config_desc(ptr);
/* free config descriptor */
free(ptr);
done:
if (do_close) {
error = libusb20_dev_close(pdev);
}
return (retval);
}
struct libusb20_device *
libusb20_dev_alloc(void)
{
struct libusb20_device *pdev;
pdev = malloc(sizeof(*pdev));
if (pdev == NULL) {
return (NULL);
}
memset(pdev, 0, sizeof(*pdev));
pdev->file = -1;
pdev->file_ctrl = -1;
pdev->methods = &libusb20_dummy_methods;
return (pdev);
}
uint8_t
libusb20_dev_get_config_index(struct libusb20_device *pdev)
{
int error;
uint8_t cfg_index;
uint8_t do_close;
if (!pdev->is_opened) {
error = libusb20_dev_open(pdev, 0);
if (error == 0) {
do_close = 1;
} else {
do_close = 0;
}
} else {
do_close = 0;
}
error = pdev->methods->get_config_index(pdev, &cfg_index);
if (error)
cfg_index = 0xFF; /* current config index */
if (do_close) {
if (libusb20_dev_close(pdev)) {
/* ignore */
}
}
return (cfg_index);
}
uint8_t
libusb20_dev_get_mode(struct libusb20_device *pdev)
{
return (pdev->usb_mode);
}
uint8_t
libusb20_dev_get_speed(struct libusb20_device *pdev)
{
return (pdev->usb_speed);
}
/* if this function returns an error, the device is gone */
int
libusb20_dev_process(struct libusb20_device *pdev)
{
int error;
error = pdev->methods->process(pdev);
return (error);
}
void
libusb20_dev_wait_process(struct libusb20_device *pdev, int timeout)
{
struct pollfd pfd[1];
if (!pdev->is_opened) {
return;
}
pfd[0].fd = pdev->file;
pfd[0].events = (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM);
pfd[0].revents = 0;
if (poll(pfd, 1, timeout)) {
/* ignore any error */
}
return;
}
void
libusb20_dev_free(struct libusb20_device *pdev)
{
if (pdev == NULL) {
/* be NULL safe */
return;
}
if (pdev->is_opened) {
if (libusb20_dev_close(pdev)) {
/* ignore any errors */
}
}
free(pdev);
return;
}
int
libusb20_dev_get_info(struct libusb20_device *pdev,
struct usb_device_info *pinfo)
{
if (pinfo == NULL)
return (LIBUSB20_ERROR_INVALID_PARAM);
return (pdev->beMethods->dev_get_info(pdev, pinfo));
}
const char *
libusb20_dev_get_backend_name(struct libusb20_device *pdev)
{
return (pdev->beMethods->get_backend_name());
}
const char *
libusb20_dev_get_desc(struct libusb20_device *pdev)
{
return (pdev->usb_desc);
}
void
libusb20_dev_set_debug(struct libusb20_device *pdev, int debug)
{
pdev->debug = debug;
return;
}
int
libusb20_dev_get_debug(struct libusb20_device *pdev)
{
return (pdev->debug);
}
uint8_t
libusb20_dev_get_address(struct libusb20_device *pdev)
{
return (pdev->device_address);
}
uint8_t
libusb20_dev_get_parent_address(struct libusb20_device *pdev)
{
return (pdev->parent_address);
}
uint8_t
libusb20_dev_get_parent_port(struct libusb20_device *pdev)
{
return (pdev->parent_port);
}
uint8_t
libusb20_dev_get_bus_number(struct libusb20_device *pdev)
{
return (pdev->bus_number);
}
int
libusb20_dev_get_iface_desc(struct libusb20_device *pdev,
uint8_t iface_index, char *buf, uint8_t len)
{
if ((buf == NULL) || (len == 0))
return (LIBUSB20_ERROR_INVALID_PARAM);
buf[0] = 0; /* set default string value */
return (pdev->beMethods->dev_get_iface_desc(
pdev, iface_index, buf, len));
}
/* USB backend operations */
int
libusb20_be_get_dev_quirk(struct libusb20_backend *pbe,
uint16_t quirk_index, struct libusb20_quirk *pq)
{
return (pbe->methods->root_get_dev_quirk(pbe, quirk_index, pq));
}
int
libusb20_be_get_quirk_name(struct libusb20_backend *pbe,
uint16_t quirk_index, struct libusb20_quirk *pq)
{
return (pbe->methods->root_get_quirk_name(pbe, quirk_index, pq));
}
int
libusb20_be_add_dev_quirk(struct libusb20_backend *pbe,
struct libusb20_quirk *pq)
{
return (pbe->methods->root_add_dev_quirk(pbe, pq));
}
int
libusb20_be_remove_dev_quirk(struct libusb20_backend *pbe,
struct libusb20_quirk *pq)
{
return (pbe->methods->root_remove_dev_quirk(pbe, pq));
}
int
libusb20_be_set_template(struct libusb20_backend *pbe, int temp)
{
return (pbe->methods->root_set_template(pbe, temp));
}
int
libusb20_be_get_template(struct libusb20_backend *pbe, int *ptemp)
{
int temp;
if (ptemp == NULL)
ptemp = &temp;
return (pbe->methods->root_get_template(pbe, ptemp));
}
struct libusb20_device *
libusb20_be_device_foreach(struct libusb20_backend *pbe, struct libusb20_device *pdev)
{
if (pbe == NULL) {
pdev = NULL;
} else if (pdev == NULL) {
pdev = TAILQ_FIRST(&(pbe->usb_devs));
} else {
pdev = TAILQ_NEXT(pdev, dev_entry);
}
return (pdev);
}
struct libusb20_backend *
libusb20_be_alloc(const struct libusb20_backend_methods *methods)
{
struct libusb20_backend *pbe;
pbe = malloc(sizeof(*pbe));
if (pbe == NULL) {
return (NULL);
}
memset(pbe, 0, sizeof(*pbe));
TAILQ_INIT(&(pbe->usb_devs));
pbe->methods = methods; /* set backend methods */
/* do the initial device scan */
if (pbe->methods->init_backend) {
pbe->methods->init_backend(pbe);
}
return (pbe);
}
struct libusb20_backend *
libusb20_be_alloc_linux(void)
{
return (NULL);
}
struct libusb20_backend *
libusb20_be_alloc_ugen20(void)
{
return (libusb20_be_alloc(&libusb20_ugen20_backend));
}
struct libusb20_backend *
libusb20_be_alloc_default(void)
{
struct libusb20_backend *pbe;
#ifdef __linux__
pbe = libusb20_be_alloc_linux();
if (pbe) {
return (pbe);
}
#endif
pbe = libusb20_be_alloc_ugen20();
if (pbe) {
return (pbe);
}
return (NULL); /* no backend found */
}
void
libusb20_be_free(struct libusb20_backend *pbe)
{
struct libusb20_device *pdev;
if (pbe == NULL) {
/* be NULL safe */
return;
}
while ((pdev = libusb20_be_device_foreach(pbe, NULL))) {
libusb20_be_dequeue_device(pbe, pdev);
libusb20_dev_free(pdev);
}
if (pbe->methods->exit_backend) {
pbe->methods->exit_backend(pbe);
}
/* free backend */
free(pbe);
}
void
libusb20_be_enqueue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev)
{
pdev->beMethods = pbe->methods; /* copy backend methods */
TAILQ_INSERT_TAIL(&(pbe->usb_devs), pdev, dev_entry);
}
void
libusb20_be_dequeue_device(struct libusb20_backend *pbe,
struct libusb20_device *pdev)
{
TAILQ_REMOVE(&(pbe->usb_devs), pdev, dev_entry);
}
const char *
libusb20_strerror(int code)
{
switch (code) {
case LIBUSB20_SUCCESS:
return ("Success");
case LIBUSB20_ERROR_IO:
return ("I/O error");
case LIBUSB20_ERROR_INVALID_PARAM:
return ("Invalid parameter");
case LIBUSB20_ERROR_ACCESS:
return ("Permissions error");
case LIBUSB20_ERROR_NO_DEVICE:
return ("No device");
case LIBUSB20_ERROR_NOT_FOUND:
return ("Not found");
case LIBUSB20_ERROR_BUSY:
return ("Device busy");
case LIBUSB20_ERROR_TIMEOUT:
return ("Timeout");
case LIBUSB20_ERROR_OVERFLOW:
return ("Overflow");
case LIBUSB20_ERROR_PIPE:
return ("Pipe error");
case LIBUSB20_ERROR_INTERRUPTED:
return ("Interrupted");
case LIBUSB20_ERROR_NO_MEM:
return ("Out of memory");
case LIBUSB20_ERROR_NOT_SUPPORTED:
return ("Not supported");
case LIBUSB20_ERROR_OTHER:
return ("Other error");
default:
return ("Unknown error");
}
}
const char *
libusb20_error_name(int code)
{
switch (code) {
case LIBUSB20_SUCCESS:
return ("LIBUSB20_SUCCESS");
case LIBUSB20_ERROR_IO:
return ("LIBUSB20_ERROR_IO");
case LIBUSB20_ERROR_INVALID_PARAM:
return ("LIBUSB20_ERROR_INVALID_PARAM");
case LIBUSB20_ERROR_ACCESS:
return ("LIBUSB20_ERROR_ACCESS");
case LIBUSB20_ERROR_NO_DEVICE:
return ("LIBUSB20_ERROR_NO_DEVICE");
case LIBUSB20_ERROR_NOT_FOUND:
return ("LIBUSB20_ERROR_NOT_FOUND");
case LIBUSB20_ERROR_BUSY:
return ("LIBUSB20_ERROR_BUSY");
case LIBUSB20_ERROR_TIMEOUT:
return ("LIBUSB20_ERROR_TIMEOUT");
case LIBUSB20_ERROR_OVERFLOW:
return ("LIBUSB20_ERROR_OVERFLOW");
case LIBUSB20_ERROR_PIPE:
return ("LIBUSB20_ERROR_PIPE");
case LIBUSB20_ERROR_INTERRUPTED:
return ("LIBUSB20_ERROR_INTERRUPTED");
case LIBUSB20_ERROR_NO_MEM:
return ("LIBUSB20_ERROR_NO_MEM");
case LIBUSB20_ERROR_NOT_SUPPORTED:
return ("LIBUSB20_ERROR_NOT_SUPPORTED");
case LIBUSB20_ERROR_OTHER:
return ("LIBUSB20_ERROR_OTHER");
default:
return ("LIBUSB20_ERROR_UNKNOWN");
}
}