/* * $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 #endif #include #include #include #include #include #include "pi-debug.h" #include "pi-source.h" #include "pi-usb.h" #include "pi-util.h" #ifdef HAVE_SYS_IOCTL_COMPAT_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #include #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: */