You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

606 lines
16 KiB

/*
* $Id: libusb.c,v 1.33 2007/02/09 16:06:22 desrod Exp $
*
* libusb.c: device i/o for libusb
*
* Copyright (c) 2004 Zephaniah E. Hull & Florent Pillet.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 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 Library
* General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <signal.h>
#include "pi-debug.h"
#include "pi-source.h"
#include "pi-usb.h"
#include "pi-util.h"
#ifdef HAVE_SYS_IOCTL_COMPAT_H
#include <sys/ioctl_compat.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <usb.h>
#if defined(sun) && defined(__SVR4)
#define __FUNCTION__ __func__
#endif
static int u_open(struct pi_socket *ps, struct pi_sockaddr *addr, size_t addrlen);
static int u_close(struct pi_socket *ps);
static ssize_t u_write(struct pi_socket *ps, const unsigned char *buf, size_t len,
int flags);
static ssize_t u_read(struct pi_socket *ps, pi_buffer_t *buf, size_t len,
int flags);
static int u_read_i(struct pi_socket *ps, pi_buffer_t *buf, size_t len, int flags,
int timeout);
static int u_poll(struct pi_socket *ps, int timeout);
static int u_wait_for_device(struct pi_socket *ps, int *timeout);
static int u_flush(pi_socket_t *ps, int flags);
static int u_control_request (pi_usb_data_t *usb_data, int request_type,
int request, int value, int control_index, void *data, int size, int timeout);
void pi_usb_impl_init (struct pi_usb_impl *impl)
{
impl->open = u_open;
impl->close = u_close;
impl->write = u_write;
impl->read = u_read;
impl->flush = u_flush;
impl->poll = u_poll;
impl->wait_for_device = u_wait_for_device;
impl->changebaud = NULL; /* we don't need this one for libusb (yet) */
impl->control_request = u_control_request;
}
/***********************************************************************
*
* Start of the device identification code.
*
***********************************************************************/
static usb_dev_handle *USB_handle;
static int USB_interface;
static int USB_in_endpoint;
static int USB_out_endpoint;
static int
USB_open (pi_usb_data_t *data)
{
usb_init ();
return 1;
}
static int
USB_poll (pi_usb_data_t *data)
{
struct usb_bus *bus;
struct usb_device *dev;
int ret;
u_int8_t input_endpoint = 0xFF, output_endpoint = 0xFF;
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
int first;
#endif
usb_find_busses ();
usb_find_devices ();
CHECK (PI_DBG_DEV, PI_DBG_LVL_DEBUG, usb_set_debug (2));
for (bus = usb_busses; bus; bus = bus->next) {
for (dev = bus->devices; dev; dev = dev->next) {
int i;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: checking device %p\n",
__FILE__, dev));
if (dev->descriptor.bNumConfigurations < 1)
continue;
if (!dev->config)
continue;
if (dev->config[0].bNumInterfaces < 1)
continue;
if (dev->config[0].interface[0].num_altsetting < 1)
continue;
if (dev->config[0].interface[0].altsetting[0].bNumEndpoints < 2)
continue;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: %d, 0x%04x 0x%04x.\n",
__FILE__, __LINE__, dev->descriptor.idVendor, dev->descriptor.idProduct));
if (USB_check_device (data, dev->descriptor.idVendor, dev->descriptor.idProduct))
continue;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: trying to open device %p\n",
__FILE__, dev));
USB_handle = usb_open(dev);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: USB_handle=%p\n",
__FILE__, USB_handle));
data->ref = USB_handle;
input_endpoint = output_endpoint = 0xFF;
USB_in_endpoint = USB_out_endpoint = 0xFF;
ret = USB_configure_device (data, &input_endpoint, &output_endpoint);
if (ret < 0) {
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG,
"%s: USB configure failed for familar device: 0x%04x 0x%04x. (LifeDrive issue?)\n",
__FILE__, dev->descriptor.idVendor, dev->descriptor.idProduct));
usb_close(USB_handle);
continue;
}
for (i = 0; i < dev->config[0].interface[0].altsetting[0].bNumEndpoints; i++) {
struct usb_endpoint_descriptor *endpoint;
u_int8_t address;
endpoint = &dev->config[0].interface[0].altsetting[0].endpoint[i];
if (endpoint->wMaxPacketSize != 0x40)
continue;
if ((endpoint->bmAttributes & USB_ENDPOINT_TYPE_MASK) != USB_ENDPOINT_TYPE_BULK)
continue;
address = endpoint->bEndpointAddress;
if ((address & USB_ENDPOINT_DIR_MASK)) {
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "In: 0x%x 0x%x.\n", address, input_endpoint));
if (input_endpoint == 0xFF)
USB_in_endpoint = address;
else if ((address & USB_ENDPOINT_ADDRESS_MASK) == input_endpoint)
USB_in_endpoint = address;
} else {
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "Out: 0x%x 0x%x.\n", address, output_endpoint));
if (output_endpoint == 0xFF)
USB_out_endpoint = address;
else if ((address & USB_ENDPOINT_ADDRESS_MASK) == output_endpoint)
USB_out_endpoint = address;
}
}
if (USB_in_endpoint == 0xFF || USB_out_endpoint == 0xFF) {
usb_close (USB_handle);
continue;
}
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG,
"Config: %d, 0x%x 0x%x | 0x%x 0x%x.\n",
ret, input_endpoint, output_endpoint, USB_in_endpoint, USB_out_endpoint));
USB_interface = dev->config[0].interface[0].altsetting[0].bInterfaceNumber;
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
first = 1;
claim:
#endif
i = usb_claim_interface (USB_handle, USB_interface);
if (i < 0) {
if (i == -EBUSY) {
LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "Unable to claim device: Busy.\n"));
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
if (first) {
usb_detach_kernel_driver_np (USB_handle, USB_interface);
first = 0;
goto claim;
}
#endif
} else if (i == -ENOMEM)
LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "Unable to claim device: No memory.\n"));
else
LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "Unable to claim device: %d.\n", i));
usb_close (USB_handle);
errno = -i;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: %d.\n",
__FILE__, __LINE__));
return 0;
}
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s: %d.\n",
__FILE__, __LINE__));
return 1;
}
}
errno = ENODEV;
CHECK (PI_DBG_DEV, PI_DBG_LVL_DEBUG, usb_set_debug (0));
return 0;
}
static int
USB_close (void)
{
if (!USB_handle)
return 0;
usb_release_interface (USB_handle, USB_interface);
usb_close (USB_handle);
USB_handle = NULL;
return 1;
}
/***********************************************************************
*
* Start of the read thread code, please note that all of this runs
* in a separate thread.
*
***********************************************************************/
#define MAX_READ_SIZE 16384
#define AUTO_READ_SIZE 64
static char *RD_buffer = NULL;
static size_t RD_buffer_size;
static size_t RD_buffer_used;
static pthread_mutex_t RD_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t RD_buffer_available_cond = PTHREAD_COND_INITIALIZER;
static int RD_wanted;
static int RD_running = 0;
static char RD_usb_buffer[MAX_READ_SIZE];
static pthread_t RD_thread = 0;
static void
RD_do_read (int timeout)
{
int bytes_read, read_size;
read_size = RD_wanted - RD_buffer_used;
if (read_size < AUTO_READ_SIZE)
read_size = AUTO_READ_SIZE;
else if (read_size > MAX_READ_SIZE)
read_size = MAX_READ_SIZE;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "Reading: len: %d, timeout: %d.\n", read_size, timeout));
bytes_read = usb_bulk_read (USB_handle, USB_in_endpoint, RD_usb_buffer, read_size, timeout);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d\n",
__FILE__, __LINE__, __FUNCTION__, bytes_read));
if (bytes_read < 0) {
if (bytes_read == -ENODEV) {
LOG((PI_DBG_DEV, PI_DBG_LVL_NONE, "Device went byebye!\n"));
RD_running = 0;
return;
#ifdef ELAST
} else if (bytes_read == -(ELAST + 1)) {
usb_clear_halt (USB_handle, USB_in_endpoint);
return;
#endif
} else if (bytes_read == -ETIMEDOUT)
return;
LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "libusb: USB bulk read returned error code %d\n", bytes_read));
return;
}
if (!bytes_read)
return;
pthread_mutex_lock (&RD_buffer_mutex);
if ((RD_buffer_used + bytes_read) > RD_buffer_size) {
RD_buffer_size = ((RD_buffer_used + bytes_read + 0xfffe) & ~0xffff) - 1; /* 64k chunks. */
RD_buffer = realloc (RD_buffer, RD_buffer_size);
}
memcpy (RD_buffer + RD_buffer_used, RD_usb_buffer, bytes_read);
RD_buffer_used += bytes_read;
pthread_cond_broadcast (&RD_buffer_available_cond);
pthread_mutex_unlock (&RD_buffer_mutex);
}
static void *
RD_main (void *foo)
{
RD_buffer_used = 0;
RD_buffer = NULL;
RD_buffer_size = 0;
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
while (RD_running == 1) {
RD_do_read (0);
}
RD_running = 0;
return NULL;
}
static int
RD_start (void)
{
if (RD_thread || RD_running)
return 0;
RD_running = 1;
pthread_create (&RD_thread, NULL, RD_main, NULL);
return 1;
}
static int
RD_stop (void)
{
if (!RD_thread && !RD_running)
return 0;
if (RD_running)
RD_running = 0;
if (RD_thread) {
pthread_cancel(RD_thread);
RD_thread = 0;
}
if (RD_thread || RD_running)
return 0;
return 1;
}
/***********************************************************************
*
* Start of the glue code which makes this whole mess WORK.
*
***********************************************************************/
static int
u_open(struct pi_socket *ps, struct pi_sockaddr *addr, size_t addrlen)
{
pi_usb_data_t *data = (pi_usb_data_t *)ps->device->data;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
if (RD_running)
return -1;
if (!USB_open (data))
return -1;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
return 1;
}
static int
u_close(struct pi_socket *ps)
{
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
RD_stop ();
USB_close ();
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
return close (ps->sd);
}
static int
u_wait_for_device(struct pi_socket *ps, int *timeout)
{
pi_usb_data_t *data = (pi_usb_data_t *)ps->device->data;
struct timespec when;
int ret = 0;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
if (*timeout)
pi_timeout_to_timespec (*timeout, &when);
while (1) {
ret = USB_poll (data);
if (ret > 0) {
/* Evil, calculate how much longer the timeout is. */
if (*timeout) {
*timeout = pi_timespec_to_timeout (&when);
if (*timeout <= 0)
*timeout = 1;
}
if (!RD_start ()) {
USB_close ();
return -1;
}
return ret;
}
if (*timeout) {
if (pi_timeout_expired(&when)) {
*timeout = 1;
return 0;
}
}
usleep (500000);
}
return 0;
}
static int
u_poll(struct pi_socket *ps, int timeout)
{
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
return u_read_i (ps, NULL, 1, PI_MSG_PEEK, timeout);
}
static ssize_t
u_write(struct pi_socket *ps, const unsigned char *buf, size_t len, int flags)
{
int timeout = ((struct pi_usb_data *)ps->device->data)->timeout;
int ret;
if (!RD_running)
return -1;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "Writing: len: %d, flags: %d, timeout: %d.\n", len, flags, timeout));
if (len <= 0)
return 0;
ret = usb_bulk_write (USB_handle, USB_out_endpoint, buf, len, timeout);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "Wrote: %d.\n", ret));
if (ret > 0)
CHECK (PI_DBG_DEV, PI_DBG_LVL_DEBUG, pi_dumpdata (buf, ret));
return (ssize_t)ret;
}
static ssize_t
u_read(struct pi_socket *ps, pi_buffer_t *buf, size_t len, int flags)
{
int ret;
ret = u_read_i (ps, buf, len, flags, ((struct pi_usb_data *)ps->device->data)->timeout);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "Read: %d (%d).\n", ret, len));
if (ret > 0)
CHECK (PI_DBG_DEV, PI_DBG_LVL_DEBUG, pi_dumpdata (buf->data, ret));
return (ssize_t)ret;
}
static int
u_read_i(struct pi_socket *ps, pi_buffer_t *buf, size_t len, int flags, int timeout)
{
if (!RD_running)
return PI_ERR_SOCK_DISCONNECTED;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d %d\n",
__FILE__, __LINE__, __FUNCTION__, len, flags, timeout));
pthread_mutex_lock (&RD_buffer_mutex);
if (flags & PI_MSG_PEEK && len > 256)
len = 256;
if (RD_buffer_used < len) {
struct timeval now;
struct timespec when, nownow;
int last_used;
gettimeofday(&now, NULL);
when.tv_sec = now.tv_sec + timeout / 1000;
when.tv_nsec = (now.tv_usec + (timeout % 1000) * 1000) * 1000;
if (when.tv_nsec >= 1000000000) {
when.tv_nsec -= 1000000000;
when.tv_sec++;
}
RD_wanted = len;
do {
last_used = RD_buffer_used;
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
if (timeout) {
gettimeofday(&now, NULL);
nownow.tv_sec = now.tv_sec;
nownow.tv_nsec = now.tv_usec * 1000;
if ((nownow.tv_sec == when.tv_sec ? (nownow.tv_nsec > when.tv_nsec) : (nownow.tv_sec > when.tv_sec))) {
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
break;
}
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
if (pthread_cond_timedwait (&RD_buffer_available_cond, &RD_buffer_mutex, &when) == ETIMEDOUT) {
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
break;
}
} else
pthread_cond_wait (&RD_buffer_available_cond, &RD_buffer_mutex);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
} while (RD_buffer_used < len);
RD_wanted = 0;
}
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s): %d %d.\n",
__FILE__, __LINE__, __FUNCTION__, len, RD_buffer_used));
if (!RD_running) {
pthread_mutex_unlock (&RD_buffer_mutex);
return PI_ERR_SOCK_DISCONNECTED;
}
if (RD_buffer_used < len)
len = RD_buffer_used;
if (len && buf) {
pi_buffer_append (buf, RD_buffer, len);
if (!(flags & PI_MSG_PEEK)) {
RD_buffer_used -= len;
if (RD_buffer_used)
memmove (RD_buffer, RD_buffer + len, RD_buffer_used);
if ((RD_buffer_size - RD_buffer_used) > (1024 * 1024)) {
/* If we have more then 1M free in the buffer, shrink it. */
RD_buffer_size = ((RD_buffer_used + 0xfffe) & ~0xffff) - 1;
RD_buffer = realloc (RD_buffer, RD_buffer_size);
}
}
}
pthread_mutex_unlock (&RD_buffer_mutex);
LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "%s %d (%s).\n",
__FILE__, __LINE__, __FUNCTION__));
return len;
}
static int
u_flush(pi_socket_t *ps, int flags)
{
if (flags & PI_FLUSH_INPUT) {
/* clear internal buffer */
pthread_mutex_lock (&RD_buffer_mutex);
RD_buffer_used = 0;
pthread_mutex_unlock (&RD_buffer_mutex);
}
return 0;
}
static int
u_control_request (pi_usb_data_t *usb_data, int request_type, int request,
int value, int control_index, void *data, int size, int timeout)
{
return usb_control_msg (usb_data->ref, request_type, request, value, control_index, data, size, timeout);
}
/* vi: set ts=8 sw=4 sts=4 noexpandtab: cin */
/* ex: set tabstop=4 expandtab: */
/* Local Variables: */
/* indent-tabs-mode: t */
/* c-basic-offset: 8 */
/* End: */